返回 oh-my-ppt
settingsStore.ts
根目录 / src / renderer / src / store / settingsStore.ts
1 import { create } from 'zustand'
2 import {
3 ipc,
4 type ImageModelConfig,
5 type ImageModelProvider,
6 type ModelConfig
7 } from '@renderer/lib/ipc'
8 import type { ConfigurableModelTimeoutProfile } from '@shared/model-timeout.js'
9 import type { ThinkingParameterMode } from '@shared/model-config.js'
10 import type { ImageModelVerificationResult } from '@shared/image-generation.js'
11
12 interface Settings {
13 theme: string
14 locale: 'zh' | 'en'
15 storagePath: string
16 timeouts: Record<ConfigurableModelTimeoutProfile, number>
17 proxyUrl: string
18 proxyUsername: string
19 proxyPassword: string
20 proxyNoProxy: string
21 }
22
23 interface SettingsStore {
24 settings: Settings | null
25 modelConfigs: ModelConfig[]
26 imageModelConfigs: ImageModelConfig[]
27 verificationMessage: string | null
28 storagePathError: string | null
29 loading: boolean
30
31 fetchSettings: () => Promise<void>
32 saveSettings: (settings: Partial<Settings>) => Promise<void>
33 upsertModelConfig: (config: {
34 id?: string
35 name: string
36 provider: 'anthropic' | 'openai' | 'openai-responses' | 'google'
37 model: string
38 apiKey: string
39 baseUrl: string
40 maxTokens?: number
41 disableTemperature?: boolean
42 thinkingParameterMode?: ThinkingParameterMode
43 active?: boolean
44 }) => Promise<string | null>
45 upsertImageModelConfig: (config: {
46 id?: string
47 name: string
48 provider: ImageModelProvider
49 active?: boolean
50 modelConfig: string
51 }) => Promise<string | null>
52 setActiveModelConfig: (id: string) => Promise<void>
53 setActiveImageModelConfig: (id: string) => Promise<void>
54 deleteModelConfig: (id: string) => Promise<void>
55 deleteImageModelConfig: (id: string) => Promise<void>
56 setVerificationMessage: (message: string | null) => void
57 verifyApiKey: (
58 provider: string,
59 apiKey: string,
60 model: string,
61 baseUrl: string,
62 maxTokens: number,
63 disableTemperature: boolean,
64 thinkingParameterMode: ThinkingParameterMode,
65 timeoutMs: number
66 ) => Promise<boolean>
67 fetchModelList: (
68 provider: string,
69 apiKey: string,
70 baseUrl: string
71 ) => Promise<{ models: string[]; message: string | null }>
72 verifyImageModel: (
73 provider: ImageModelProvider,
74 modelConfig: string
75 ) => Promise<ImageModelVerificationResult>
76 chooseStoragePath: () => Promise<string | null>
77 }
78
79 const readStoredLocale = (): 'zh' | 'en' => {
80 if (typeof window === 'undefined') return 'zh'
81 return window.localStorage.getItem('oh-my-ppt:lang') === 'en' ? 'en' : 'zh'
82 }
83
84 const fallbackMessage = (zh: string, en: string): string => (readStoredLocale() === 'en' ? en : zh)
85
86 export const useSettingsStore = create<SettingsStore>((set, get) => ({
87 settings: null,
88 modelConfigs: [],
89 imageModelConfigs: [],
90 verificationMessage: null,
91 storagePathError: null,
92 loading: false,
93
94 fetchSettings: async () => {
95 try {
96 const [settings, modelConfigs, imageModelConfigs] = await Promise.all([
97 ipc.getSettings(),
98 ipc.listModelConfigs(),
99 ipc.listImageModelConfigs()
100 ])
101 const typedSettings = settings as unknown as Settings
102 const locale = typedSettings.locale === 'en' ? 'en' : 'zh'
103 set({
104 settings: {
105 ...typedSettings,
106 locale
107 },
108 modelConfigs: Array.isArray(modelConfigs) ? modelConfigs : [],
109 imageModelConfigs: Array.isArray(imageModelConfigs) ? imageModelConfigs : [],
110 storagePathError: null,
111 verificationMessage: null
112 })
113 } catch (error) {
114 const message =
115 error instanceof Error && error.message
116 ? error.message
117 : fallbackMessage('读取设置失败。', 'Failed to read settings.')
118 set({ verificationMessage: message })
119 }
120 },
121
122 saveSettings: async (newSettings) => {
123 set({ verificationMessage: null })
124 const settingsToSave: Partial<Settings> = { ...newSettings }
125
126 try {
127 await ipc.saveSettings(settingsToSave)
128 await get().fetchSettings()
129 } catch (error) {
130 const message =
131 error instanceof Error && error.message
132 ? error.message
133 : fallbackMessage('保存设置失败。', 'Failed to save settings.')
134 set({ verificationMessage: message })
135 }
136 },
137
138 upsertModelConfig: async (config) => {
139 set({ verificationMessage: null })
140 try {
141 const result = await ipc.upsertModelConfig(config)
142 await get().fetchSettings()
143 return result.id
144 } catch (error) {
145 const message =
146 error instanceof Error && error.message
147 ? error.message
148 : fallbackMessage('保存模型失败。', 'Failed to save model.')
149 set({ verificationMessage: message })
150 return null
151 }
152 },
153
154 setActiveModelConfig: async (id) => {
155 set({ verificationMessage: null })
156 try {
157 await ipc.setActiveModelConfig(id)
158 await get().fetchSettings()
159 } catch (error) {
160 const message =
161 error instanceof Error && error.message
162 ? error.message
163 : fallbackMessage('启用模型失败。', 'Failed to activate model.')
164 set({ verificationMessage: message })
165 }
166 },
167
168 upsertImageModelConfig: async (config) => {
169 set({ verificationMessage: null })
170 try {
171 const result = await ipc.upsertImageModelConfig(config)
172 await get().fetchSettings()
173 return result.id
174 } catch (error) {
175 const message =
176 error instanceof Error && error.message
177 ? error.message
178 : fallbackMessage('保存生图模型失败。', 'Failed to save image model.')
179 set({ verificationMessage: message })
180 return null
181 }
182 },
183
184 setActiveImageModelConfig: async (id) => {
185 set({ verificationMessage: null })
186 try {
187 await ipc.setActiveImageModelConfig(id)
188 await get().fetchSettings()
189 } catch (error) {
190 const message =
191 error instanceof Error && error.message
192 ? error.message
193 : fallbackMessage('启用生图模型失败。', 'Failed to activate image model.')
194 set({ verificationMessage: message })
195 }
196 },
197
198 deleteModelConfig: async (id) => {
199 set({ verificationMessage: null })
200 try {
201 await ipc.deleteModelConfig(id)
202 await get().fetchSettings()
203 } catch (error) {
204 const message =
205 error instanceof Error && error.message
206 ? error.message
207 : fallbackMessage('删除模型失败。', 'Failed to delete model.')
208 set({ verificationMessage: message })
209 }
210 },
211
212 deleteImageModelConfig: async (id) => {
213 set({ verificationMessage: null })
214 try {
215 await ipc.deleteImageModelConfig(id)
216 await get().fetchSettings()
217 } catch (error) {
218 const message =
219 error instanceof Error && error.message
220 ? error.message
221 : fallbackMessage('删除生图模型失败。', 'Failed to delete image model.')
222 set({ verificationMessage: message })
223 }
224 },
225
226 setVerificationMessage: (message) => set({ verificationMessage: message }),
227
228 verifyApiKey: async (
229 provider,
230 apiKey,
231 model,
232 baseUrl,
233 maxTokens,
234 disableTemperature,
235 thinkingParameterMode,
236 timeoutMs
237 ) => {
238 try {
239 const { valid, message } = await ipc.verifyApiKey({
240 provider,
241 apiKey,
242 model,
243 baseUrl,
244 maxTokens,
245 disableTemperature,
246 thinkingParameterMode,
247 timeoutMs
248 })
249 set({ verificationMessage: message || null })
250 return valid
251 } catch (error) {
252 const message =
253 error instanceof Error && error.message
254 ? error.message
255 : fallbackMessage('发送验证请求失败。', 'Failed to send verification request.')
256 set({ verificationMessage: message })
257 return false
258 }
259 },
260
261 fetchModelList: async (provider, apiKey, baseUrl) => {
262 try {
263 return await ipc.fetchModelList({ provider, apiKey, baseUrl })
264 } catch (error) {
265 const message =
266 error instanceof Error && error.message
267 ? error.message
268 : fallbackMessage('发送模型列表请求失败。', 'Failed to request the model list.')
269 return { models: [], message }
270 }
271 },
272
273 verifyImageModel: async (provider, modelConfig) => {
274 try {
275 const result = await ipc.verifyImageModel({
276 provider,
277 modelConfig
278 })
279 set({ verificationMessage: result.message || null })
280 return result
281 } catch (error) {
282 const message =
283 error instanceof Error && error.message
284 ? error.message
285 : fallbackMessage('发送生图验证请求失败。', 'Failed to verify image model.')
286 set({ verificationMessage: message })
287 return { valid: false, message }
288 }
289 },
290
291 chooseStoragePath: async () => {
292 set({ storagePathError: null })
293 try {
294 const { path, error } = await ipc.chooseStoragePath()
295 set({ storagePathError: error || null })
296 return path
297 } catch (error) {
298 const message =
299 error instanceof Error && error.message
300 ? error.message
301 : fallbackMessage('选择文件夹失败。', 'Failed to choose folder.')
302 set({ storagePathError: message })
303 return null
304 }
305 }
306 }))
307
307 lines TYPESCRIPT