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