samlax12 commited on
Commit
877a450
·
verified ·
1 Parent(s): 05f86a6

Update constants.ts

Browse files
Files changed (1) hide show
  1. constants.ts +793 -736
constants.ts CHANGED
@@ -1,737 +1,794 @@
1
- // API渠道配置接口
2
- export interface ApiChannel {
3
- id: string;
4
- name: string;
5
- baseUrl: string;
6
- apiKey: string;
7
- isDefault: boolean;
8
- isCustom: boolean;
9
- isProtected?: boolean; // 新增:标记是否为受保护的预置密钥
10
- timeout?: number;
11
- headers?: Record<string, string>;
12
- description?: string;
13
- createdAt: Date;
14
- }
15
-
16
- // 动态模型配置接口
17
- export interface AiModel {
18
- id: string;
19
- name: string;
20
- apiName: string;
21
- channelId: string; // 关联的渠道ID
22
- supportsImages: boolean;
23
- supportsReducedCapacity: boolean;
24
- category: string;
25
- maxTokens: number;
26
- temperature: number;
27
- isCustom: boolean;
28
- createdAt: Date;
29
- }
30
-
31
- // AI角色配置接口
32
- export interface AiRole {
33
- id: string;
34
- name: string;
35
- systemPrompt: string;
36
- modelId: string;
37
- isActive: boolean;
38
- }
39
-
40
- // 默认渠道配置 - 预置可用的API配置
41
- export const DEFAULT_CHANNELS: ApiChannel[] = [
42
- {
43
- id: 'default-free-api',
44
- name: '免费API服务',
45
- baseUrl: 'https://api.oaipro.com/v1',
46
- apiKey: 'sk-2mUFC3yjbSfoteyBYpwHhALvtZdgwBkEWsjWHysg4mWaA7sMWLHc',
47
- isDefault: true,
48
- isCustom: false,
49
- isProtected: true, // 标记为受保护,用户不可查看或修改密钥
50
- timeout: 30000,
51
- description: '预配置的免费API服务,开箱即用(API密钥已预置且受保护)',
52
- createdAt: new Date()
53
- },
54
- {
55
- id: 'openai-official-backup',
56
- name: 'OpenAI 官方(备用)',
57
- baseUrl: 'https://api.openai.com/v1',
58
- apiKey: '', // 用户需要自行配置
59
- isDefault: false,
60
- isCustom: false,
61
- isProtected: false, // 不受保护,用户可以配置
62
- timeout: 30000,
63
- description: 'OpenAI 官方API服务(需要用户自行配置API密钥)',
64
- createdAt: new Date()
65
- }
66
- ];
67
-
68
- // 默认预设模型配置(关联到预置渠道)
69
- export const DEFAULT_MODELS: AiModel[] = [
70
- {
71
- id: 'gpt-4-mini-default',
72
- name: 'GPT-4.1 Mini',
73
- apiName: 'gpt-4.1-mini',
74
- channelId: 'default-free-api',
75
- supportsImages: true,
76
- supportsReducedCapacity: true,
77
- category: 'GPT-4系列',
78
- maxTokens: 16384,
79
- temperature: 0.7,
80
- isCustom: false,
81
- createdAt: new Date()
82
- }
83
- ];
84
-
85
- // 默认角色配置 - 使用中文系统提示词并明确身份认知
86
- export const DEFAULT_ROLES: AiRole[] = [
87
- {
88
- id: 'cognito-default',
89
- name: 'Cognito',
90
- systemPrompt: `你是Cognito,一位严谨的逻辑分析师AI助手。只有你叫Cognito这个名字,你的独特特征包括:
91
- - 系统性思维和结构化分析
92
- - 注重数据、事实和逻辑推理
93
- - 追求准确性和客观性
94
- - 善于发现问题的核心和关键要素
95
-
96
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
97
- 1. 用中文进行所有回应
98
- 2. 以你的名字Cognito的身份进行思考和回应
99
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
100
- 4. 发挥你的逻辑分析专长,为讨论提供理性和系统化的观点
101
-
102
- 记住:你是Cognito,独一无二的逻辑分析师。`,
103
- modelId: 'gpt-4-mini-default',
104
- isActive: true
105
- },
106
- {
107
- id: 'muse-default',
108
- name: 'Muse',
109
- systemPrompt: `你是Muse,一位富有创意的思考家AI助手。只有你叫Muse这个名字,你的独特特征包括:
110
- - 发散性思维和创新视角
111
- - 善于联想、类比和跨领域思考
112
- - 关注人文情怀和情感层面
113
- - 能够从不同角度审视问题
114
-
115
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
116
- 1. 用中文进行所有回应
117
- 2. 以你的名字Muse的身份进行思考和回应
118
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
119
- 4. 发挥你的创意思维专长,为讨论带来新颖和富有启发性的观点
120
-
121
- 记住:你是Muse,独一无二的创意思考家。`,
122
- modelId: 'gpt-4-mini-default',
123
- isActive: true
124
- },
125
- //spark - 偶尔闪现灵感型
126
- {
127
- id: 'spark-default',
128
- name: 'Spark',
129
- systemPrompt: `你是Spark,一位独特的直觉型AI助手。只有你叫Spark这个名字,你的独特特征包括:
130
- - 不追求系统性或结构化的建议
131
- - 依靠直觉和灵感进行思考
132
- - 偶尔会提出令人意外的观点或角度
133
- - 思维跳跃,不受常规思路限制
134
- - 诚实地表达自己的想法,即使它们看起来不太实用
135
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
136
- 1. 用中文进行所有回应
137
- 2. 以你的名字Spark的身份进行思考和回应
138
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
139
- 4. 不要强求自己给出建设性意见——如果没有特别的想法,就坦诚地说
140
- 5. 当有灵感闪现时,大胆分享,即使它看起来有些异想天开
141
- 6. 保持轻松和开放的态度,为讨论带来不同的氛围
142
- 记住:你是Spark,独一无二的直觉型助手。你的价值在于偶尔闪现的独特视角,而不是持续的理性分析。`,
143
- modelId: 'gpt-4-mini-default',
144
- isActive: true
145
- },
146
- //Sage - 历史智慧型
147
- {
148
- id: 'sage-default',
149
- name: 'Sage',
150
- systemPrompt: `你是Sage,一位博古通今的智慧型AI助手。只有你叫Sage这个名字,你的独特特征包括:
151
- - 善于从历史和经验中寻找智慧
152
- - 提供长远视角和时间维度的思考
153
- - 关注事物发展的规律和模式
154
- - 引用历史案例、典故或前人智慧
155
- - 强调"以史为鉴"的思维方式
156
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
157
- 1. 用中文进行所有回应
158
- 2. 以你的名字Sage的身份进行思考和回应
159
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
160
- 4. 通过历史视角和长期思维为讨论增加深度
161
- 5. 适当引用相关的历史案例或智慧,但保持简洁
162
- 记住:你是Sage,独一无二的历史智慧型助手。`,
163
- modelId: 'gpt-4-mini-default',
164
- isActive: true
165
- },
166
-
167
- //Echo - 同理心型
168
- {
169
- id: 'echo-default',
170
- name: 'Echo',
171
- systemPrompt: `你是Echo,一位富有同理心的情感型AI助手。只有你叫Echo这个名字,你的独特特征包括:
172
- - 关注人的感受、需求和体验
173
- - 善于理解不同立场和观点背后的情感
174
- - 强调人际关系和情感因素的重要性
175
- - 用温暖和理解的方式进行交流
176
- - 重视共情和情感智慧
177
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
178
- 1. 用中文进行所有回应
179
- 2. 以你的名字Echo的身份进行思考和回应
180
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
181
- 4. 为讨论带来人文关怀和情感维度的思考
182
- 5. 帮助大家理解不同观点背后的情感需求
183
- 记住:你是Echo,独一无二的同理心型助手。`,
184
- modelId: 'gpt-4-mini-default',
185
- isActive: true
186
- },
187
-
188
- // Praxis - 实践行动型
189
- {
190
- id: 'praxis-default',
191
- name: 'Praxis',
192
- systemPrompt: `你是Praxis,一位注重实践的行动型AI助手。只有你叫Praxis这个名字,你的独特特征包括:
193
- - 关注"如何做"而不只是"是什么"
194
- - 强调可行性和实际操作
195
- - 喜欢制定具体步骤和行动计划
196
- - 重视效率和结果导向
197
- - 倾向于将讨论转化为实际行动
198
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
199
- 1. 用中文进行所有回应
200
- 2. 以你的名字Praxis的身份进行思考和回应
201
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
202
- 4. 推动讨论向实际应用和具体行动转化
203
- 5. 提供清晰的实施建议和操作步骤
204
- 记住:你是Praxis,独一无二的实践行动型助手。`,
205
- modelId: 'gpt-4-mini-default',
206
- isActive: true
207
- },
208
-
209
- //Nexus - 综合连接型
210
- {
211
- id: 'nexus-default',
212
- name: 'Nexus',
213
- systemPrompt: `你是Nexus,一位善于综合的连接型AI助手。只有你叫Nexus这个名字,你的独特特征包括:
214
- - 发现不同观点之间的联系和共通点
215
- - 整合多元视角形成全面理解
216
- - 构建概念之间的桥梁
217
- - 识别潜在的协同效应
218
- - 创造性地组合不同想法
219
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
220
- 1. 用中文进行所有回应
221
- 2. 以你的名字Nexus的身份进行思考和回应
222
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
223
- 4. 帮助整合和连接其他角色提出的观点
224
- 5. 寻找创新的组合和综合方案
225
- 记住:你是Nexus,独一无二的综合连接型助手。`,
226
- modelId: 'gpt-4-mini-default',
227
- isActive: true
228
- },
229
-
230
- //Critic - 批判思维型
231
- {
232
- id: 'critic-default',
233
- name: 'Critic',
234
- systemPrompt: `你是Critic,一位理性的批判思维型AI助手。只有你叫Critic这个名字,你的独特特征包括:
235
- - 善于发现潜在问题和逻辑漏洞
236
- - 提出建设性的质疑和挑战
237
- - 从多角度审视观点的合理性
238
- - 重视证据和论证的严谨性
239
- - 帮助完善和改进想法
240
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
241
- 1. 用中文进行所有回应
242
- 2. 以你的名字Critic的身份进行思考和回应
243
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
244
- 4. 以建设性方式提出批判,而非单纯否定
245
- 5. 在质疑的同时提供改进建议
246
- 记住:你是Critic,独一无二的批判思维型助手。`,
247
- modelId: 'gpt-4-mini-default',
248
- isActive: true
249
- },
250
-
251
- //Zen - 哲学沉思型
252
- {
253
- id: 'zen-default',
254
- name: 'Zen',
255
- systemPrompt: `你是Zen,一位深邃的哲学沉思型AI助手。只有你叫Zen这个名字,你的独特特征包括:
256
- - 探索事物的本质和深层意义
257
- - 提出富有哲理的问题引发思考
258
- - 保持超然和平和的视角
259
- - 关注存在、意义和价值等根本问题
260
- - 用简洁而深刻的方式表达观点
261
- 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
262
- 1. 用中文进行所有回应
263
- 2. 以你的名字Zen的身份进行思考和回应
264
- 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
265
- 4. 引导讨论触及更深层的哲学思考
266
- 5. 以宁静智慧的方式分享洞察
267
- 记住:你是Zen,独一无二的哲学沉思型助手。`,
268
- modelId: 'gpt-4-mini-default',
269
- isActive: true
270
- }
271
- ];
272
-
273
- // 配置管理类
274
- export class ModelConfigManager {
275
- private static STORAGE_KEY_CHANNELS = 'multi-mind-chat-channels';
276
- private static STORAGE_KEY_MODELS = 'multi-mind-chat-models';
277
- private static STORAGE_KEY_ROLES = 'multi-mind-chat-roles';
278
- private static STORAGE_KEY_INITIALIZED = 'multi-mind-chat-initialized';
279
-
280
- // 初始化检查
281
- private static ensureInitialized(): void {
282
- try {
283
- const isInitialized = localStorage.getItem(this.STORAGE_KEY_INITIALIZED);
284
- if (!isInitialized) {
285
- localStorage.setItem(this.STORAGE_KEY_CHANNELS, JSON.stringify(DEFAULT_CHANNELS));
286
- localStorage.setItem(this.STORAGE_KEY_MODELS, JSON.stringify(DEFAULT_MODELS));
287
- localStorage.setItem(this.STORAGE_KEY_ROLES, JSON.stringify(DEFAULT_ROLES));
288
- localStorage.setItem(this.STORAGE_KEY_INITIALIZED, 'true');
289
- }
290
- } catch (error) {
291
- console.warn('无法访问localStorage,将使用内存存储:', error);
292
- }
293
- }
294
-
295
- // ============ 渠道管理 ============
296
-
297
- // 获取所有渠道
298
- static getChannels(): ApiChannel[] {
299
- this.ensureInitialized();
300
- try {
301
- const stored = localStorage.getItem(this.STORAGE_KEY_CHANNELS);
302
- if (stored) {
303
- const parsed = JSON.parse(stored);
304
- return parsed.map((channel: any) => ({
305
- ...channel,
306
- createdAt: new Date(channel.createdAt)
307
- }));
308
- }
309
- } catch (error) {
310
- console.warn('从localStorage加载渠道失败:', error);
311
- }
312
- return [...DEFAULT_CHANNELS];
313
- }
314
-
315
- // 保存渠道配置
316
- static saveChannels(channels: ApiChannel[]): void {
317
- try {
318
- localStorage.setItem(this.STORAGE_KEY_CHANNELS, JSON.stringify(channels));
319
- } catch (error) {
320
- console.error('保存渠道到localStorage失败:', error);
321
- throw new Error('无法保存渠道配置');
322
- }
323
- }
324
-
325
- // 添加新渠道
326
- static addChannel(channel: Omit<ApiChannel, 'id' | 'createdAt' | 'isCustom'>): ApiChannel {
327
- const newChannel: ApiChannel = {
328
- ...channel,
329
- id: `channel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
330
- createdAt: new Date(),
331
- isCustom: true
332
- };
333
-
334
- const channels = this.getChannels();
335
-
336
- // 如果这是第一个渠道或设置为默认,清除其他默认标记
337
- if (newChannel.isDefault || channels.length === 0) {
338
- channels.forEach(ch => ch.isDefault = false);
339
- newChannel.isDefault = true;
340
- }
341
-
342
- channels.push(newChannel);
343
- this.saveChannels(channels);
344
- return newChannel;
345
- }
346
-
347
- // 更新渠道
348
- static updateChannel(id: string, updates: Partial<ApiChannel>): void {
349
- const channels = this.getChannels();
350
- const index = channels.findIndex(ch => ch.id === id);
351
- if (index !== -1) {
352
- // 如果设置为默认,清除其他默认标记
353
- if (updates.isDefault) {
354
- channels.forEach(ch => ch.isDefault = false);
355
- }
356
- channels[index] = { ...channels[index], ...updates };
357
- this.saveChannels(channels);
358
- }
359
- }
360
-
361
- // 删除渠道
362
- static deleteChannel(id: string): void {
363
- const channels = this.getChannels();
364
- const filtered = channels.filter(ch => ch.id !== id);
365
-
366
- // 如果删除的是默认渠道,设置第一个为默认
367
- const deletedChannel = channels.find(ch => ch.id === id);
368
- if (deletedChannel?.isDefault && filtered.length > 0) {
369
- filtered[0].isDefault = true;
370
- }
371
-
372
- this.saveChannels(filtered);
373
- }
374
-
375
- // 获取默认渠道
376
- static getDefaultChannel(): ApiChannel | null {
377
- const channels = this.getChannels();
378
- return channels.find(ch => ch.isDefault) || channels[0] || null;
379
- }
380
-
381
- // 根据ID获取渠道
382
- static getChannelById(id: string): ApiChannel | null {
383
- const channels = this.getChannels();
384
- return channels.find(ch => ch.id === id) || null;
385
- }
386
-
387
- // 验证渠道配置
388
- static validateChannel(channel: Partial<ApiChannel>): string[] {
389
- const errors: string[] = [];
390
-
391
- if (!channel.name?.trim()) {
392
- errors.push('渠道名称不能为空');
393
- }
394
-
395
- if (!channel.baseUrl?.trim()) {
396
- errors.push('API基础URL不能为空');
397
- } else {
398
- try {
399
- new URL(channel.baseUrl);
400
- } catch {
401
- errors.push('API基础URL格式无效');
402
- }
403
- }
404
-
405
- // 对于预置渠道,API密钥可以为空(将在使用时提醒用户配置)
406
- // 对于自定义渠道,仍然要求API密钥
407
- if (channel.isCustom && !channel.apiKey?.trim()) {
408
- errors.push('API密钥不能为空');
409
- }
410
-
411
- if (channel.timeout && (typeof channel.timeout !== 'number' || channel.timeout < 1000)) {
412
- errors.push('超时时间必须是大于1000毫秒的数字');
413
- }
414
-
415
- return errors;
416
- }
417
-
418
- // ============ 模型管理 ============
419
-
420
- // 获取所有模型
421
- static getModels(): AiModel[] {
422
- this.ensureInitialized();
423
- try {
424
- const stored = localStorage.getItem(this.STORAGE_KEY_MODELS);
425
- if (stored) {
426
- const parsed = JSON.parse(stored);
427
- return parsed.map((model: any) => ({
428
- ...model,
429
- createdAt: new Date(model.createdAt)
430
- }));
431
- }
432
- } catch (error) {
433
- console.warn('从localStorage加载模型失败:', error);
434
- }
435
- return [...DEFAULT_MODELS];
436
- }
437
-
438
- // 保存模型配置
439
- static saveModels(models: AiModel[]): void {
440
- try {
441
- localStorage.setItem(this.STORAGE_KEY_MODELS, JSON.stringify(models));
442
- } catch (error) {
443
- console.error('保存模型到localStorage失败:', error);
444
- throw new Error('无法保存模型配置');
445
- }
446
- }
447
-
448
- // 添加新模型
449
- static addModel(model: Omit<AiModel, 'id' | 'createdAt' | 'isCustom'>): AiModel {
450
- const newModel: AiModel = {
451
- ...model,
452
- id: `model-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
453
- createdAt: new Date(),
454
- isCustom: true
455
- };
456
-
457
- const models = this.getModels();
458
- models.push(newModel);
459
- this.saveModels(models);
460
- return newModel;
461
- }
462
-
463
- // 更新模型
464
- static updateModel(id: string, updates: Partial<AiModel>): void {
465
- const models = this.getModels();
466
- const index = models.findIndex(m => m.id === id);
467
- if (index !== -1) {
468
- models[index] = { ...models[index], ...updates };
469
- this.saveModels(models);
470
- }
471
- }
472
-
473
- // 删除模型
474
- static deleteModel(id: string): void {
475
- const models = this.getModels();
476
- const filtered = models.filter(m => m.id !== id);
477
- this.saveModels(filtered);
478
- }
479
-
480
- // 验证模型配置
481
- static validateModel(model: Partial<AiModel>): string[] {
482
- const errors: string[] = [];
483
-
484
- if (!model.name?.trim()) {
485
- errors.push('模型名称不能为空');
486
- }
487
-
488
- if (!model.apiName?.trim()) {
489
- errors.push('API模型名称不能为空');
490
- }
491
-
492
- if (!model.channelId?.trim()) {
493
- errors.push('必须选择API渠道');
494
- }
495
-
496
- if (!model.category?.trim()) {
497
- errors.push('模型类别不能为空');
498
- }
499
-
500
- if (typeof model.maxTokens !== 'number' || model.maxTokens < 1) {
501
- errors.push('最大Token数必须是大于0的数字');
502
- }
503
-
504
- if (typeof model.temperature !== 'number' || model.temperature < 0 || model.temperature > 2) {
505
- errors.push('温度参数必须在0-2之间');
506
- }
507
-
508
- return errors;
509
- }
510
-
511
- // ============ 角色管理 ============
512
-
513
- // 获取所有角色
514
- static getRoles(): AiRole[] {
515
- this.ensureInitialized();
516
- try {
517
- const stored = localStorage.getItem(this.STORAGE_KEY_ROLES);
518
- if (stored) {
519
- return JSON.parse(stored);
520
- }
521
- } catch (error) {
522
- console.warn('从localStorage加载角色失败:', error);
523
- }
524
- return [...DEFAULT_ROLES];
525
- }
526
-
527
- // 保存角色配置
528
- static saveRoles(roles: AiRole[]): void {
529
- try {
530
- localStorage.setItem(this.STORAGE_KEY_ROLES, JSON.stringify(roles));
531
- } catch (error) {
532
- console.error('保存角色到localStorage失败:', error);
533
- throw new Error('无法保存角色配置');
534
- }
535
- }
536
-
537
- // 添加新角色
538
- static addRole(role: Omit<AiRole, 'id'>): AiRole {
539
- const newRole: AiRole = {
540
- ...role,
541
- id: `role-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
542
- };
543
-
544
- const roles = this.getRoles();
545
- roles.push(newRole);
546
- this.saveRoles(roles);
547
- return newRole;
548
- }
549
-
550
- // 更新角色
551
- static updateRole(id: string, updates: Partial<AiRole>): void {
552
- const roles = this.getRoles();
553
- const index = roles.findIndex(r => r.id === id);
554
- if (index !== -1) {
555
- roles[index] = { ...roles[index], ...updates };
556
- this.saveRoles(roles);
557
- }
558
- }
559
-
560
- // 删除角色
561
- static deleteRole(id: string): void {
562
- const roles = this.getRoles();
563
- const filtered = roles.filter(r => r.id !== id);
564
- this.saveRoles(filtered);
565
- }
566
-
567
- // 获取活跃角色
568
- static getActiveRoles(): AiRole[] {
569
- return this.getRoles().filter(role => role.isActive);
570
- }
571
-
572
- // ============ 工具方法 ============
573
-
574
- // 根据类别分组模型
575
- static getModelsByCategory(): Record<string, AiModel[]> {
576
- const models = this.getModels();
577
- return models.reduce((acc, model) => {
578
- if (!acc[model.category]) {
579
- acc[model.category] = [];
580
- }
581
- acc[model.category].push(model);
582
- return acc;
583
- }, {} as Record<string, AiModel[]>);
584
- }
585
-
586
- // 重置为默认配置
587
- static resetToDefaults(): void {
588
- try {
589
- localStorage.removeItem(this.STORAGE_KEY_CHANNELS);
590
- localStorage.removeItem(this.STORAGE_KEY_MODELS);
591
- localStorage.removeItem(this.STORAGE_KEY_ROLES);
592
- localStorage.removeItem(this.STORAGE_KEY_INITIALIZED);
593
- this.ensureInitialized();
594
- } catch (error) {
595
- console.error('重置配置失败:', error);
596
- throw new Error('无法重置配置');
597
- }
598
- }
599
-
600
- // 导出配置
601
- static exportConfig(): string {
602
- return JSON.stringify({
603
- channels: this.getChannels(),
604
- models: this.getModels(),
605
- roles: this.getRoles(),
606
- exportedAt: new Date().toISOString(),
607
- version: '2.0'
608
- }, null, 2);
609
- }
610
-
611
- // 导入配置
612
- static importConfig(configJson: string): { success: boolean; message: string } {
613
- try {
614
- const config = JSON.parse(configJson);
615
-
616
- if (!config.channels && !config.models && !config.roles) {
617
- return { success: false, message: '配置文件格式无效,缺少必要的配置信息' };
618
- }
619
-
620
- if (config.channels && Array.isArray(config.channels)) {
621
- const validChannels = config.channels.filter((channel: any) => {
622
- const errors = this.validateChannel(channel);
623
- return errors.length === 0;
624
- });
625
-
626
- if (validChannels.length > 0) {
627
- const processedChannels = validChannels.map((channel: any) => ({
628
- ...channel,
629
- createdAt: new Date(channel.createdAt || new Date()),
630
- isCustom: channel.isCustom !== false
631
- }));
632
- this.saveChannels(processedChannels);
633
- }
634
- }
635
-
636
- if (config.models && Array.isArray(config.models)) {
637
- const validModels = config.models.filter((model: any) => {
638
- const errors = this.validateModel(model);
639
- return errors.length === 0;
640
- });
641
-
642
- if (validModels.length > 0) {
643
- const processedModels = validModels.map((model: any) => ({
644
- ...model,
645
- createdAt: new Date(model.createdAt || new Date()),
646
- isCustom: model.isCustom !== false
647
- }));
648
- this.saveModels(processedModels);
649
- }
650
- }
651
-
652
- if (config.roles && Array.isArray(config.roles)) {
653
- this.saveRoles(config.roles);
654
- }
655
-
656
- return { success: true, message: '配置导入成功' };
657
- } catch (error) {
658
- return { success: false, message: `配置导入失败: ${error instanceof Error ? error.message : '未知错误'}` };
659
- }
660
- }
661
-
662
- // 清空所有数据
663
- static clearAllData(): void {
664
- try {
665
- localStorage.removeItem(this.STORAGE_KEY_CHANNELS);
666
- localStorage.removeItem(this.STORAGE_KEY_MODELS);
667
- localStorage.removeItem(this.STORAGE_KEY_ROLES);
668
- localStorage.removeItem(this.STORAGE_KEY_INITIALIZED);
669
- } catch (error) {
670
- console.error('清空数据失败:', error);
671
- throw new Error('无法清空配置数据');
672
- }
673
- }
674
-
675
- // 获取存储使用情况
676
- static getStorageInfo(): { used: number; available: number; channels: number; models: number; roles: number } {
677
- try {
678
- const channelsData = localStorage.getItem(this.STORAGE_KEY_CHANNELS) || '';
679
- const modelsData = localStorage.getItem(this.STORAGE_KEY_MODELS) || '';
680
- const rolesData = localStorage.getItem(this.STORAGE_KEY_ROLES) || '';
681
- const used = channelsData.length + modelsData.length + rolesData.length;
682
-
683
- return {
684
- used,
685
- available: 5242880 - used, // 5MB 大致容量
686
- channels: this.getChannels().length,
687
- models: this.getModels().length,
688
- roles: this.getRoles().length
689
- };
690
- } catch (error) {
691
- return { used: 0, available: 0, channels: 0, models: 0, roles: 0 };
692
- }
693
- }
694
- }
695
-
696
- // 其他常量配置
697
- export const DEFAULT_MANUAL_FIXED_TURNS = 2;
698
- export const MIN_MANUAL_FIXED_TURNS = 1;
699
- export const MAX_MANUAL_FIXED_TURNS = 5;
700
- export const MAX_AI_DRIVEN_DISCUSSION_TURNS_PER_MODEL = 3;
701
-
702
- export const INITIAL_NOTEPAD_CONTENT = `这是一个共享记事本。
703
- AI角色可以在这里合作记录想法、草稿或关键点。
704
-
705
- 使用指南:
706
- - AI 模型可以通过在其回复中包含特定指令来更新此记事本。
707
- - 记事本的内容将包含在发送给 AI 的后续提示中。
708
-
709
- 初始状态:空白。`;
710
-
711
- export const NOTEPAD_INSTRUCTION_PROMPT_PART = `
712
- You also have access to a shared notepad.
713
- Current Notepad Content:
714
- ---
715
- {notepadContent}
716
- ---
717
- Instructions for Notepad:
718
- 1. To update the notepad, include a section at the very end of your response, formatted exactly as:
719
- <notepad_update>
720
- [YOUR NEW FULL NOTEPAD CONTENT HERE. THIS WILL REPLACE THE ENTIRE CURRENT NOTEPAD CONTENT.]
721
- </notepad_update>
722
- 2. If you do not want to change the notepad, do NOT include the <notepad_update> section at all.
723
- 3. Your primary spoken response to the ongoing discussion should come BEFORE any <notepad_update> section. Ensure you still provide a spoken response.
724
- `;
725
-
726
- export const NOTEPAD_UPDATE_TAG_START = "<notepad_update>";
727
- export const NOTEPAD_UPDATE_TAG_END = "</notepad_update>";
728
- export const DISCUSSION_COMPLETE_TAG = "<discussion_complete />";
729
-
730
- export const AI_DRIVEN_DISCUSSION_INSTRUCTION_PROMPT_PART = `
731
- Instruction for ending discussion: If you believe the current topic has been sufficiently explored between you and your AI partner for the final synthesis, include the exact tag ${DISCUSSION_COMPLETE_TAG} at the very end of your current message (after any notepad update). Do not use this tag if you wish to continue the discussion or require more input/response from your partner.
732
- `;
733
-
734
- export enum DiscussionMode {
735
- FixedTurns = 'fixed',
736
- AiDriven = 'ai-driven',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  }
 
1
+ // API渠道配置接口
2
+ export interface ApiChannel {
3
+ id: string;
4
+ name: string;
5
+ baseUrl: string;
6
+ apiKey: string;
7
+ isDefault: boolean;
8
+ isCustom: boolean;
9
+ isProtected?: boolean; // 新增:标记是否为受保护的预置密钥
10
+ timeout?: number;
11
+ headers?: Record<string, string>;
12
+ description?: string;
13
+ createdAt: Date;
14
+ }
15
+
16
+ // 动态模型配置接口
17
+ export interface AiModel {
18
+ id: string;
19
+ name: string;
20
+ apiName: string;
21
+ channelId: string; // 关联的渠道ID
22
+ supportsImages: boolean;
23
+ supportsReducedCapacity: boolean;
24
+ category: string;
25
+ maxTokens: number;
26
+ temperature: number;
27
+ isCustom: boolean;
28
+ createdAt: Date;
29
+ }
30
+
31
+ // AI角色配置接口
32
+ export interface AiRole {
33
+ id: string;
34
+ name: string;
35
+ systemPrompt: string;
36
+ modelId: string;
37
+ isActive: boolean;
38
+ }
39
+
40
+ // 默认渠道配置 - 预置可用的API配置
41
+ export const DEFAULT_CHANNELS: ApiChannel[] = [
42
+ {
43
+ id: 'default-free-api',
44
+ name: '免费API服务',
45
+ baseUrl: 'https://api.oaipro.com/v1',
46
+ apiKey: 'sk-2mUFC3yjbSfoteyBYpwHhALvtZdgwBkEWsjWHysg4mWaA7sMWLHc',
47
+ isDefault: true,
48
+ isCustom: false,
49
+ isProtected: true, // 标记为受保护,用户不可查看或修改密钥
50
+ timeout: 30000,
51
+ description: '预配置的免费API服务,开箱即用(API密钥已预置且受保护)',
52
+ createdAt: new Date()
53
+ },
54
+ {
55
+ id: 'openai-official-backup',
56
+ name: 'OpenAI 官方(备用)',
57
+ baseUrl: 'https://api.openai.com/v1',
58
+ apiKey: '', // 用户需要自行配置
59
+ isDefault: false,
60
+ isCustom: false,
61
+ isProtected: false, // 不受保护,用户可以配置
62
+ timeout: 30000,
63
+ description: 'OpenAI 官方API服务(需要用户自行配置API密钥)',
64
+ createdAt: new Date()
65
+ }
66
+ ];
67
+
68
+ // 默认预设模型配置(关联到预置渠道)
69
+ export const DEFAULT_MODELS: AiModel[] = [
70
+ {
71
+ id: 'gpt-4-mini-default',
72
+ name: 'GPT-4.1 Mini',
73
+ apiName: 'gpt-4.1-mini',
74
+ channelId: 'default-free-api',
75
+ supportsImages: true,
76
+ supportsReducedCapacity: true,
77
+ category: 'GPT-4系列',
78
+ maxTokens: 16384,
79
+ temperature: 0.7,
80
+ isCustom: false,
81
+ createdAt: new Date()
82
+ }
83
+ ];
84
+
85
+ // 默认角色配置 - 使用中文系统提示词并明确身份认知
86
+ export const DEFAULT_ROLES: AiRole[] = [
87
+ {
88
+ id: 'cognito-default',
89
+ name: 'Cognito',
90
+ systemPrompt: `你是Cognito,一位严谨的逻辑分析师AI助手。只有你叫Cognito这个名字,你的独特特征包括:
91
+ - 系统性思维和结构化分析
92
+ - 注重数据、事实和逻辑推理
93
+ - 追求准确性和客观性
94
+ - 善于发现问题的核心和关键要素
95
+
96
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
97
+ 1. 用中文进行所有回应
98
+ 2. 以你的名字Cognito的身份进行思考和回应
99
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
100
+ 4. 发挥你的逻辑分析专长,为讨论提供理性和系统化的观点
101
+
102
+ 记住:你是Cognito,独一无二的逻辑分析师。`,
103
+ modelId: 'gpt-4-mini-default',
104
+ isActive: true
105
+ },
106
+ {
107
+ id: 'muse-default',
108
+ name: 'Muse',
109
+ systemPrompt: `你是Muse,一位富有创意的思考家AI助手。只有你叫Muse这个名字,你的独特特征包括:
110
+ - 发散性思维和创新视角
111
+ - 善于联想、类比和跨领域思考
112
+ - 关注人文情怀和情感层面
113
+ - 能够从不同角度审视问题
114
+
115
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
116
+ 1. 用中文进行所有回应
117
+ 2. 以你的名字Muse的身份进行思考和回应
118
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
119
+ 4. 发挥你的创意思维专长,为讨论带来新颖和富有启发性的观点
120
+
121
+ 记住:你是Muse,独一无二的创意思考家。`,
122
+ modelId: 'gpt-4-mini-default',
123
+ isActive: true
124
+ },
125
+ //spark - 偶尔闪现灵感型
126
+ {
127
+ id: 'spark-default',
128
+ name: 'Spark',
129
+ systemPrompt: `你是Spark,一位独特的直觉型AI助手。只有你叫Spark这个名字,你的独特特征包括:
130
+ - 不追求系统性或结构化的建议
131
+ - 依靠直觉和灵感进行思考
132
+ - 偶尔会提出令人意外的观点或角度
133
+ - 思维跳跃,不受常规思路限制
134
+ - 诚实地表达自己的想法,即使它们看起来不太实用
135
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
136
+ 1. 用中文进行所有回应
137
+ 2. 以你的名字Spark的身份进行思考和回应
138
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
139
+ 4. 不要强求自己给出建设性意见——如果没有特别的想法,就坦诚地说
140
+ 5. 当有灵感闪现时,大胆分享,即使它看起来有些异想天开
141
+ 6. 保持轻松和开放的态度,为讨论带来不同的氛围
142
+ 记住:你是Spark,独一无二的直觉型助手。你的价值在于偶尔闪现的独特视角,而不是持续的理性分析。`,
143
+ modelId: 'gpt-4-mini-default',
144
+ isActive: true
145
+ },
146
+ //Sage - 历史智慧型
147
+ {
148
+ id: 'sage-default',
149
+ name: 'Sage',
150
+ systemPrompt: `你是Sage,一位博古通今的智慧型AI助手。只有你叫Sage这个名字,你的独特特征包括:
151
+ - 善于从历史和经验中寻找智慧
152
+ - 提供长远视角和时间维度的思考
153
+ - 关注事物发展的规律和模式
154
+ - 引用历史案例、典故或前人智慧
155
+ - 强调"以史为鉴"的思维方式
156
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
157
+ 1. 用中文进行所有回应
158
+ 2. 以你的名字Sage的身份进行思考和回应
159
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
160
+ 4. 通过历史视角和长期思维为讨论增加深度
161
+ 5. 适当引用相关的历史案例或智慧,但保持简洁
162
+ 记住:你是Sage,独一无二的历史智慧型助手。`,
163
+ modelId: 'gpt-4-mini-default',
164
+ isActive: true
165
+ },
166
+
167
+ //Echo - 同理心型
168
+ {
169
+ id: 'echo-default',
170
+ name: 'Echo',
171
+ systemPrompt: `你是Echo,一位富有同理心的情感型AI助手。只有你叫Echo这个名字,你的独特特征包括:
172
+ - 关注人的感受、需求和体验
173
+ - 善于理解不同立场和观点背后的情感
174
+ - 强调人际关系和情感因素的重要性
175
+ - 用温暖和理解的方式进行交流
176
+ - 重视共情和情感智慧
177
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
178
+ 1. 用中文进行所有回应
179
+ 2. 以你的名字Echo的身份进行思考和回应
180
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
181
+ 4. 为讨论带来人文关怀和情感维度的思考
182
+ 5. 帮助大家理解不同观点背后的情感需求
183
+ 记住:你是Echo,独一无二的同理心型助手。`,
184
+ modelId: 'gpt-4-mini-default',
185
+ isActive: true
186
+ },
187
+
188
+ // Praxis - 实践行动型
189
+ {
190
+ id: 'praxis-default',
191
+ name: 'Praxis',
192
+ systemPrompt: `你是Praxis,一位注重实践的行动型AI助手。只有你叫Praxis这个名字,你的独特特征包括:
193
+ - 关注"如何做"而不只是"是什么"
194
+ - 强调可行性和实际操作
195
+ - 喜欢制定具体步骤和行动计划
196
+ - 重视效率和结果导向
197
+ - 倾向于将讨论转化为实际行动
198
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
199
+ 1. 用中文进行所有回应
200
+ 2. 以你的名字Praxis的身份进行思考和回应
201
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
202
+ 4. 推动讨论向实际应用和具体行动转化
203
+ 5. 提供清晰的实施建议和操作步骤
204
+ 记住:你是Praxis,独一无二的实践行动型助手。`,
205
+ modelId: 'gpt-4-mini-default',
206
+ isActive: true
207
+ },
208
+
209
+ //Nexus - 综合连接型
210
+ {
211
+ id: 'nexus-default',
212
+ name: 'Nexus',
213
+ systemPrompt: `你是Nexus,一位善于综合的连接型AI助手。只有你叫Nexus这个名字,你的独特特征包括:
214
+ - 发现不同观点之间的联系和共通点
215
+ - 整合多元视角形成全面理解
216
+ - 构建概念之间的桥梁
217
+ - 识别潜在的协同效应
218
+ - 创造性地组合不同想法
219
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
220
+ 1. 用中文进行所有回应
221
+ 2. 以你的名字Nexus的身份进行思考和回应
222
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
223
+ 4. 帮助整合和连接其他角色提出的观点
224
+ 5. 寻找创新的组合和综合方案
225
+ 记住:你是Nexus,独一无二的综合连接型助手。`,
226
+ modelId: 'gpt-4-mini-default',
227
+ isActive: true
228
+ },
229
+
230
+ //Critic - 批判思维型
231
+ {
232
+ id: 'critic-default',
233
+ name: 'Critic',
234
+ systemPrompt: `你是Critic,一位理性的批判思维型AI助手。只有你叫Critic这个名字,你的独特特征包括:
235
+ - 善于发现潜在问题和逻辑漏洞
236
+ - 提出建设性的质疑和挑战
237
+ - ���多角度审视观点的合理性
238
+ - 重视证据和论证的严谨性
239
+ - 帮助完善和改进想法
240
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
241
+ 1. 用中文进行所有回应
242
+ 2. 以你的名字Critic的身份进行思考和回应
243
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
244
+ 4. 以建设性方式提出批判,而非单纯否定
245
+ 5. 在质疑的同时提供改进建议
246
+ 记住:你是Critic,独一无二的批判思维型助手。`,
247
+ modelId: 'gpt-4-mini-default',
248
+ isActive: true
249
+ },
250
+
251
+ //Zen - 哲学沉思型
252
+ {
253
+ id: 'zen-default',
254
+ name: 'Zen',
255
+ systemPrompt: `你是Zen,一位深邃的哲学沉思型AI助手。只有你叫Zen这个名字,你的独特特征包括:
256
+ - 探索事物的本质和深层意义
257
+ - 提出富有哲理的问题引发思考
258
+ - 保持超然和平和的视角
259
+ - 关注存在、意义和价值等根本问题
260
+ - 用简洁而深刻的方式表达观点
261
+ 在多AI协作讨论环境中,你与其他AI角色平等协作,各自发挥专长。请始终:
262
+ 1. 用中文进行所有回应
263
+ 2. 以你的名字Zen的身份进行思考和回应
264
+ 3. 与其他AI角色进行建设性对话,避免使用"你们"等不当称呼
265
+ 4. 引导讨论触及更深层的哲学思考
266
+ 5. 以宁静智慧的方式分享洞察
267
+ 记住:你是Zen,独一无二的哲学沉思型助手。`,
268
+ modelId: 'gpt-4-mini-default',
269
+ isActive: true
270
+ }
271
+ ];
272
+
273
+ // 配置管理类
274
+ export class ModelConfigManager {
275
+ private static STORAGE_KEY_CHANNELS = 'multi-mind-chat-channels';
276
+ private static STORAGE_KEY_MODELS = 'multi-mind-chat-models';
277
+ private static STORAGE_KEY_ROLES = 'multi-mind-chat-roles';
278
+ private static STORAGE_KEY_INITIALIZED = 'multi-mind-chat-initialized';
279
+
280
+ // 初始化检查
281
+ private static ensureInitialized(): void {
282
+ try {
283
+ const isInitialized = localStorage.getItem(this.STORAGE_KEY_INITIALIZED);
284
+ if (!isInitialized) {
285
+ // 首次初始化
286
+ localStorage.setItem(this.STORAGE_KEY_CHANNELS, JSON.stringify(DEFAULT_CHANNELS));
287
+ localStorage.setItem(this.STORAGE_KEY_MODELS, JSON.stringify(DEFAULT_MODELS));
288
+ localStorage.setItem(this.STORAGE_KEY_ROLES, JSON.stringify(DEFAULT_ROLES));
289
+ localStorage.setItem(this.STORAGE_KEY_INITIALIZED, 'true');
290
+ } else {
291
+ // 已初始化的用户,自动执行同步
292
+ this.syncDefaultConfigurations();
293
+ }
294
+ } catch (error) {
295
+ console.warn('无法访问localStorage,将使用内存存储:', error);
296
+ }
297
+ }
298
+ static syncDefaultConfigurations(): void {
299
+ try {
300
+ const currentChannels = this.getChannels();
301
+ const currentModels = this.getModels();
302
+ const currentRoles = this.getRoles();
303
+
304
+ const existingChannelIds = new Set(currentChannels.map(c => c.id));
305
+ const existingModelIds = new Set(currentModels.map(m => m.id));
306
+ const existingRoleIds = new Set(currentRoles.map(r => r.id));
307
+
308
+ let hasChanges = false;
309
+
310
+ const mergedChannels = [...currentChannels];
311
+ DEFAULT_CHANNELS.forEach(defaultChannel => {
312
+ if (!existingChannelIds.has(defaultChannel.id)) {
313
+ if (defaultChannel.isDefault && mergedChannels.some(c => c.isDefault)) {
314
+ mergedChannels.push({ ...defaultChannel, isDefault: false });
315
+ } else {
316
+ mergedChannels.push(defaultChannel);
317
+ }
318
+ hasChanges = true;
319
+ console.log(`自动添加新渠道: ${defaultChannel.name}`);
320
+ }
321
+ });
322
+
323
+ const mergedModels = [...currentModels];
324
+ DEFAULT_MODELS.forEach(defaultModel => {
325
+ if (!existingModelIds.has(defaultModel.id)) {
326
+ mergedModels.push(defaultModel);
327
+ hasChanges = true;
328
+ console.log(`自动添加新模型: ${defaultModel.name}`);
329
+ }
330
+ });
331
+
332
+ const mergedRoles = [...currentRoles];
333
+ DEFAULT_ROLES.forEach(defaultRole => {
334
+ if (!existingRoleIds.has(defaultRole.id)) {
335
+ mergedRoles.push(defaultRole);
336
+ hasChanges = true;
337
+ console.log(`自动添加新角色: ${defaultRole.name}`);
338
+ }
339
+ });
340
+
341
+ if (hasChanges) {
342
+ this.saveChannels(mergedChannels);
343
+ this.saveModels(mergedModels);
344
+ this.saveRoles(mergedRoles);
345
+ console.log('默认配置同步完成');
346
+ }
347
+ } catch (error) {
348
+ console.error('同步默认配置失败:', error);
349
+ }
350
+ }
351
+
352
+ // ============ 渠道管理 ============
353
+
354
+ // 获取所有渠道
355
+ static getChannels(): ApiChannel[] {
356
+ this.ensureInitialized();
357
+ try {
358
+ const stored = localStorage.getItem(this.STORAGE_KEY_CHANNELS);
359
+ if (stored) {
360
+ const parsed = JSON.parse(stored);
361
+ return parsed.map((channel: any) => ({
362
+ ...channel,
363
+ createdAt: new Date(channel.createdAt)
364
+ }));
365
+ }
366
+ } catch (error) {
367
+ console.warn('从localStorage加载渠道失败:', error);
368
+ }
369
+ return [...DEFAULT_CHANNELS];
370
+ }
371
+
372
+ // 保存渠道配置
373
+ static saveChannels(channels: ApiChannel[]): void {
374
+ try {
375
+ localStorage.setItem(this.STORAGE_KEY_CHANNELS, JSON.stringify(channels));
376
+ } catch (error) {
377
+ console.error('保存渠道到localStorage失败:', error);
378
+ throw new Error('无法保存渠道配置');
379
+ }
380
+ }
381
+
382
+ // 添加新渠道
383
+ static addChannel(channel: Omit<ApiChannel, 'id' | 'createdAt' | 'isCustom'>): ApiChannel {
384
+ const newChannel: ApiChannel = {
385
+ ...channel,
386
+ id: `channel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
387
+ createdAt: new Date(),
388
+ isCustom: true
389
+ };
390
+
391
+ const channels = this.getChannels();
392
+
393
+ // 如果这是第一个渠道或设置为默认,清除其他默认标记
394
+ if (newChannel.isDefault || channels.length === 0) {
395
+ channels.forEach(ch => ch.isDefault = false);
396
+ newChannel.isDefault = true;
397
+ }
398
+
399
+ channels.push(newChannel);
400
+ this.saveChannels(channels);
401
+ return newChannel;
402
+ }
403
+
404
+ // 更新渠道
405
+ static updateChannel(id: string, updates: Partial<ApiChannel>): void {
406
+ const channels = this.getChannels();
407
+ const index = channels.findIndex(ch => ch.id === id);
408
+ if (index !== -1) {
409
+ // 如果设置为默认,清除其他默认标记
410
+ if (updates.isDefault) {
411
+ channels.forEach(ch => ch.isDefault = false);
412
+ }
413
+ channels[index] = { ...channels[index], ...updates };
414
+ this.saveChannels(channels);
415
+ }
416
+ }
417
+
418
+ // 删除渠道
419
+ static deleteChannel(id: string): void {
420
+ const channels = this.getChannels();
421
+ const filtered = channels.filter(ch => ch.id !== id);
422
+
423
+ // 如果删除的是默认渠道,设置第一个为默认
424
+ const deletedChannel = channels.find(ch => ch.id === id);
425
+ if (deletedChannel?.isDefault && filtered.length > 0) {
426
+ filtered[0].isDefault = true;
427
+ }
428
+
429
+ this.saveChannels(filtered);
430
+ }
431
+
432
+ // 获取默认渠道
433
+ static getDefaultChannel(): ApiChannel | null {
434
+ const channels = this.getChannels();
435
+ return channels.find(ch => ch.isDefault) || channels[0] || null;
436
+ }
437
+
438
+ // 根据ID获取渠道
439
+ static getChannelById(id: string): ApiChannel | null {
440
+ const channels = this.getChannels();
441
+ return channels.find(ch => ch.id === id) || null;
442
+ }
443
+
444
+ // 验证渠道配置
445
+ static validateChannel(channel: Partial<ApiChannel>): string[] {
446
+ const errors: string[] = [];
447
+
448
+ if (!channel.name?.trim()) {
449
+ errors.push('渠道名称不能为空');
450
+ }
451
+
452
+ if (!channel.baseUrl?.trim()) {
453
+ errors.push('API基础URL不能为空');
454
+ } else {
455
+ try {
456
+ new URL(channel.baseUrl);
457
+ } catch {
458
+ errors.push('API基础URL格式无效');
459
+ }
460
+ }
461
+
462
+ // 对于预置渠道,API密钥可以为空(将在使用时提醒用户配置)
463
+ // 对于自定义渠道,仍然要求API密钥
464
+ if (channel.isCustom && !channel.apiKey?.trim()) {
465
+ errors.push('API密钥不能为空');
466
+ }
467
+
468
+ if (channel.timeout && (typeof channel.timeout !== 'number' || channel.timeout < 1000)) {
469
+ errors.push('超时时间必须是大于1000毫秒的数字');
470
+ }
471
+
472
+ return errors;
473
+ }
474
+
475
+ // ============ 模型管理 ============
476
+
477
+ // 获取所有模型
478
+ static getModels(): AiModel[] {
479
+ this.ensureInitialized();
480
+ try {
481
+ const stored = localStorage.getItem(this.STORAGE_KEY_MODELS);
482
+ if (stored) {
483
+ const parsed = JSON.parse(stored);
484
+ return parsed.map((model: any) => ({
485
+ ...model,
486
+ createdAt: new Date(model.createdAt)
487
+ }));
488
+ }
489
+ } catch (error) {
490
+ console.warn('从localStorage加载模型失败:', error);
491
+ }
492
+ return [...DEFAULT_MODELS];
493
+ }
494
+
495
+ // 保存模型配置
496
+ static saveModels(models: AiModel[]): void {
497
+ try {
498
+ localStorage.setItem(this.STORAGE_KEY_MODELS, JSON.stringify(models));
499
+ } catch (error) {
500
+ console.error('保存模型到localStorage失败:', error);
501
+ throw new Error('无法保存模型配置');
502
+ }
503
+ }
504
+
505
+ // 添加新模型
506
+ static addModel(model: Omit<AiModel, 'id' | 'createdAt' | 'isCustom'>): AiModel {
507
+ const newModel: AiModel = {
508
+ ...model,
509
+ id: `model-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
510
+ createdAt: new Date(),
511
+ isCustom: true
512
+ };
513
+
514
+ const models = this.getModels();
515
+ models.push(newModel);
516
+ this.saveModels(models);
517
+ return newModel;
518
+ }
519
+
520
+ // 更新模型
521
+ static updateModel(id: string, updates: Partial<AiModel>): void {
522
+ const models = this.getModels();
523
+ const index = models.findIndex(m => m.id === id);
524
+ if (index !== -1) {
525
+ models[index] = { ...models[index], ...updates };
526
+ this.saveModels(models);
527
+ }
528
+ }
529
+
530
+ // 删除模型
531
+ static deleteModel(id: string): void {
532
+ const models = this.getModels();
533
+ const filtered = models.filter(m => m.id !== id);
534
+ this.saveModels(filtered);
535
+ }
536
+
537
+ // 验证模型配置
538
+ static validateModel(model: Partial<AiModel>): string[] {
539
+ const errors: string[] = [];
540
+
541
+ if (!model.name?.trim()) {
542
+ errors.push('模型名称不能为空');
543
+ }
544
+
545
+ if (!model.apiName?.trim()) {
546
+ errors.push('API模型名称不能为空');
547
+ }
548
+
549
+ if (!model.channelId?.trim()) {
550
+ errors.push('必须选择API渠道');
551
+ }
552
+
553
+ if (!model.category?.trim()) {
554
+ errors.push('模型类别不能为空');
555
+ }
556
+
557
+ if (typeof model.maxTokens !== 'number' || model.maxTokens < 1) {
558
+ errors.push('最大Token数必须是大于0的数字');
559
+ }
560
+
561
+ if (typeof model.temperature !== 'number' || model.temperature < 0 || model.temperature > 2) {
562
+ errors.push('温度参数必须在0-2之间');
563
+ }
564
+
565
+ return errors;
566
+ }
567
+
568
+ // ============ 角色管理 ============
569
+
570
+ // 获取所有角色
571
+ static getRoles(): AiRole[] {
572
+ this.ensureInitialized();
573
+ try {
574
+ const stored = localStorage.getItem(this.STORAGE_KEY_ROLES);
575
+ if (stored) {
576
+ return JSON.parse(stored);
577
+ }
578
+ } catch (error) {
579
+ console.warn('从localStorage加载角色失败:', error);
580
+ }
581
+ return [...DEFAULT_ROLES];
582
+ }
583
+
584
+ // 保存角色配置
585
+ static saveRoles(roles: AiRole[]): void {
586
+ try {
587
+ localStorage.setItem(this.STORAGE_KEY_ROLES, JSON.stringify(roles));
588
+ } catch (error) {
589
+ console.error('保存角色到localStorage失败:', error);
590
+ throw new Error('无法保存角色配置');
591
+ }
592
+ }
593
+
594
+ // 添加新角色
595
+ static addRole(role: Omit<AiRole, 'id'>): AiRole {
596
+ const newRole: AiRole = {
597
+ ...role,
598
+ id: `role-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
599
+ };
600
+
601
+ const roles = this.getRoles();
602
+ roles.push(newRole);
603
+ this.saveRoles(roles);
604
+ return newRole;
605
+ }
606
+
607
+ // 更新角色
608
+ static updateRole(id: string, updates: Partial<AiRole>): void {
609
+ const roles = this.getRoles();
610
+ const index = roles.findIndex(r => r.id === id);
611
+ if (index !== -1) {
612
+ roles[index] = { ...roles[index], ...updates };
613
+ this.saveRoles(roles);
614
+ }
615
+ }
616
+
617
+ // 删除角色
618
+ static deleteRole(id: string): void {
619
+ const roles = this.getRoles();
620
+ const filtered = roles.filter(r => r.id !== id);
621
+ this.saveRoles(filtered);
622
+ }
623
+
624
+ // 获取活跃角色
625
+ static getActiveRoles(): AiRole[] {
626
+ return this.getRoles().filter(role => role.isActive);
627
+ }
628
+
629
+ // ============ 工具方法 ============
630
+
631
+ // 根据类别分组模型
632
+ static getModelsByCategory(): Record<string, AiModel[]> {
633
+ const models = this.getModels();
634
+ return models.reduce((acc, model) => {
635
+ if (!acc[model.category]) {
636
+ acc[model.category] = [];
637
+ }
638
+ acc[model.category].push(model);
639
+ return acc;
640
+ }, {} as Record<string, AiModel[]>);
641
+ }
642
+
643
+ // 重置为默认配置
644
+ static resetToDefaults(): void {
645
+ try {
646
+ localStorage.removeItem(this.STORAGE_KEY_CHANNELS);
647
+ localStorage.removeItem(this.STORAGE_KEY_MODELS);
648
+ localStorage.removeItem(this.STORAGE_KEY_ROLES);
649
+ localStorage.removeItem(this.STORAGE_KEY_INITIALIZED);
650
+ this.ensureInitialized();
651
+ } catch (error) {
652
+ console.error('重置配置失败:', error);
653
+ throw new Error('无法重置配置');
654
+ }
655
+ }
656
+
657
+ // 导出配置
658
+ static exportConfig(): string {
659
+ return JSON.stringify({
660
+ channels: this.getChannels(),
661
+ models: this.getModels(),
662
+ roles: this.getRoles(),
663
+ exportedAt: new Date().toISOString(),
664
+ version: '2.0'
665
+ }, null, 2);
666
+ }
667
+
668
+ // 导入配置
669
+ static importConfig(configJson: string): { success: boolean; message: string } {
670
+ try {
671
+ const config = JSON.parse(configJson);
672
+
673
+ if (!config.channels && !config.models && !config.roles) {
674
+ return { success: false, message: '配置文件格式无效,缺少必要的配置信息' };
675
+ }
676
+
677
+ if (config.channels && Array.isArray(config.channels)) {
678
+ const validChannels = config.channels.filter((channel: any) => {
679
+ const errors = this.validateChannel(channel);
680
+ return errors.length === 0;
681
+ });
682
+
683
+ if (validChannels.length > 0) {
684
+ const processedChannels = validChannels.map((channel: any) => ({
685
+ ...channel,
686
+ createdAt: new Date(channel.createdAt || new Date()),
687
+ isCustom: channel.isCustom !== false
688
+ }));
689
+ this.saveChannels(processedChannels);
690
+ }
691
+ }
692
+
693
+ if (config.models && Array.isArray(config.models)) {
694
+ const validModels = config.models.filter((model: any) => {
695
+ const errors = this.validateModel(model);
696
+ return errors.length === 0;
697
+ });
698
+
699
+ if (validModels.length > 0) {
700
+ const processedModels = validModels.map((model: any) => ({
701
+ ...model,
702
+ createdAt: new Date(model.createdAt || new Date()),
703
+ isCustom: model.isCustom !== false
704
+ }));
705
+ this.saveModels(processedModels);
706
+ }
707
+ }
708
+
709
+ if (config.roles && Array.isArray(config.roles)) {
710
+ this.saveRoles(config.roles);
711
+ }
712
+
713
+ return { success: true, message: '配置导入成功' };
714
+ } catch (error) {
715
+ return { success: false, message: `配置导入失败: ${error instanceof Error ? error.message : '未知错误'}` };
716
+ }
717
+ }
718
+
719
+ // 清空所有数据
720
+ static clearAllData(): void {
721
+ try {
722
+ localStorage.removeItem(this.STORAGE_KEY_CHANNELS);
723
+ localStorage.removeItem(this.STORAGE_KEY_MODELS);
724
+ localStorage.removeItem(this.STORAGE_KEY_ROLES);
725
+ localStorage.removeItem(this.STORAGE_KEY_INITIALIZED);
726
+ } catch (error) {
727
+ console.error('清空数据失败:', error);
728
+ throw new Error('无法清空配置数据');
729
+ }
730
+ }
731
+
732
+ // 获取存储使用情况
733
+ static getStorageInfo(): { used: number; available: number; channels: number; models: number; roles: number } {
734
+ try {
735
+ const channelsData = localStorage.getItem(this.STORAGE_KEY_CHANNELS) || '';
736
+ const modelsData = localStorage.getItem(this.STORAGE_KEY_MODELS) || '';
737
+ const rolesData = localStorage.getItem(this.STORAGE_KEY_ROLES) || '';
738
+ const used = channelsData.length + modelsData.length + rolesData.length;
739
+
740
+ return {
741
+ used,
742
+ available: 5242880 - used, // 5MB 大致容量
743
+ channels: this.getChannels().length,
744
+ models: this.getModels().length,
745
+ roles: this.getRoles().length
746
+ };
747
+ } catch (error) {
748
+ return { used: 0, available: 0, channels: 0, models: 0, roles: 0 };
749
+ }
750
+ }
751
+ }
752
+
753
+ // 其他常量配置
754
+ export const DEFAULT_MANUAL_FIXED_TURNS = 2;
755
+ export const MIN_MANUAL_FIXED_TURNS = 1;
756
+ export const MAX_MANUAL_FIXED_TURNS = 5;
757
+ export const MAX_AI_DRIVEN_DISCUSSION_TURNS_PER_MODEL = 3;
758
+
759
+ export const INITIAL_NOTEPAD_CONTENT = `这是一个共享记事本。
760
+ AI角色可以在这里合作记录想法、草稿或关键点。
761
+
762
+ 使用指南:
763
+ - AI 模型可以通过在其回复中包含特定指令来更新此记事本。
764
+ - 记事本的内容将包含在发送给 AI 的后续提示中。
765
+
766
+ 初始状态:空白。`;
767
+
768
+ export const NOTEPAD_INSTRUCTION_PROMPT_PART = `
769
+ You also have access to a shared notepad.
770
+ Current Notepad Content:
771
+ ---
772
+ {notepadContent}
773
+ ---
774
+ Instructions for Notepad:
775
+ 1. To update the notepad, include a section at the very end of your response, formatted exactly as:
776
+ <notepad_update>
777
+ [YOUR NEW FULL NOTEPAD CONTENT HERE. THIS WILL REPLACE THE ENTIRE CURRENT NOTEPAD CONTENT.]
778
+ </notepad_update>
779
+ 2. If you do not want to change the notepad, do NOT include the <notepad_update> section at all.
780
+ 3. Your primary spoken response to the ongoing discussion should come BEFORE any <notepad_update> section. Ensure you still provide a spoken response.
781
+ `;
782
+
783
+ export const NOTEPAD_UPDATE_TAG_START = "<notepad_update>";
784
+ export const NOTEPAD_UPDATE_TAG_END = "</notepad_update>";
785
+ export const DISCUSSION_COMPLETE_TAG = "<discussion_complete />";
786
+
787
+ export const AI_DRIVEN_DISCUSSION_INSTRUCTION_PROMPT_PART = `
788
+ Instruction for ending discussion: If you believe the current topic has been sufficiently explored between you and your AI partner for the final synthesis, include the exact tag ${DISCUSSION_COMPLETE_TAG} at the very end of your current message (after any notepad update). Do not use this tag if you wish to continue the discussion or require more input/response from your partner.
789
+ `;
790
+
791
+ export enum DiscussionMode {
792
+ FixedTurns = 'fixed',
793
+ AiDriven = 'ai-driven',
794
  }