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