-
Notifications
You must be signed in to change notification settings - Fork 649
Expand file tree
/
Copy pathindex.ts
More file actions
1211 lines (1037 loc) · 39 KB
/
index.ts
File metadata and controls
1211 lines (1037 loc) · 39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { eventBus, SendTarget } from '@/eventbus'
import {
IConfigPresenter,
LLM_PROVIDER,
MODEL_META,
ModelConfig,
RENDERER_MODEL_META,
MCPServerConfig,
Prompt,
IModelConfig,
BuiltinKnowledgeConfig
} from '@shared/presenter'
import { SearchEngineTemplate } from '@shared/chat'
import { ModelType } from '@shared/model'
import ElectronStore from 'electron-store'
import { DEFAULT_PROVIDERS } from './providers'
import path from 'path'
import { app, nativeTheme, shell } from 'electron'
import fs from 'fs'
import { CONFIG_EVENTS, SYSTEM_EVENTS, FLOATING_BUTTON_EVENTS } from '@/events'
import { McpConfHelper, SYSTEM_INMEM_MCP_SERVERS } from './mcpConfHelper'
import { presenter } from '@/presenter'
import { compare } from 'compare-versions'
import { defaultShortcutKey, ShortcutKeySetting } from './shortcutKeySettings'
import { ModelConfigHelper } from './modelConfig'
import { KnowledgeConfHelper } from './knowledgeConfHelper'
// 定义应用设置的接口
interface IAppSettings {
// 在这里定义你的配置项,例如:
language: string
providers: LLM_PROVIDER[]
closeToQuit: boolean // 是否点击关闭按钮时退出程序
appVersion?: string // 用于版本检查和数据迁移
proxyMode?: string // 代理模式:system, none, custom
customProxyUrl?: string // 自定义代理地址
customShortKey?: ShortcutKeySetting // 自定义快捷键
artifactsEffectEnabled?: boolean // artifacts动画效果是否启用
searchPreviewEnabled?: boolean // 搜索预览是否启用
contentProtectionEnabled?: boolean // 投屏保护是否启用
syncEnabled?: boolean // 是否启用同步功能
syncFolderPath?: string // 同步文件夹路径
lastSyncTime?: number // 上次同步时间
customSearchEngines?: string // 自定义搜索引擎JSON字符串
soundEnabled?: boolean // 音效是否启用
copyWithCotEnabled?: boolean
loggingEnabled?: boolean // 日志记录是否启用
floatingButtonEnabled?: boolean // 悬浮按钮是否启用
floatingButtonPosition?: { x: number; y: number } // 悬浮按钮自定义位置
default_system_prompt?: string // 默认系统提示词
[key: string]: unknown // 允许任意键,使用unknown类型替代any
}
// 为模型存储创建接口
interface IModelStore {
models: MODEL_META[]
custom_models: MODEL_META[]
}
const defaultProviders = DEFAULT_PROVIDERS.map((provider) => ({
id: provider.id,
name: provider.name,
apiType: provider.apiType,
apiKey: provider.apiKey,
baseUrl: provider.baseUrl,
enable: provider.enable,
websites: provider.websites
}))
// 定义 storeKey 常量
const PROVIDERS_STORE_KEY = 'providers'
const PROVIDER_MODELS_DIR = 'provider_models'
// 模型状态键前缀
const MODEL_STATUS_KEY_PREFIX = 'model_status_'
export class ConfigPresenter implements IConfigPresenter {
private store: ElectronStore<IAppSettings>
private providersModelStores: Map<string, ElectronStore<IModelStore>> = new Map()
private customPromptsStore: ElectronStore<{ prompts: Prompt[] }>
private userDataPath: string
private currentAppVersion: string
private mcpConfHelper: McpConfHelper // 使用MCP配置助手
private modelConfigHelper: ModelConfigHelper // 模型配置助手
private knowledgeConfHelper: KnowledgeConfHelper // 知识配置助手
// Model status memory cache for high-frequency read/write operations
private modelStatusCache: Map<string, boolean> = new Map()
constructor() {
this.userDataPath = app.getPath('userData')
this.currentAppVersion = app.getVersion()
// 初始化应用设置存储
this.store = new ElectronStore<IAppSettings>({
name: 'app-settings',
defaults: {
language: 'system',
providers: defaultProviders,
closeToQuit: false,
customShortKey: defaultShortcutKey,
proxyMode: 'system',
customProxyUrl: '',
artifactsEffectEnabled: true,
searchPreviewEnabled: true,
contentProtectionEnabled: false,
syncEnabled: false,
syncFolderPath: path.join(this.userDataPath, 'sync'),
lastSyncTime: 0,
soundEnabled: false,
copyWithCotEnabled: true,
loggingEnabled: false,
floatingButtonEnabled: false,
floatingButtonPosition: undefined,
default_system_prompt: '',
appVersion: this.currentAppVersion
}
})
this.initTheme()
// 初始化 custom prompts 存储
this.customPromptsStore = new ElectronStore<{ prompts: Prompt[] }>({
name: 'custom_prompts',
defaults: {
prompts: []
}
})
// 初始化MCP配置助手
this.mcpConfHelper = new McpConfHelper()
// 初始化模型配置助手
this.modelConfigHelper = new ModelConfigHelper()
// 初始化知识配置助手
this.knowledgeConfHelper = new KnowledgeConfHelper()
// 初始化provider models目录
this.initProviderModelsDir()
// 如果应用版本更新了,更新appVersion
if (this.store.get('appVersion') !== this.currentAppVersion) {
const oldVersion = this.store.get('appVersion')
this.store.set('appVersion', this.currentAppVersion)
// 迁移数据
this.migrateModelData(oldVersion)
this.mcpConfHelper.onUpgrade(oldVersion)
}
const existingProviders = this.getSetting<LLM_PROVIDER[]>(PROVIDERS_STORE_KEY) || []
const newProviders = defaultProviders.filter(
(defaultProvider) =>
!existingProviders.some((existingProvider) => existingProvider.id === defaultProvider.id)
)
if (newProviders.length > 0) {
this.setProviders([...existingProviders, ...newProviders])
}
}
private initProviderModelsDir(): void {
const modelsDir = path.join(this.userDataPath, PROVIDER_MODELS_DIR)
if (!fs.existsSync(modelsDir)) {
fs.mkdirSync(modelsDir, { recursive: true })
}
}
private getProviderModelStore(providerId: string): ElectronStore<IModelStore> {
if (!this.providersModelStores.has(providerId)) {
const store = new ElectronStore<IModelStore>({
name: `models_${providerId}`,
cwd: path.join(this.userDataPath, PROVIDER_MODELS_DIR),
defaults: {
models: [],
custom_models: []
}
})
this.providersModelStores.set(providerId, store)
}
return this.providersModelStores.get(providerId)!
}
private migrateModelData(oldVersion: string | undefined): void {
// 0.2.4 版本之前,minimax 的 baseUrl 是错误的,需要修正
if (oldVersion && compare(oldVersion, '0.2.4', '<')) {
const providers = this.getProviders()
for (const provider of providers) {
if (provider.id === 'minimax') {
provider.baseUrl = 'https://api.minimax.chat/v1'
this.setProviderById('minimax', provider)
}
}
}
// 0.0.10 版本之前,模型数据存储在app-settings.json中
if (oldVersion && compare(oldVersion, '0.0.10', '<')) {
// 迁移旧的模型数据
const providers = this.getProviders()
for (const provider of providers) {
// 检查并修正 ollama 的 baseUrl
if (provider.id === 'ollama' && provider.baseUrl) {
if (provider.baseUrl.endsWith('/v1')) {
provider.baseUrl = provider.baseUrl.replace(/\/v1$/, '')
// 保存修改后的提供者
this.setProviderById('ollama', provider)
}
}
// 迁移provider模型
const oldProviderModelsKey = `${provider.id}_models`
const oldModels =
this.getSetting<(MODEL_META & { enabled: boolean })[]>(oldProviderModelsKey)
if (oldModels && oldModels.length > 0) {
const store = this.getProviderModelStore(provider.id)
// 遍历旧模型,保存启用状态
oldModels.forEach((model) => {
if (model.enabled) {
this.setModelStatus(provider.id, model.id, true)
}
// @ts-ignore - 需要删除enabled属性以便独立存储状态
delete model.enabled
})
// 保存模型列表到新存储
store.set('models', oldModels)
// 清除旧存储
this.store.delete(oldProviderModelsKey)
}
// 迁移custom模型
const oldCustomModelsKey = `custom_models_${provider.id}`
const oldCustomModels =
this.getSetting<(MODEL_META & { enabled: boolean })[]>(oldCustomModelsKey)
if (oldCustomModels && oldCustomModels.length > 0) {
const store = this.getProviderModelStore(provider.id)
// 遍历旧的自定义模型,保存启用状态
oldCustomModels.forEach((model) => {
if (model.enabled) {
this.setModelStatus(provider.id, model.id, true)
}
// @ts-ignore - 需要删除enabled属性以便独立存储状态
delete model.enabled
})
// 保存自定义模型列表到新存储
store.set('custom_models', oldCustomModels)
// 清除旧存储
this.store.delete(oldCustomModelsKey)
}
}
}
// 0.0.17 版本之前,需要移除 qwenlm 提供商
if (oldVersion && compare(oldVersion, '0.0.17', '<')) {
// 获取当前所有提供商
const providers = this.getProviders()
// 过滤掉 qwenlm 提供商
const filteredProviders = providers.filter((provider) => provider.id !== 'qwenlm')
// 如果过滤后数量不同,说明有移除操作,需要保存更新后的提供商列表
if (filteredProviders.length !== providers.length) {
this.setProviders(filteredProviders)
}
}
}
getSetting<T>(key: string): T | undefined {
try {
return this.store.get(key) as T
} catch (error) {
console.error(`[Config] Failed to get setting ${key}:`, error)
return undefined
}
}
setSetting<T>(key: string, value: T): void {
try {
this.store.set(key, value)
// 触发设置变更事件(仅主进程内部使用)
eventBus.sendToMain(CONFIG_EVENTS.SETTING_CHANGED, key, value)
} catch (error) {
console.error(`[Config] Failed to set setting ${key}:`, error)
}
}
getProviders(): LLM_PROVIDER[] {
const providers = this.getSetting<LLM_PROVIDER[]>(PROVIDERS_STORE_KEY)
if (Array.isArray(providers) && providers.length > 0) {
return providers
} else {
this.setSetting(PROVIDERS_STORE_KEY, defaultProviders)
return defaultProviders
}
}
setProviders(providers: LLM_PROVIDER[]): void {
this.setSetting<LLM_PROVIDER[]>(PROVIDERS_STORE_KEY, providers)
// 触发新事件(需要通知所有标签页)
eventBus.send(CONFIG_EVENTS.PROVIDER_CHANGED, SendTarget.ALL_WINDOWS)
}
getProviderById(id: string): LLM_PROVIDER | undefined {
const providers = this.getProviders()
return providers.find((provider) => provider.id === id)
}
setProviderById(id: string, provider: LLM_PROVIDER): void {
const providers = this.getProviders()
const index = providers.findIndex((p) => p.id === id)
if (index !== -1) {
providers[index] = provider
this.setProviders(providers)
} else {
console.error(`[Config] Provider ${id} not found`)
}
}
// 构造模型状态的存储键
private getModelStatusKey(providerId: string, modelId: string): string {
// 将 modelId 中的点号替换为连字符
const formattedModelId = modelId.replace(/\./g, '-')
return `${MODEL_STATUS_KEY_PREFIX}${providerId}_${formattedModelId}`
}
// 获取模型启用状态 (带内存缓存优化)
getModelStatus(providerId: string, modelId: string): boolean {
const statusKey = this.getModelStatusKey(providerId, modelId)
// First check memory cache
if (this.modelStatusCache.has(statusKey)) {
return this.modelStatusCache.get(statusKey)!
}
// Cache miss: read from settings and cache the result
const status = this.getSetting<boolean>(statusKey)
const finalStatus = typeof status === 'boolean' ? status : true
this.modelStatusCache.set(statusKey, finalStatus)
return finalStatus
}
// 批量获取模型启用状态 (带内存缓存优化)
getBatchModelStatus(providerId: string, modelIds: string[]): Record<string, boolean> {
const result: Record<string, boolean> = {}
const uncachedKeys: string[] = []
const uncachedModelIds: string[] = []
// First pass: check cache for all models
for (const modelId of modelIds) {
const statusKey = this.getModelStatusKey(providerId, modelId)
if (this.modelStatusCache.has(statusKey)) {
result[modelId] = this.modelStatusCache.get(statusKey)!
} else {
uncachedKeys.push(statusKey)
uncachedModelIds.push(modelId)
}
}
// Second pass: fetch uncached values from settings and cache them
for (let i = 0; i < uncachedModelIds.length; i++) {
const modelId = uncachedModelIds[i]
const statusKey = uncachedKeys[i]
const status = this.getSetting<boolean>(statusKey)
const finalStatus = typeof status === 'boolean' ? status : true
// Cache the result and add to return object
this.modelStatusCache.set(statusKey, finalStatus)
result[modelId] = finalStatus
}
return result
}
// 设置模型启用状态 (同步更新内存缓存)
setModelStatus(providerId: string, modelId: string, enabled: boolean): void {
const statusKey = this.getModelStatusKey(providerId, modelId)
// Update both settings and memory cache synchronously
this.setSetting(statusKey, enabled)
this.modelStatusCache.set(statusKey, enabled)
// 触发模型状态变更事件(需要通知所有标签页)
eventBus.sendToRenderer(
CONFIG_EVENTS.MODEL_STATUS_CHANGED,
SendTarget.ALL_WINDOWS,
providerId,
modelId,
enabled
)
}
// 启用模型
enableModel(providerId: string, modelId: string): void {
this.setModelStatus(providerId, modelId, true)
}
// 禁用模型
disableModel(providerId: string, modelId: string): void {
this.setModelStatus(providerId, modelId, false)
}
// 清理模型状态缓存 (用于配置重载或重置场景)
clearModelStatusCache(): void {
this.modelStatusCache.clear()
}
// 清理特定 provider 的模型状态缓存
clearProviderModelStatusCache(providerId: string): void {
const keysToDelete: string[] = []
for (const key of this.modelStatusCache.keys()) {
if (key.startsWith(`${MODEL_STATUS_KEY_PREFIX}${providerId}_`)) {
keysToDelete.push(key)
}
}
keysToDelete.forEach((key) => this.modelStatusCache.delete(key))
}
// 批量设置模型状态
batchSetModelStatus(providerId: string, modelStatusMap: Record<string, boolean>): void {
for (const [modelId, enabled] of Object.entries(modelStatusMap)) {
this.setModelStatus(providerId, modelId, enabled)
}
}
getProviderModels(providerId: string): MODEL_META[] {
const store = this.getProviderModelStore(providerId)
let models = store.get('models') || []
models = models.map((model) => {
const config = this.getModelConfig(model.id, providerId)
if (config) {
model.maxTokens = config.maxTokens
model.contextLength = config.contextLength
// 如果模型中已经有这些属性则保留,否则使用配置中的值或默认为false
model.vision = model.vision !== undefined ? model.vision : config.vision || false
model.functionCall =
model.functionCall !== undefined ? model.functionCall : config.functionCall || false
model.reasoning =
model.reasoning !== undefined ? model.reasoning : config.reasoning || false
model.type = model.type !== undefined ? model.type : config.type || ModelType.Chat
} else {
// 确保模型具有这些属性,如果没有配置,默认为false
model.vision = model.vision || false
model.functionCall = model.functionCall || false
model.reasoning = model.reasoning || false
model.type = model.type || ModelType.Chat
}
return model
})
return models
}
getModelDefaultConfig(modelId: string, providerId?: string): ModelConfig {
const model = this.getModelConfig(modelId, providerId)
if (model) {
return model
}
return {
maxTokens: 4096,
contextLength: 4096,
temperature: 0.7,
vision: false,
functionCall: false,
reasoning: false,
type: ModelType.Chat
}
}
setProviderModels(providerId: string, models: MODEL_META[]): void {
const store = this.getProviderModelStore(providerId)
store.set('models', models)
}
getEnabledProviders(): LLM_PROVIDER[] {
const providers = this.getProviders()
return providers.filter((provider) => provider.enable)
}
getAllEnabledModels(): Promise<{ providerId: string; models: RENDERER_MODEL_META[] }[]> {
const enabledProviders = this.getEnabledProviders()
return Promise.all(
enabledProviders.map(async (provider) => {
const providerId = provider.id
const allModels = [
...this.getProviderModels(providerId),
...this.getCustomModels(providerId)
]
// 批量获取模型状态
const modelIds = allModels.map((model) => model.id)
const modelStatusMap = this.getBatchModelStatus(providerId, modelIds)
// 根据批量获取的状态过滤启用的模型
const enabledModels = allModels
.filter((model) => modelStatusMap[model.id])
.map((model) => ({
...model,
enabled: true,
// 确保能力属性被复制
vision: model.vision || false,
functionCall: model.functionCall || false,
reasoning: model.reasoning || false
}))
return {
providerId,
models: enabledModels
}
})
)
}
getCustomModels(providerId: string): MODEL_META[] {
const store = this.getProviderModelStore(providerId)
let customModels = store.get('custom_models') || []
// 确保自定义模型也有能力属性
customModels = customModels.map((model) => {
// 如果模型已经有这些属性,保留它们,否则默认为false
model.vision = model.vision !== undefined ? model.vision : false
model.functionCall = model.functionCall !== undefined ? model.functionCall : false
model.reasoning = model.reasoning !== undefined ? model.reasoning : false
return model
})
return customModels
}
setCustomModels(providerId: string, models: MODEL_META[]): void {
const store = this.getProviderModelStore(providerId)
store.set('custom_models', models)
}
addCustomModel(providerId: string, model: MODEL_META): void {
const models = this.getCustomModels(providerId)
const existingIndex = models.findIndex((m) => m.id === model.id)
// 创建不包含enabled属性的模型副本
const modelWithoutStatus: MODEL_META = { ...model }
// @ts-ignore - 需要删除enabled属性以便独立存储状态
delete modelWithoutStatus.enabled
if (existingIndex !== -1) {
models[existingIndex] = modelWithoutStatus as MODEL_META
} else {
models.push(modelWithoutStatus as MODEL_META)
}
this.setCustomModels(providerId, models)
// 单独设置模型状态
this.setModelStatus(providerId, model.id, true)
// 触发模型列表变更事件(需要通知所有标签页)
eventBus.sendToRenderer(CONFIG_EVENTS.MODEL_LIST_CHANGED, SendTarget.ALL_WINDOWS, providerId)
}
removeCustomModel(providerId: string, modelId: string): void {
const models = this.getCustomModels(providerId)
const filteredModels = models.filter((model) => model.id !== modelId)
this.setCustomModels(providerId, filteredModels)
// 删除模型状态
const statusKey = this.getModelStatusKey(providerId, modelId)
this.store.delete(statusKey)
// 触发模型列表变更事件(需要通知所有标签页)
eventBus.sendToRenderer(CONFIG_EVENTS.MODEL_LIST_CHANGED, SendTarget.ALL_WINDOWS, providerId)
}
updateCustomModel(providerId: string, modelId: string, updates: Partial<MODEL_META>): void {
const models = this.getCustomModels(providerId)
const index = models.findIndex((model) => model.id === modelId)
if (index !== -1) {
Object.assign(models[index], updates)
this.setCustomModels(providerId, models)
eventBus.sendToRenderer(CONFIG_EVENTS.MODEL_LIST_CHANGED, SendTarget.ALL_WINDOWS, providerId)
}
}
getCloseToQuit(): boolean {
return this.getSetting<boolean>('closeToQuit') ?? false
}
setCloseToQuit(value: boolean): void {
this.setSetting('closeToQuit', value)
}
// 获取应用当前语言,考虑系统语言设置
getLanguage(): string {
const language = this.getSetting<string>('language') || 'system'
if (language !== 'system') {
return language
}
return this.getSystemLanguage()
}
// 设置应用语言
setLanguage(language: string): void {
this.setSetting('language', language)
// 触发语言变更事件(需要通知所有标签页)
eventBus.sendToRenderer(CONFIG_EVENTS.LANGUAGE_CHANGED, SendTarget.ALL_WINDOWS, language)
}
// 获取系统语言并匹配支持的语言列表
private getSystemLanguage(): string {
const systemLang = app.getLocale()
const supportedLanguages = [
'zh-CN',
'zh-TW',
'en-US',
'zh-HK',
'ko-KR',
'ru-RU',
'ja-JP',
'fr-FR',
'fa-IR'
]
// 完全匹配
if (supportedLanguages.includes(systemLang)) {
return systemLang
}
// 部分匹配(只匹配语言代码)
const langCode = systemLang.split('-')[0]
const matchedLang = supportedLanguages.find((lang) => lang.startsWith(langCode))
if (matchedLang) {
return matchedLang
}
// 默认返回英文
return 'en-US'
}
public getDefaultProviders(): LLM_PROVIDER[] {
return DEFAULT_PROVIDERS
}
// 获取代理模式
getProxyMode(): string {
return this.getSetting<string>('proxyMode') || 'system'
}
// 设置代理模式
setProxyMode(mode: string): void {
this.setSetting('proxyMode', mode)
eventBus.sendToMain(CONFIG_EVENTS.PROXY_MODE_CHANGED, mode)
}
// 获取自定义代理地址
getCustomProxyUrl(): string {
return this.getSetting<string>('customProxyUrl') || ''
}
// 设置自定义代理地址
setCustomProxyUrl(url: string): void {
this.setSetting('customProxyUrl', url)
eventBus.sendToMain(CONFIG_EVENTS.CUSTOM_PROXY_URL_CHANGED, url)
}
// 获取同步功能状态
getSyncEnabled(): boolean {
return this.getSetting<boolean>('syncEnabled') || false
}
// 获取日志文件夹路径
getLoggingFolderPath(): string {
return path.join(this.userDataPath, 'logs')
}
// 打开日志文件夹
async openLoggingFolder(): Promise<void> {
const loggingFolderPath = this.getLoggingFolderPath()
// 如果文件夹不存在,先创建它
if (!fs.existsSync(loggingFolderPath)) {
fs.mkdirSync(loggingFolderPath, { recursive: true })
}
// 打开文件夹
await shell.openPath(loggingFolderPath)
}
// 设置同步功能状态
setSyncEnabled(enabled: boolean): void {
console.log('setSyncEnabled', enabled)
this.setSetting('syncEnabled', enabled)
eventBus.send(CONFIG_EVENTS.SYNC_SETTINGS_CHANGED, SendTarget.ALL_WINDOWS, { enabled })
}
// 获取同步文件夹路径
getSyncFolderPath(): string {
return (
this.getSetting<string>('syncFolderPath') || path.join(app.getPath('home'), 'DeepchatSync')
)
}
// 设置同步文件夹路径
setSyncFolderPath(folderPath: string): void {
this.setSetting('syncFolderPath', folderPath)
eventBus.send(CONFIG_EVENTS.SYNC_SETTINGS_CHANGED, SendTarget.ALL_WINDOWS, { folderPath })
}
// 获取上次同步时间
getLastSyncTime(): number {
return this.getSetting<number>('lastSyncTime') || 0
}
// 设置上次同步时间
setLastSyncTime(time: number): void {
this.setSetting('lastSyncTime', time)
}
// 获取自定义搜索引擎
async getCustomSearchEngines(): Promise<SearchEngineTemplate[]> {
try {
const customEnginesJson = this.store.get('customSearchEngines')
if (customEnginesJson) {
return JSON.parse(customEnginesJson as string)
}
return []
} catch (error) {
console.error('获取自定义搜索引擎失败:', error)
return []
}
}
// 设置自定义搜索引擎
async setCustomSearchEngines(engines: SearchEngineTemplate[]): Promise<void> {
try {
this.store.set('customSearchEngines', JSON.stringify(engines))
// 发送事件通知搜索引擎更新(需要通知所有标签页)
eventBus.send(CONFIG_EVENTS.SEARCH_ENGINES_UPDATED, SendTarget.ALL_WINDOWS, engines)
} catch (error) {
console.error('设置自定义搜索引擎失败:', error)
throw error
}
}
// 获取搜索预览设置状态
getSearchPreviewEnabled(): Promise<boolean> {
const value = this.getSetting<boolean>('searchPreviewEnabled')
// 默认关闭搜索预览
return Promise.resolve(value === undefined || value === null ? false : value)
}
// 设置搜索预览状态
setSearchPreviewEnabled(enabled: boolean): void {
console.log('ConfigPresenter.setSearchPreviewEnabled:', enabled, typeof enabled)
// 确保传入的是布尔值
const boolValue = Boolean(enabled)
this.setSetting('searchPreviewEnabled', boolValue)
}
// 获取投屏保护设置状态
getContentProtectionEnabled(): boolean {
const value = this.getSetting<boolean>('contentProtectionEnabled')
// 默认投屏保护关闭
return value === undefined || value === null ? false : value
}
// 设置投屏保护状态
setContentProtectionEnabled(enabled: boolean): void {
this.setSetting('contentProtectionEnabled', enabled)
eventBus.send(CONFIG_EVENTS.CONTENT_PROTECTION_CHANGED, SendTarget.ALL_WINDOWS, enabled)
}
getLoggingEnabled(): boolean {
return this.getSetting<boolean>('loggingEnabled') ?? false
}
setLoggingEnabled(enabled: boolean): void {
this.setSetting('loggingEnabled', enabled)
setTimeout(() => {
presenter.devicePresenter.restartApp()
}, 1000)
}
// 获取音效开关状态
getSoundEnabled(): boolean {
const value = this.getSetting<boolean>('soundEnabled') ?? false
return value === undefined || value === null ? false : value
}
// 设置音效开关状态
setSoundEnabled(enabled: boolean): void {
this.setSetting('soundEnabled', enabled)
eventBus.sendToRenderer(CONFIG_EVENTS.SOUND_ENABLED_CHANGED, SendTarget.ALL_WINDOWS, enabled)
}
getCopyWithCotEnabled(): boolean {
const value = this.getSetting<boolean>('copyWithCotEnabled') ?? true
return value === undefined || value === null ? false : value
}
setCopyWithCotEnabled(enabled: boolean): void {
this.setSetting('copyWithCotEnabled', enabled)
eventBus.sendToRenderer(CONFIG_EVENTS.COPY_WITH_COT_CHANGED, SendTarget.ALL_WINDOWS, enabled)
}
// 获取悬浮按钮开关状态
getFloatingButtonEnabled(): boolean {
const value = this.getSetting<boolean>('floatingButtonEnabled') ?? false
return value === undefined || value === null ? false : value
}
// 设置悬浮按钮开关状态
setFloatingButtonEnabled(enabled: boolean): void {
this.setSetting('floatingButtonEnabled', enabled)
eventBus.sendToMain(FLOATING_BUTTON_EVENTS.ENABLED_CHANGED, enabled)
try {
presenter.floatingButtonPresenter.setEnabled(enabled)
} catch (error) {
console.error('Failed to directly call floatingButtonPresenter:', error)
}
}
// 获取悬浮按钮位置
getFloatingButtonPosition(): { x: number; y: number } | null {
return this.getSetting<{ x: number; y: number }>('floatingButtonPosition') || null
}
// 设置悬浮按钮位置
setFloatingButtonPosition(position: { x: number; y: number }): void {
this.setSetting('floatingButtonPosition', position)
}
// ===================== MCP配置相关方法 =====================
// 获取MCP服务器配置
async getMcpServers(): Promise<Record<string, MCPServerConfig>> {
const servers = await this.mcpConfHelper.getMcpServers()
// 检查是否有自定义提示词,如果有则添加 custom-prompts-server
try {
const customPrompts = await this.getCustomPrompts()
if (customPrompts && customPrompts.length > 0) {
const customPromptsServerName = 'deepchat-inmemory/custom-prompts-server'
const systemServers = SYSTEM_INMEM_MCP_SERVERS[customPromptsServerName]
if (systemServers && !servers[customPromptsServerName]) {
servers[customPromptsServerName] = systemServers
servers[customPromptsServerName].disable = false
servers[customPromptsServerName].autoApprove = ['all']
}
}
} catch {
// 检查自定义提示词时出错
}
return servers
}
// 设置MCP服务器配置
async setMcpServers(servers: Record<string, MCPServerConfig>): Promise<void> {
return this.mcpConfHelper.setMcpServers(servers)
}
// 获取默认MCP服务器
getMcpDefaultServers(): Promise<string[]> {
return this.mcpConfHelper.getMcpDefaultServers()
}
// 设置默认MCP服务器
async addMcpDefaultServer(serverName: string): Promise<void> {
return this.mcpConfHelper.addMcpDefaultServer(serverName)
}
async removeMcpDefaultServer(serverName: string): Promise<void> {
return this.mcpConfHelper.removeMcpDefaultServer(serverName)
}
async toggleMcpDefaultServer(serverName: string): Promise<void> {
return this.mcpConfHelper.toggleMcpDefaultServer(serverName)
}
// 获取MCP启用状态
getMcpEnabled(): Promise<boolean> {
return this.mcpConfHelper.getMcpEnabled()
}
// 设置MCP启用状态
async setMcpEnabled(enabled: boolean): Promise<void> {
return this.mcpConfHelper.setMcpEnabled(enabled)
}
// 添加MCP服务器
async addMcpServer(name: string, config: MCPServerConfig): Promise<boolean> {
return this.mcpConfHelper.addMcpServer(name, config)
}
// 移除MCP服务器
async removeMcpServer(name: string): Promise<void> {
return this.mcpConfHelper.removeMcpServer(name)
}
// 更新MCP服务器配置
async updateMcpServer(name: string, config: Partial<MCPServerConfig>): Promise<void> {
await this.mcpConfHelper.updateMcpServer(name, config)
}
// 提供getMcpConfHelper方法,用于获取MCP配置助手
getMcpConfHelper(): McpConfHelper {
return this.mcpConfHelper
}
/**
* 获取指定provider和model的推荐配置
* @param modelId 模型ID
* @param providerId 可选的提供商ID,如果提供则优先查找该提供商的特定配置
* @returns ModelConfig 模型配置
*/
getModelConfig(modelId: string, providerId?: string): ModelConfig {
return this.modelConfigHelper.getModelConfig(modelId, providerId)
}
/**
* Set custom model configuration for a specific provider and model
* @param modelId - The model ID
* @param providerId - The provider ID
* @param config - The model configuration
*/
setModelConfig(modelId: string, providerId: string, config: ModelConfig): void {
this.modelConfigHelper.setModelConfig(modelId, providerId, config)
// 触发模型配置变更事件(需要通知所有标签页)
eventBus.sendToRenderer(
CONFIG_EVENTS.MODEL_CONFIG_CHANGED,
SendTarget.ALL_WINDOWS,
providerId,
modelId,
config
)
}
/**
* Reset model configuration for a specific provider and model
* @param modelId - The model ID
* @param providerId - The provider ID
*/
resetModelConfig(modelId: string, providerId: string): void {
this.modelConfigHelper.resetModelConfig(modelId, providerId)
// 触发模型配置重置事件(需要通知所有标签页)
eventBus.sendToRenderer(
CONFIG_EVENTS.MODEL_CONFIG_RESET,
SendTarget.ALL_WINDOWS,
providerId,
modelId
)
}
/**
* Get all user-defined model configurations
*/
getAllModelConfigs(): Record<string, IModelConfig> {
return this.modelConfigHelper.getAllModelConfigs()
}
/**
* Get configurations for a specific provider
* @param providerId - The provider ID
*/
getProviderModelConfigs(providerId: string): Array<{ modelId: string; config: ModelConfig }> {
return this.modelConfigHelper.getProviderModelConfigs(providerId)
}
/**
* Check if a model has user-defined configuration
* @param modelId - The model ID
* @param providerId - The provider ID
*/
hasUserModelConfig(modelId: string, providerId: string): boolean {
return this.modelConfigHelper.hasUserConfig(modelId, providerId)
}
/**
* Export all model configurations for backup/sync
*/
exportModelConfigs(): Record<string, IModelConfig> {
return this.modelConfigHelper.exportConfigs()
}
/**
* Import model configurations for restore/sync
* @param configs - Model configurations to import
* @param overwrite - Whether to overwrite existing configurations
*/
importModelConfigs(configs: Record<string, IModelConfig>, overwrite: boolean = false): void {
this.modelConfigHelper.importConfigs(configs, overwrite)
// 触发批量导入事件(需要通知所有标签页)
eventBus.sendToRenderer(CONFIG_EVENTS.MODEL_CONFIGS_IMPORTED, SendTarget.ALL_WINDOWS, overwrite)
}
getNotificationsEnabled(): boolean {
const value = this.getSetting<boolean>('notificationsEnabled')
if (value === undefined) {