codexmate 0.0.10 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -12
- package/README.zh-CN.md +52 -12
- package/cli.js +3491 -563
- package/{CHANGELOG.md → doc/CHANGELOG.md} +6 -0
- package/{CHANGELOG.zh-CN.md → doc/CHANGELOG.zh-CN.md} +6 -0
- package/lib/mcp-stdio.js +440 -0
- package/package.json +22 -2
- package/res/logo.png +0 -0
- package/web-ui/app.js +1171 -149
- package/web-ui/index.html +1605 -0
- package/web-ui/logic.mjs +21 -21
- package/web-ui/styles.css +3213 -0
- package/web-ui.html +7 -3967
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- package/.github/workflows/ci.yml +0 -26
- package/.github/workflows/release.yml +0 -159
- package/.planning/.fix-attempts +0 -1
- package/.planning/.lock +0 -6
- package/.planning/.verify-cache.json +0 -14
- package/.planning/CHECKPOINT.json +0 -46
- package/.planning/DESIGN.md +0 -26
- package/.planning/HISTORY.json +0 -124
- package/.planning/PLAN.md +0 -69
- package/.planning/REVIEW.md +0 -41
- package/.planning/STATE.md +0 -12
- package/.planning/STATS.json +0 -13
- package/.planning/VERIFICATION.md +0 -70
- package/.planning/daude-code-plan.md +0 -51
- package/.planning/research/architecture.md +0 -32
- package/.planning/research/conventions.md +0 -36
- package/.planning/task_1-REVIEW.md +0 -29
- package/.planning/task_1-SUMMARY.md +0 -32
- package/.planning/task_2-REVIEW.md +0 -24
- package/.planning/task_2-SUMMARY.md +0 -37
- package/.planning/task_3-REVIEW.md +0 -25
- package/.planning/task_3-SUMMARY.md +0 -31
- package/cmd/publish-npm.cmd +0 -65
- package/tests/e2e/helpers.js +0 -214
- package/tests/e2e/recent-health.e2e.js +0 -142
- package/tests/e2e/run.js +0 -154
- package/tests/e2e/test-claude.js +0 -21
- package/tests/e2e/test-config.js +0 -124
- package/tests/e2e/test-health-speed.js +0 -79
- package/tests/e2e/test-openclaw.js +0 -47
- package/tests/e2e/test-session-search.js +0 -114
- package/tests/e2e/test-sessions.js +0 -69
- package/tests/e2e/test-setup.js +0 -159
- package/tests/unit/run.mjs +0 -29
- package/tests/unit/web-ui-logic.test.mjs +0 -186
package/web-ui/app.js
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
currentProvider: '',
|
|
59
59
|
currentModel: '',
|
|
60
60
|
serviceTier: 'fast',
|
|
61
|
+
modelReasoningEffort: 'high',
|
|
61
62
|
providersList: [],
|
|
62
63
|
models: [],
|
|
63
64
|
codexModelsLoading: false,
|
|
@@ -80,8 +81,10 @@
|
|
|
80
81
|
showOpenclawConfigModal: false,
|
|
81
82
|
showConfigTemplateModal: false,
|
|
82
83
|
showAgentsModal: false,
|
|
84
|
+
showInstallModal: false,
|
|
83
85
|
configTemplateContent: '',
|
|
84
86
|
configTemplateApplying: false,
|
|
87
|
+
codexApplying: false,
|
|
85
88
|
agentsContent: '',
|
|
86
89
|
agentsPath: '',
|
|
87
90
|
agentsExists: false,
|
|
@@ -91,9 +94,9 @@
|
|
|
91
94
|
agentsContext: 'codex',
|
|
92
95
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
93
96
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
94
|
-
sessionsList: [],
|
|
95
|
-
sessionsLoading: false,
|
|
96
|
-
sessionFilterSource: '
|
|
97
|
+
sessionsList: [],
|
|
98
|
+
sessionsLoading: false,
|
|
99
|
+
sessionFilterSource: 'all',
|
|
97
100
|
sessionPathFilter: '',
|
|
98
101
|
sessionQuery: '',
|
|
99
102
|
sessionRoleFilter: 'all',
|
|
@@ -134,8 +137,35 @@
|
|
|
134
137
|
claudeSpeedLoading: {},
|
|
135
138
|
claudeShareLoading: {},
|
|
136
139
|
providerShareLoading: {},
|
|
140
|
+
installPackageManager: 'npm',
|
|
141
|
+
installCommandAction: 'install',
|
|
142
|
+
installRegistryPreset: 'default',
|
|
143
|
+
installRegistryCustom: '',
|
|
144
|
+
installStatusTargets: [
|
|
145
|
+
{
|
|
146
|
+
id: 'claude',
|
|
147
|
+
name: 'Claude Code CLI',
|
|
148
|
+
packageName: '@anthropic-ai/claude-code',
|
|
149
|
+
installed: false,
|
|
150
|
+
bin: 'claude',
|
|
151
|
+
version: '',
|
|
152
|
+
commandPath: '',
|
|
153
|
+
error: ''
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'codex',
|
|
157
|
+
name: 'Codex CLI',
|
|
158
|
+
packageName: '@openai/codex',
|
|
159
|
+
installed: false,
|
|
160
|
+
bin: 'codex',
|
|
161
|
+
version: '',
|
|
162
|
+
commandPath: '',
|
|
163
|
+
error: ''
|
|
164
|
+
}
|
|
165
|
+
],
|
|
137
166
|
newProvider: { name: '', url: '', key: '' },
|
|
138
|
-
|
|
167
|
+
resetConfigLoading: false,
|
|
168
|
+
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
139
169
|
newModelName: '',
|
|
140
170
|
currentClaudeConfig: '',
|
|
141
171
|
currentClaudeModel: '',
|
|
@@ -201,7 +231,34 @@
|
|
|
201
231
|
openclawMissingProviders: [],
|
|
202
232
|
healthCheckLoading: false,
|
|
203
233
|
healthCheckResult: null,
|
|
204
|
-
healthCheckRemote: false
|
|
234
|
+
healthCheckRemote: false,
|
|
235
|
+
claudeDownloadLoading: false,
|
|
236
|
+
claudeDownloadProgress: 0,
|
|
237
|
+
claudeDownloadTimer: null,
|
|
238
|
+
codexDownloadLoading: false,
|
|
239
|
+
codexDownloadProgress: 0,
|
|
240
|
+
codexDownloadTimer: null,
|
|
241
|
+
claudeImportLoading: false,
|
|
242
|
+
codexImportLoading: false,
|
|
243
|
+
codexAuthProfiles: [],
|
|
244
|
+
codexAuthImportLoading: false,
|
|
245
|
+
codexAuthSwitching: {},
|
|
246
|
+
codexAuthDeleting: {},
|
|
247
|
+
proxySettings: {
|
|
248
|
+
enabled: false,
|
|
249
|
+
host: '127.0.0.1',
|
|
250
|
+
port: 8318,
|
|
251
|
+
provider: '',
|
|
252
|
+
authSource: 'provider',
|
|
253
|
+
timeoutMs: 30000
|
|
254
|
+
},
|
|
255
|
+
proxyRuntime: null,
|
|
256
|
+
proxyLoading: false,
|
|
257
|
+
proxySaving: false,
|
|
258
|
+
proxyStarting: false,
|
|
259
|
+
proxyStopping: false,
|
|
260
|
+
proxyApplying: false,
|
|
261
|
+
showProxyAdvanced: false
|
|
205
262
|
}
|
|
206
263
|
},
|
|
207
264
|
mounted() {
|
|
@@ -248,19 +305,19 @@
|
|
|
248
305
|
this.loadAll();
|
|
249
306
|
},
|
|
250
307
|
|
|
251
|
-
computed: {
|
|
252
|
-
isSessionQueryEnabled() {
|
|
253
|
-
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
254
|
-
},
|
|
255
|
-
sessionQueryPlaceholder() {
|
|
256
|
-
if (this.isSessionQueryEnabled) {
|
|
257
|
-
return '关键词检索(支持 Codex/Claude,例:claude code)';
|
|
258
|
-
}
|
|
259
|
-
return '当前来源暂不支持关键词检索';
|
|
260
|
-
},
|
|
261
|
-
claudeModelHasList() {
|
|
262
|
-
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
263
|
-
},
|
|
308
|
+
computed: {
|
|
309
|
+
isSessionQueryEnabled() {
|
|
310
|
+
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
311
|
+
},
|
|
312
|
+
sessionQueryPlaceholder() {
|
|
313
|
+
if (this.isSessionQueryEnabled) {
|
|
314
|
+
return '关键词检索(支持 Codex/Claude,例:claude code)';
|
|
315
|
+
}
|
|
316
|
+
return '当前来源暂不支持关键词检索';
|
|
317
|
+
},
|
|
318
|
+
claudeModelHasList() {
|
|
319
|
+
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
320
|
+
},
|
|
264
321
|
claudeModelOptions() {
|
|
265
322
|
const list = Array.isArray(this.claudeModels) ? [...this.claudeModels] : [];
|
|
266
323
|
const current = (this.currentClaudeModel || '').trim();
|
|
@@ -268,6 +325,162 @@
|
|
|
268
325
|
list.unshift(current);
|
|
269
326
|
}
|
|
270
327
|
return list;
|
|
328
|
+
},
|
|
329
|
+
proxyProviderOptions() {
|
|
330
|
+
const source = Array.isArray(this.providersList) ? this.providersList : [];
|
|
331
|
+
const list = source
|
|
332
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
333
|
+
.filter((name) => name && name !== 'codexmate-proxy');
|
|
334
|
+
return Array.from(new Set(list));
|
|
335
|
+
},
|
|
336
|
+
proxyRuntimeDisplayProvider() {
|
|
337
|
+
if (!this.proxyRuntime) return '';
|
|
338
|
+
const value = typeof this.proxyRuntime.provider === 'string'
|
|
339
|
+
? this.proxyRuntime.provider.trim()
|
|
340
|
+
: '';
|
|
341
|
+
return value || 'local';
|
|
342
|
+
},
|
|
343
|
+
installTargetCards() {
|
|
344
|
+
const targets = Array.isArray(this.installStatusTargets) ? this.installStatusTargets : [];
|
|
345
|
+
const action = this.normalizeInstallAction(this.installCommandAction);
|
|
346
|
+
return targets.map((target) => {
|
|
347
|
+
const id = target && typeof target.id === 'string' ? target.id : '';
|
|
348
|
+
return {
|
|
349
|
+
...target,
|
|
350
|
+
command: this.getInstallCommand(id, action)
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
installRegistryPreview() {
|
|
355
|
+
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
356
|
+
},
|
|
357
|
+
inspectorMainTabLabel() {
|
|
358
|
+
if (this.mainTab === 'config') return '配置中心';
|
|
359
|
+
if (this.mainTab === 'sessions') return '会话浏览';
|
|
360
|
+
if (this.mainTab === 'settings') return '设置';
|
|
361
|
+
return '未知';
|
|
362
|
+
},
|
|
363
|
+
inspectorConfigModeLabel() {
|
|
364
|
+
if (this.mainTab !== 'config') return '--';
|
|
365
|
+
if (this.configMode === 'codex') return 'Codex';
|
|
366
|
+
if (this.configMode === 'claude') return 'Claude Code';
|
|
367
|
+
if (this.configMode === 'openclaw') return 'OpenClaw';
|
|
368
|
+
return '未选择';
|
|
369
|
+
},
|
|
370
|
+
inspectorCurrentConfigLabel() {
|
|
371
|
+
if (this.mainTab !== 'config') return '--';
|
|
372
|
+
if (this.configMode === 'codex') {
|
|
373
|
+
const provider = typeof this.currentProvider === 'string' ? this.currentProvider.trim() : '';
|
|
374
|
+
return provider || '未选择';
|
|
375
|
+
}
|
|
376
|
+
if (this.configMode === 'claude') {
|
|
377
|
+
const config = typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig.trim() : '';
|
|
378
|
+
return config || '未选择';
|
|
379
|
+
}
|
|
380
|
+
const openclaw = typeof this.currentOpenclawConfig === 'string' ? this.currentOpenclawConfig.trim() : '';
|
|
381
|
+
return openclaw || '未选择';
|
|
382
|
+
},
|
|
383
|
+
inspectorCurrentModelLabel() {
|
|
384
|
+
if (this.mainTab !== 'config') return '--';
|
|
385
|
+
if (this.configMode === 'codex') {
|
|
386
|
+
const model = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
|
|
387
|
+
return model || '未选择';
|
|
388
|
+
}
|
|
389
|
+
if (this.configMode === 'claude') {
|
|
390
|
+
const model = typeof this.currentClaudeModel === 'string' ? this.currentClaudeModel.trim() : '';
|
|
391
|
+
return model || '未选择';
|
|
392
|
+
}
|
|
393
|
+
const model = this.openclawStructured && typeof this.openclawStructured.agentPrimary === 'string'
|
|
394
|
+
? this.openclawStructured.agentPrimary.trim()
|
|
395
|
+
: '';
|
|
396
|
+
return model || '按配置文件';
|
|
397
|
+
},
|
|
398
|
+
inspectorTemplateStatus() {
|
|
399
|
+
if (this.mainTab !== 'config') return '--';
|
|
400
|
+
if (this.configMode === 'codex') {
|
|
401
|
+
if (this.configTemplateApplying || this.codexApplying) {
|
|
402
|
+
return '模板应用中';
|
|
403
|
+
}
|
|
404
|
+
return '模板可编辑(手动确认应用)';
|
|
405
|
+
}
|
|
406
|
+
if (this.configMode === 'claude') {
|
|
407
|
+
return '即时写入 Claude settings';
|
|
408
|
+
}
|
|
409
|
+
if (this.openclawApplying || this.openclawSaving) {
|
|
410
|
+
return 'OpenClaw 保存/应用中';
|
|
411
|
+
}
|
|
412
|
+
return 'JSON5 可保存并应用';
|
|
413
|
+
},
|
|
414
|
+
inspectorBusyStatus() {
|
|
415
|
+
const tasks = [];
|
|
416
|
+
if (this.loading) tasks.push('初始化');
|
|
417
|
+
if (this.sessionsLoading) tasks.push('会话加载');
|
|
418
|
+
if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
|
|
419
|
+
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
420
|
+
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
421
|
+
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
|
|
422
|
+
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
423
|
+
},
|
|
424
|
+
inspectorMessageSummary() {
|
|
425
|
+
const value = typeof this.message === 'string' ? this.message.trim() : '';
|
|
426
|
+
return value || '暂无提示';
|
|
427
|
+
},
|
|
428
|
+
inspectorSessionSourceLabel() {
|
|
429
|
+
if (this.sessionFilterSource === 'codex') return 'Codex';
|
|
430
|
+
if (this.sessionFilterSource === 'claude') return 'Claude Code';
|
|
431
|
+
return '全部';
|
|
432
|
+
},
|
|
433
|
+
inspectorSessionPathLabel() {
|
|
434
|
+
const value = typeof this.sessionPathFilter === 'string' ? this.sessionPathFilter.trim() : '';
|
|
435
|
+
return value || '全部路径';
|
|
436
|
+
},
|
|
437
|
+
inspectorSessionQueryLabel() {
|
|
438
|
+
if (!this.isSessionQueryEnabled) return '当前来源不支持';
|
|
439
|
+
const value = typeof this.sessionQuery === 'string' ? this.sessionQuery.trim() : '';
|
|
440
|
+
return value || '未设置';
|
|
441
|
+
},
|
|
442
|
+
inspectorHealthStatus() {
|
|
443
|
+
if (this.initError) return '读取失败';
|
|
444
|
+
if (this.loading) return '初始化中';
|
|
445
|
+
return '正常';
|
|
446
|
+
},
|
|
447
|
+
inspectorHealthTone() {
|
|
448
|
+
if (this.initError) return 'error';
|
|
449
|
+
if (this.loading) return 'warn';
|
|
450
|
+
return 'ok';
|
|
451
|
+
},
|
|
452
|
+
inspectorModelLoadStatus() {
|
|
453
|
+
if (this.codexModelsLoading || this.claudeModelsLoading) {
|
|
454
|
+
return '加载中';
|
|
455
|
+
}
|
|
456
|
+
if (this.modelsSource === 'error' || this.claudeModelsSource === 'error') {
|
|
457
|
+
return '加载异常';
|
|
458
|
+
}
|
|
459
|
+
return '正常';
|
|
460
|
+
},
|
|
461
|
+
inspectorProxyStatus() {
|
|
462
|
+
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) {
|
|
463
|
+
return '状态更新中';
|
|
464
|
+
}
|
|
465
|
+
if (this.proxyRuntime && this.proxyRuntime.running === true) {
|
|
466
|
+
return `运行中(${this.proxyRuntimeDisplayProvider})`;
|
|
467
|
+
}
|
|
468
|
+
return '未运行';
|
|
469
|
+
},
|
|
470
|
+
installTroubleshootingTips() {
|
|
471
|
+
const platform = this.resolveInstallPlatform();
|
|
472
|
+
if (platform === 'win32') {
|
|
473
|
+
return [
|
|
474
|
+
'PowerShell 报权限不足(EACCES/EPERM)时,请以管理员身份执行安装命令。',
|
|
475
|
+
'安装后若仍提示找不到命令,重开终端并执行:where codex / where claude。',
|
|
476
|
+
'公司网络受限时,可先切换镜像源快捷项(npmmirror / 腾讯云 / 自定义)。'
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
return [
|
|
480
|
+
'出现 EACCES 权限错误时,优先修复 Node 全局目录权限,不建议直接 sudo npm。',
|
|
481
|
+
'安装后若命令未生效,重开终端并执行:which codex / which claude。',
|
|
482
|
+
'公司网络受限时,可先切换镜像源快捷项(npmmirror / 腾讯云 / 自定义)。'
|
|
483
|
+
];
|
|
271
484
|
}
|
|
272
485
|
},
|
|
273
486
|
methods: {
|
|
@@ -275,8 +488,7 @@
|
|
|
275
488
|
this.loading = true;
|
|
276
489
|
this.initError = '';
|
|
277
490
|
try {
|
|
278
|
-
const statusRes = await api('status');
|
|
279
|
-
const listRes = await api('list');
|
|
491
|
+
const [statusRes, listRes] = await Promise.all([api('status'), api('list')]);
|
|
280
492
|
|
|
281
493
|
if (statusRes.error) {
|
|
282
494
|
this.initError = statusRes.error;
|
|
@@ -289,13 +501,18 @@
|
|
|
289
501
|
: '';
|
|
290
502
|
this.serviceTier = tier === 'fast' ? 'fast' : (tier ? 'standard' : 'fast');
|
|
291
503
|
}
|
|
504
|
+
{
|
|
505
|
+
const effort = typeof statusRes.modelReasoningEffort === 'string'
|
|
506
|
+
? statusRes.modelReasoningEffort.trim().toLowerCase()
|
|
507
|
+
: '';
|
|
508
|
+
this.modelReasoningEffort = effort || 'high';
|
|
509
|
+
}
|
|
292
510
|
this.providersList = listRes.providers;
|
|
293
|
-
await this.loadModelsForProvider(this.currentProvider);
|
|
294
511
|
if (statusRes.configReady === false) {
|
|
295
|
-
this.showMessage(
|
|
512
|
+
this.showMessage('配置已加载', 'info');
|
|
296
513
|
}
|
|
297
514
|
if (statusRes.initNotice) {
|
|
298
|
-
this.showMessage(
|
|
515
|
+
this.showMessage('配置就绪', 'info');
|
|
299
516
|
}
|
|
300
517
|
this.maybeShowStarPrompt();
|
|
301
518
|
}
|
|
@@ -304,6 +521,22 @@
|
|
|
304
521
|
} finally {
|
|
305
522
|
this.loading = false;
|
|
306
523
|
}
|
|
524
|
+
|
|
525
|
+
// 模型加载单独异步,不阻塞主 loading
|
|
526
|
+
try {
|
|
527
|
+
await this.loadModelsForProvider(this.currentProvider);
|
|
528
|
+
} catch (e) {
|
|
529
|
+
// loadModelsForProvider 内部已有 toast,这里吞掉防止抛出
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
await Promise.all([
|
|
534
|
+
this.loadCodexAuthProfiles(),
|
|
535
|
+
this.loadProxyStatus()
|
|
536
|
+
]);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
// 认证/代理状态加载失败不阻塞主界面
|
|
539
|
+
}
|
|
307
540
|
},
|
|
308
541
|
|
|
309
542
|
async loadModelsForProvider(providerName) {
|
|
@@ -324,7 +557,7 @@
|
|
|
324
557
|
return;
|
|
325
558
|
}
|
|
326
559
|
if (res.error) {
|
|
327
|
-
this.showMessage('
|
|
560
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
328
561
|
this.models = [];
|
|
329
562
|
this.modelsSource = 'error';
|
|
330
563
|
this.modelsHasCurrent = true;
|
|
@@ -335,7 +568,7 @@
|
|
|
335
568
|
this.modelsSource = res.source || 'remote';
|
|
336
569
|
this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
|
|
337
570
|
} catch (e) {
|
|
338
|
-
this.showMessage('
|
|
571
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
339
572
|
this.models = [];
|
|
340
573
|
this.modelsSource = 'error';
|
|
341
574
|
this.modelsHasCurrent = true;
|
|
@@ -380,7 +613,7 @@
|
|
|
380
613
|
const res = await api('get-claude-settings');
|
|
381
614
|
if (res && res.error) {
|
|
382
615
|
if (!silent) {
|
|
383
|
-
this.showMessage('
|
|
616
|
+
this.showMessage('读取配置失败', 'error');
|
|
384
617
|
}
|
|
385
618
|
return;
|
|
386
619
|
}
|
|
@@ -403,7 +636,7 @@
|
|
|
403
636
|
}
|
|
404
637
|
} catch (e) {
|
|
405
638
|
if (!silent) {
|
|
406
|
-
this.showMessage('
|
|
639
|
+
this.showMessage('读取配置失败', 'error');
|
|
407
640
|
}
|
|
408
641
|
}
|
|
409
642
|
},
|
|
@@ -454,7 +687,7 @@
|
|
|
454
687
|
return;
|
|
455
688
|
}
|
|
456
689
|
if (res.error) {
|
|
457
|
-
this.showMessage('
|
|
690
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
458
691
|
this.claudeModels = [];
|
|
459
692
|
this.claudeModelsSource = 'error';
|
|
460
693
|
this.claudeModelsHasCurrent = true;
|
|
@@ -465,7 +698,7 @@
|
|
|
465
698
|
this.claudeModelsSource = res.source || 'remote';
|
|
466
699
|
this.updateClaudeModelsCurrent();
|
|
467
700
|
} catch (e) {
|
|
468
|
-
this.showMessage('
|
|
701
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
469
702
|
this.claudeModels = [];
|
|
470
703
|
this.claudeModelsSource = 'error';
|
|
471
704
|
this.claudeModelsHasCurrent = true;
|
|
@@ -483,7 +716,7 @@
|
|
|
483
716
|
if (localStorage.getItem(storageKey)) {
|
|
484
717
|
return;
|
|
485
718
|
}
|
|
486
|
-
this.showMessage('
|
|
719
|
+
this.showMessage('欢迎到 GitHub 点 Star', 'info');
|
|
487
720
|
localStorage.setItem(storageKey, '1');
|
|
488
721
|
},
|
|
489
722
|
|
|
@@ -593,7 +826,7 @@
|
|
|
593
826
|
openSessionStandalone(session) {
|
|
594
827
|
const url = this.buildSessionStandaloneUrl(session);
|
|
595
828
|
if (!url) {
|
|
596
|
-
this.showMessage('
|
|
829
|
+
this.showMessage('无法生成链接', 'error');
|
|
597
830
|
return;
|
|
598
831
|
}
|
|
599
832
|
window.open(url, '_blank', 'noopener');
|
|
@@ -674,38 +907,79 @@
|
|
|
674
907
|
copyAgentsContent() {
|
|
675
908
|
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
676
909
|
if (!text) {
|
|
677
|
-
this.showMessage('
|
|
910
|
+
this.showMessage('没有可复制内容', 'info');
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const ok = this.fallbackCopyText(text);
|
|
914
|
+
if (ok) {
|
|
915
|
+
this.showMessage('已复制', 'success');
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
this.showMessage('复制失败', 'error');
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
exportAgentsContent() {
|
|
922
|
+
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
923
|
+
if (!text) {
|
|
924
|
+
this.showMessage('没有可导出内容', 'info');
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const now = new Date();
|
|
928
|
+
const year = String(now.getFullYear());
|
|
929
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
930
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
931
|
+
const hour = String(now.getHours()).padStart(2, '0');
|
|
932
|
+
const minute = String(now.getMinutes()).padStart(2, '0');
|
|
933
|
+
const second = String(now.getSeconds()).padStart(2, '0');
|
|
934
|
+
const fileName = `agent-${year}${month}${day}-${hour}${minute}${second}.txt`;
|
|
935
|
+
this.downloadTextFile(fileName, text, 'text/plain;charset=utf-8');
|
|
936
|
+
this.showMessage(`已导出 ${fileName}`, 'success');
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
async copyInstallCommand(cmd) {
|
|
940
|
+
const text = typeof cmd === 'string' ? cmd.trim() : '';
|
|
941
|
+
if (!text) {
|
|
942
|
+
this.showMessage('没有可复制内容', 'info');
|
|
678
943
|
return;
|
|
679
944
|
}
|
|
945
|
+
try {
|
|
946
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
947
|
+
await navigator.clipboard.writeText(text);
|
|
948
|
+
this.showMessage('已复制命令', 'success');
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
} catch (e) {
|
|
952
|
+
// fallback to legacy copy path
|
|
953
|
+
}
|
|
680
954
|
const ok = this.fallbackCopyText(text);
|
|
681
955
|
if (ok) {
|
|
682
|
-
this.showMessage('
|
|
956
|
+
this.showMessage('已复制命令', 'success');
|
|
683
957
|
return;
|
|
684
958
|
}
|
|
685
|
-
this.showMessage('
|
|
959
|
+
this.showMessage('复制失败', 'error');
|
|
686
960
|
},
|
|
687
961
|
|
|
688
962
|
async copyResumeCommand(session) {
|
|
689
963
|
if (!this.isResumeCommandAvailable(session)) {
|
|
690
|
-
this.showMessage('
|
|
964
|
+
this.showMessage('不支持此操作', 'error');
|
|
691
965
|
return;
|
|
692
966
|
}
|
|
693
967
|
const command = this.buildResumeCommand(session);
|
|
694
968
|
const ok = this.fallbackCopyText(command);
|
|
695
969
|
if (ok) {
|
|
696
|
-
this.showMessage('
|
|
970
|
+
this.showMessage('已复制', 'success');
|
|
697
971
|
return;
|
|
698
972
|
}
|
|
699
973
|
try {
|
|
700
974
|
if (navigator.clipboard && window.isSecureContext) {
|
|
701
975
|
await navigator.clipboard.writeText(command);
|
|
702
|
-
this.showMessage('
|
|
976
|
+
this.showMessage('已复制', 'success');
|
|
703
977
|
return;
|
|
704
978
|
}
|
|
705
979
|
} catch (e) {
|
|
706
980
|
// keep fallback failure message
|
|
707
981
|
}
|
|
708
|
-
this.showMessage('
|
|
982
|
+
this.showMessage('复制失败', 'error');
|
|
709
983
|
},
|
|
710
984
|
|
|
711
985
|
buildProviderShareCommand(payload) {
|
|
@@ -742,7 +1016,11 @@
|
|
|
742
1016
|
async copyProviderShareCommand(provider) {
|
|
743
1017
|
const name = provider && typeof provider.name === 'string' ? provider.name.trim() : '';
|
|
744
1018
|
if (!name) {
|
|
745
|
-
this.showMessage('
|
|
1019
|
+
this.showMessage('参数无效', 'error');
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (!this.shouldAllowProviderShare(provider)) {
|
|
1023
|
+
this.showMessage('本地入口不可分享', 'info');
|
|
746
1024
|
return;
|
|
747
1025
|
}
|
|
748
1026
|
if (this.providerShareLoading[name]) {
|
|
@@ -757,26 +1035,26 @@
|
|
|
757
1035
|
}
|
|
758
1036
|
const command = this.buildProviderShareCommand(res && res.payload ? res.payload : null);
|
|
759
1037
|
if (!command) {
|
|
760
|
-
this.showMessage('
|
|
1038
|
+
this.showMessage('生成命令失败', 'error');
|
|
761
1039
|
return;
|
|
762
1040
|
}
|
|
763
1041
|
const ok = this.fallbackCopyText(command);
|
|
764
1042
|
if (ok) {
|
|
765
|
-
this.showMessage('
|
|
1043
|
+
this.showMessage('已复制', 'success');
|
|
766
1044
|
return;
|
|
767
1045
|
}
|
|
768
1046
|
try {
|
|
769
1047
|
if (navigator.clipboard && window.isSecureContext) {
|
|
770
1048
|
await navigator.clipboard.writeText(command);
|
|
771
|
-
this.showMessage('
|
|
1049
|
+
this.showMessage('已复制', 'success');
|
|
772
1050
|
return;
|
|
773
1051
|
}
|
|
774
1052
|
} catch (e) {
|
|
775
1053
|
// keep fallback failure message
|
|
776
1054
|
}
|
|
777
|
-
this.showMessage('
|
|
1055
|
+
this.showMessage('复制失败', 'error');
|
|
778
1056
|
} catch (e) {
|
|
779
|
-
this.showMessage('
|
|
1057
|
+
this.showMessage('生成命令失败', 'error');
|
|
780
1058
|
} finally {
|
|
781
1059
|
this.providerShareLoading[name] = false;
|
|
782
1060
|
}
|
|
@@ -798,26 +1076,26 @@
|
|
|
798
1076
|
}
|
|
799
1077
|
const command = this.buildClaudeShareCommand(res && res.payload ? res.payload : null);
|
|
800
1078
|
if (!command) {
|
|
801
|
-
this.showMessage('
|
|
1079
|
+
this.showMessage('生成命令失败', 'error');
|
|
802
1080
|
return;
|
|
803
1081
|
}
|
|
804
1082
|
const ok = this.fallbackCopyText(command);
|
|
805
1083
|
if (ok) {
|
|
806
|
-
this.showMessage('
|
|
1084
|
+
this.showMessage('已复制', 'success');
|
|
807
1085
|
return;
|
|
808
1086
|
}
|
|
809
1087
|
try {
|
|
810
1088
|
if (navigator.clipboard && window.isSecureContext) {
|
|
811
1089
|
await navigator.clipboard.writeText(command);
|
|
812
|
-
this.showMessage('
|
|
1090
|
+
this.showMessage('已复制', 'success');
|
|
813
1091
|
return;
|
|
814
1092
|
}
|
|
815
1093
|
} catch (e) {
|
|
816
1094
|
// fall through
|
|
817
1095
|
}
|
|
818
|
-
this.showMessage('
|
|
1096
|
+
this.showMessage('复制失败', 'error');
|
|
819
1097
|
} catch (e) {
|
|
820
|
-
this.showMessage('
|
|
1098
|
+
this.showMessage('生成命令失败', 'error');
|
|
821
1099
|
} finally {
|
|
822
1100
|
this.claudeShareLoading[name] = false;
|
|
823
1101
|
}
|
|
@@ -825,7 +1103,7 @@
|
|
|
825
1103
|
|
|
826
1104
|
async cloneSession(session) {
|
|
827
1105
|
if (!this.isCloneAvailable(session)) {
|
|
828
|
-
this.showMessage('
|
|
1106
|
+
this.showMessage('不支持此操作', 'error');
|
|
829
1107
|
return;
|
|
830
1108
|
}
|
|
831
1109
|
const key = this.getSessionExportKey(session);
|
|
@@ -844,7 +1122,7 @@
|
|
|
844
1122
|
return;
|
|
845
1123
|
}
|
|
846
1124
|
|
|
847
|
-
this.showMessage('
|
|
1125
|
+
this.showMessage('操作成功', 'success');
|
|
848
1126
|
await this.loadSessions();
|
|
849
1127
|
if (res.sessionId) {
|
|
850
1128
|
const matched = this.sessionsList.find(item => item.source === 'codex' && item.sessionId === res.sessionId);
|
|
@@ -853,7 +1131,7 @@
|
|
|
853
1131
|
}
|
|
854
1132
|
}
|
|
855
1133
|
} catch (e) {
|
|
856
|
-
this.showMessage('
|
|
1134
|
+
this.showMessage('克隆失败', 'error');
|
|
857
1135
|
} finally {
|
|
858
1136
|
this.sessionCloning[key] = false;
|
|
859
1137
|
}
|
|
@@ -861,7 +1139,7 @@
|
|
|
861
1139
|
|
|
862
1140
|
async deleteSession(session) {
|
|
863
1141
|
if (!this.isDeleteAvailable(session)) {
|
|
864
|
-
this.showMessage('
|
|
1142
|
+
this.showMessage('不支持此操作', 'error');
|
|
865
1143
|
return;
|
|
866
1144
|
}
|
|
867
1145
|
const key = this.getSessionExportKey(session);
|
|
@@ -879,10 +1157,10 @@
|
|
|
879
1157
|
this.showMessage(res.error, 'error');
|
|
880
1158
|
return;
|
|
881
1159
|
}
|
|
882
|
-
this.showMessage('
|
|
1160
|
+
this.showMessage('操作成功', 'success');
|
|
883
1161
|
await this.loadSessions();
|
|
884
1162
|
} catch (e) {
|
|
885
|
-
this.showMessage('
|
|
1163
|
+
this.showMessage('删除失败', 'error');
|
|
886
1164
|
} finally {
|
|
887
1165
|
this.sessionDeleting[key] = false;
|
|
888
1166
|
}
|
|
@@ -893,7 +1171,7 @@
|
|
|
893
1171
|
return value.trim();
|
|
894
1172
|
},
|
|
895
1173
|
|
|
896
|
-
mergeSessionPathOptions(baseList = [], incomingList = []) {
|
|
1174
|
+
mergeSessionPathOptions(baseList = [], incomingList = []) {
|
|
897
1175
|
const merged = [];
|
|
898
1176
|
const seen = new Set();
|
|
899
1177
|
const append = (items) => {
|
|
@@ -931,8 +1209,8 @@
|
|
|
931
1209
|
return paths;
|
|
932
1210
|
},
|
|
933
1211
|
|
|
934
|
-
syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
|
|
935
|
-
const targetSource = source === 'claude' ? 'claude' : 'codex';
|
|
1212
|
+
syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
|
|
1213
|
+
const targetSource = source === 'claude' ? 'claude' : (source === 'all' ? 'all' : 'codex');
|
|
936
1214
|
const current = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
937
1215
|
? this.sessionPathOptionsMap[targetSource]
|
|
938
1216
|
: [];
|
|
@@ -944,10 +1222,10 @@
|
|
|
944
1222
|
[targetSource]: merged
|
|
945
1223
|
};
|
|
946
1224
|
this.refreshSessionPathOptions(targetSource);
|
|
947
|
-
},
|
|
948
|
-
|
|
949
|
-
refreshSessionPathOptions(source) {
|
|
950
|
-
const targetSource = source === 'claude' ? 'claude' : 'codex';
|
|
1225
|
+
},
|
|
1226
|
+
|
|
1227
|
+
refreshSessionPathOptions(source) {
|
|
1228
|
+
const targetSource = source === 'claude' ? 'claude' : (source === 'all' ? 'all' : 'codex');
|
|
951
1229
|
const base = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
952
1230
|
? [...this.sessionPathOptionsMap[targetSource]]
|
|
953
1231
|
: [];
|
|
@@ -960,8 +1238,8 @@
|
|
|
960
1238
|
}
|
|
961
1239
|
},
|
|
962
1240
|
|
|
963
|
-
async loadSessionPathOptions(options = {}) {
|
|
964
|
-
const source = options.source === 'claude' ? 'claude' : 'codex';
|
|
1241
|
+
async loadSessionPathOptions(options = {}) {
|
|
1242
|
+
const source = options.source === 'claude' ? 'claude' : (options.source === 'all' ? 'all' : 'codex');
|
|
965
1243
|
const forceRefresh = !!options.forceRefresh;
|
|
966
1244
|
const loaded = !!this.sessionPathOptionsLoadedMap[source];
|
|
967
1245
|
if (!forceRefresh && loaded) {
|
|
@@ -1013,12 +1291,12 @@
|
|
|
1013
1291
|
await this.loadSessions();
|
|
1014
1292
|
},
|
|
1015
1293
|
|
|
1016
|
-
async clearSessionFilters() {
|
|
1017
|
-
this.sessionFilterSource = '
|
|
1018
|
-
this.sessionPathFilter = '';
|
|
1019
|
-
this.sessionQuery = '';
|
|
1020
|
-
this.sessionRoleFilter = 'all';
|
|
1021
|
-
this.sessionTimePreset = 'all';
|
|
1294
|
+
async clearSessionFilters() {
|
|
1295
|
+
this.sessionFilterSource = 'all';
|
|
1296
|
+
this.sessionPathFilter = '';
|
|
1297
|
+
this.sessionQuery = '';
|
|
1298
|
+
this.sessionRoleFilter = 'all';
|
|
1299
|
+
this.sessionTimePreset = 'all';
|
|
1022
1300
|
await this.onSessionSourceChange();
|
|
1023
1301
|
},
|
|
1024
1302
|
|
|
@@ -1093,7 +1371,7 @@
|
|
|
1093
1371
|
this.activeSession = null;
|
|
1094
1372
|
this.activeSessionMessages = [];
|
|
1095
1373
|
this.activeSessionDetailClipped = false;
|
|
1096
|
-
this.showMessage('
|
|
1374
|
+
this.showMessage('加载会话失败', 'error');
|
|
1097
1375
|
} finally {
|
|
1098
1376
|
this.sessionsLoading = false;
|
|
1099
1377
|
}
|
|
@@ -1223,8 +1501,10 @@
|
|
|
1223
1501
|
}
|
|
1224
1502
|
},
|
|
1225
1503
|
|
|
1226
|
-
downloadTextFile(fileName, content) {
|
|
1227
|
-
|
|
1504
|
+
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
1505
|
+
// 使用 UTF-8 BOM 确保文本编辑器正确识别编码
|
|
1506
|
+
const BOM = '\uFEFF';
|
|
1507
|
+
const blob = new Blob([BOM + content], { type: mimeType });
|
|
1228
1508
|
const url = URL.createObjectURL(blob);
|
|
1229
1509
|
const link = document.createElement('a');
|
|
1230
1510
|
link.href = url;
|
|
@@ -1255,10 +1535,10 @@
|
|
|
1255
1535
|
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
1256
1536
|
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
1257
1537
|
} else {
|
|
1258
|
-
this.showMessage('
|
|
1538
|
+
this.showMessage('操作成功', 'success');
|
|
1259
1539
|
}
|
|
1260
1540
|
} catch (e) {
|
|
1261
|
-
this.showMessage('
|
|
1541
|
+
this.showMessage('导出失败', 'error');
|
|
1262
1542
|
} finally {
|
|
1263
1543
|
this.sessionExporting[key] = false;
|
|
1264
1544
|
}
|
|
@@ -1267,15 +1547,22 @@
|
|
|
1267
1547
|
async switchProvider(name) {
|
|
1268
1548
|
this.currentProvider = name;
|
|
1269
1549
|
await this.loadModelsForProvider(name);
|
|
1270
|
-
|
|
1550
|
+
if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
|
|
1551
|
+
this.currentModel = this.models[0];
|
|
1552
|
+
}
|
|
1553
|
+
await this.applyCodexConfigDirect({ silent: true });
|
|
1271
1554
|
},
|
|
1272
1555
|
|
|
1273
1556
|
async onModelChange() {
|
|
1274
|
-
await this.
|
|
1557
|
+
await this.applyCodexConfigDirect();
|
|
1275
1558
|
},
|
|
1276
1559
|
|
|
1277
1560
|
async onServiceTierChange() {
|
|
1278
|
-
await this.
|
|
1561
|
+
await this.applyCodexConfigDirect({ silent: true });
|
|
1562
|
+
},
|
|
1563
|
+
|
|
1564
|
+
async onReasoningEffortChange() {
|
|
1565
|
+
await this.applyCodexConfigDirect({ silent: true });
|
|
1279
1566
|
},
|
|
1280
1567
|
|
|
1281
1568
|
async runHealthCheck() {
|
|
@@ -1320,15 +1607,15 @@
|
|
|
1320
1607
|
remote
|
|
1321
1608
|
};
|
|
1322
1609
|
if (ok) {
|
|
1323
|
-
this.showMessage('
|
|
1610
|
+
this.showMessage('检查通过', 'success');
|
|
1324
1611
|
}
|
|
1325
1612
|
} else {
|
|
1326
1613
|
this.healthCheckResult = null;
|
|
1327
|
-
this.showMessage('
|
|
1614
|
+
this.showMessage('检查失败', 'error');
|
|
1328
1615
|
}
|
|
1329
1616
|
} catch (e) {
|
|
1330
1617
|
this.healthCheckResult = null;
|
|
1331
|
-
this.showMessage('
|
|
1618
|
+
this.showMessage('检查失败', 'error');
|
|
1332
1619
|
} finally {
|
|
1333
1620
|
if (this.configMode === 'claude') {
|
|
1334
1621
|
try {
|
|
@@ -1369,7 +1656,50 @@
|
|
|
1369
1656
|
this.configTemplateContent = template;
|
|
1370
1657
|
this.showConfigTemplateModal = true;
|
|
1371
1658
|
} catch (e) {
|
|
1372
|
-
this.showMessage('
|
|
1659
|
+
this.showMessage('加载模板失败', 'error');
|
|
1660
|
+
}
|
|
1661
|
+
},
|
|
1662
|
+
|
|
1663
|
+
async applyCodexConfigDirect(options = {}) {
|
|
1664
|
+
if (this.codexApplying) return;
|
|
1665
|
+
|
|
1666
|
+
const provider = (this.currentProvider || '').trim();
|
|
1667
|
+
const model = (this.currentModel || '').trim();
|
|
1668
|
+
if (!provider || !model) {
|
|
1669
|
+
this.showMessage('请选择提供商和模型', 'error');
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
this.codexApplying = true;
|
|
1674
|
+
try {
|
|
1675
|
+
const tplRes = await api('get-config-template', {
|
|
1676
|
+
provider,
|
|
1677
|
+
model,
|
|
1678
|
+
serviceTier: this.serviceTier,
|
|
1679
|
+
reasoningEffort: this.modelReasoningEffort
|
|
1680
|
+
});
|
|
1681
|
+
if (tplRes.error) {
|
|
1682
|
+
this.showMessage('获取模板失败', 'error');
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
const applyRes = await api('apply-config-template', {
|
|
1687
|
+
template: tplRes.template
|
|
1688
|
+
});
|
|
1689
|
+
if (applyRes.error) {
|
|
1690
|
+
this.showMessage('应用模板失败', 'error');
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
if (options.silent !== true) {
|
|
1695
|
+
this.showMessage('配置已应用', 'success');
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
await this.loadAll();
|
|
1699
|
+
} catch (e) {
|
|
1700
|
+
this.showMessage('应用失败', 'error');
|
|
1701
|
+
} finally {
|
|
1702
|
+
this.codexApplying = false;
|
|
1373
1703
|
}
|
|
1374
1704
|
},
|
|
1375
1705
|
|
|
@@ -1380,7 +1710,7 @@
|
|
|
1380
1710
|
|
|
1381
1711
|
async applyConfigTemplate() {
|
|
1382
1712
|
if (!this.configTemplateContent || !this.configTemplateContent.trim()) {
|
|
1383
|
-
this.showMessage('
|
|
1713
|
+
this.showMessage('模板不能为空', 'error');
|
|
1384
1714
|
return;
|
|
1385
1715
|
}
|
|
1386
1716
|
|
|
@@ -1393,11 +1723,11 @@
|
|
|
1393
1723
|
this.showMessage(res.error, 'error');
|
|
1394
1724
|
return;
|
|
1395
1725
|
}
|
|
1396
|
-
this.showMessage('
|
|
1726
|
+
this.showMessage('模板已应用', 'success');
|
|
1397
1727
|
this.closeConfigTemplateModal();
|
|
1398
1728
|
await this.loadAll();
|
|
1399
1729
|
} catch (e) {
|
|
1400
|
-
this.showMessage('
|
|
1730
|
+
this.showMessage('应用模板失败', 'error');
|
|
1401
1731
|
} finally {
|
|
1402
1732
|
this.configTemplateApplying = false;
|
|
1403
1733
|
}
|
|
@@ -1418,7 +1748,7 @@
|
|
|
1418
1748
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1419
1749
|
this.showAgentsModal = true;
|
|
1420
1750
|
} catch (e) {
|
|
1421
|
-
this.showMessage('
|
|
1751
|
+
this.showMessage('加载文件失败', 'error');
|
|
1422
1752
|
} finally {
|
|
1423
1753
|
this.agentsLoading = false;
|
|
1424
1754
|
}
|
|
@@ -1442,7 +1772,7 @@
|
|
|
1442
1772
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1443
1773
|
this.showAgentsModal = true;
|
|
1444
1774
|
} catch (e) {
|
|
1445
|
-
this.showMessage('
|
|
1775
|
+
this.showMessage('加载文件失败', 'error');
|
|
1446
1776
|
} finally {
|
|
1447
1777
|
this.agentsLoading = false;
|
|
1448
1778
|
}
|
|
@@ -1451,7 +1781,7 @@
|
|
|
1451
1781
|
async openOpenclawWorkspaceEditor() {
|
|
1452
1782
|
const fileName = (this.openclawWorkspaceFileName || '').trim();
|
|
1453
1783
|
if (!fileName) {
|
|
1454
|
-
this.showMessage('
|
|
1784
|
+
this.showMessage('请输入文件名', 'error');
|
|
1455
1785
|
return;
|
|
1456
1786
|
}
|
|
1457
1787
|
this.setAgentsModalContext('openclaw-workspace', { fileName });
|
|
@@ -1471,7 +1801,7 @@
|
|
|
1471
1801
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1472
1802
|
this.showAgentsModal = true;
|
|
1473
1803
|
} catch (e) {
|
|
1474
|
-
this.showMessage('
|
|
1804
|
+
this.showMessage('加载文件失败', 'error');
|
|
1475
1805
|
} finally {
|
|
1476
1806
|
this.agentsLoading = false;
|
|
1477
1807
|
}
|
|
@@ -1533,7 +1863,7 @@
|
|
|
1533
1863
|
this.showMessage(successLabel, 'success');
|
|
1534
1864
|
this.closeAgentsModal();
|
|
1535
1865
|
} catch (e) {
|
|
1536
|
-
this.showMessage('
|
|
1866
|
+
this.showMessage('保存失败', 'error');
|
|
1537
1867
|
} finally {
|
|
1538
1868
|
this.agentsSaving = false;
|
|
1539
1869
|
}
|
|
@@ -1547,82 +1877,214 @@
|
|
|
1547
1877
|
if (!name) {
|
|
1548
1878
|
return this.showMessage('名称不能为空', 'error');
|
|
1549
1879
|
}
|
|
1880
|
+
if (name.toLowerCase() === 'local') {
|
|
1881
|
+
return this.showMessage('local provider 为系统保留名称,不可新增', 'error');
|
|
1882
|
+
}
|
|
1550
1883
|
if (this.providersList.some(item => item.name === name)) {
|
|
1551
|
-
return this.showMessage('
|
|
1884
|
+
return this.showMessage('名称已存在', 'error');
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
try {
|
|
1888
|
+
const res = await api('add-provider', {
|
|
1889
|
+
name,
|
|
1890
|
+
url: this.newProvider.url.trim(),
|
|
1891
|
+
key: this.newProvider.key || ''
|
|
1892
|
+
});
|
|
1893
|
+
if (res.error) {
|
|
1894
|
+
this.showMessage(res.error, 'error');
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
this.showMessage('操作成功', 'success');
|
|
1899
|
+
this.closeAddModal();
|
|
1900
|
+
await this.loadAll();
|
|
1901
|
+
} catch (e) {
|
|
1902
|
+
this.showMessage('添加失败', 'error');
|
|
1552
1903
|
}
|
|
1904
|
+
},
|
|
1553
1905
|
|
|
1554
|
-
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1906
|
+
getCurrentCodexAuthProfile() {
|
|
1907
|
+
const list = Array.isArray(this.codexAuthProfiles) ? this.codexAuthProfiles : [];
|
|
1908
|
+
return list.find((item) => !!(item && item.current)) || null;
|
|
1909
|
+
},
|
|
1558
1910
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1911
|
+
isLocalLikeProvider(providerOrName) {
|
|
1912
|
+
if (!providerOrName) return false;
|
|
1913
|
+
const rawName = typeof providerOrName === 'object'
|
|
1914
|
+
? String(providerOrName.name || '')
|
|
1915
|
+
: String(providerOrName);
|
|
1916
|
+
const normalized = rawName.trim().toLowerCase();
|
|
1917
|
+
return normalized === 'local' || normalized === 'codexmate-proxy';
|
|
1918
|
+
},
|
|
1919
|
+
|
|
1920
|
+
providerPillState(provider) {
|
|
1921
|
+
if (this.isLocalLikeProvider(provider)) {
|
|
1922
|
+
const currentProfile = this.getCurrentCodexAuthProfile();
|
|
1923
|
+
return currentProfile
|
|
1924
|
+
? { configured: true, text: '已登录' }
|
|
1925
|
+
: { configured: false, text: '未登录' };
|
|
1926
|
+
}
|
|
1927
|
+
const configured = !!(provider && provider.hasKey);
|
|
1928
|
+
return {
|
|
1929
|
+
configured,
|
|
1930
|
+
text: configured ? '已配置' : '未配置'
|
|
1931
|
+
};
|
|
1932
|
+
},
|
|
1933
|
+
|
|
1934
|
+
providerPillConfigured(provider) {
|
|
1935
|
+
return this.providerPillState(provider).configured;
|
|
1936
|
+
},
|
|
1937
|
+
|
|
1938
|
+
providerPillText(provider) {
|
|
1939
|
+
return this.providerPillState(provider).text;
|
|
1940
|
+
},
|
|
1941
|
+
|
|
1942
|
+
isReadOnlyProvider(providerOrName) {
|
|
1943
|
+
if (!providerOrName) return false;
|
|
1944
|
+
if (typeof providerOrName === 'object') {
|
|
1945
|
+
return !!providerOrName.readOnly;
|
|
1946
|
+
}
|
|
1947
|
+
const name = String(providerOrName).trim();
|
|
1948
|
+
if (!name) return false;
|
|
1949
|
+
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
1950
|
+
return !!(target && target.readOnly);
|
|
1951
|
+
},
|
|
1952
|
+
|
|
1953
|
+
isNonDeletableProvider(providerOrName) {
|
|
1954
|
+
if (!providerOrName) return false;
|
|
1955
|
+
if (typeof providerOrName === 'object') {
|
|
1956
|
+
const directName = String(providerOrName.name || '').trim().toLowerCase();
|
|
1957
|
+
if (directName === 'local' || directName === 'codexmate-proxy') {
|
|
1958
|
+
return true;
|
|
1959
|
+
}
|
|
1960
|
+
return !!providerOrName.nonDeletable;
|
|
1961
|
+
}
|
|
1962
|
+
const name = String(providerOrName).trim();
|
|
1963
|
+
if (!name) return false;
|
|
1964
|
+
const normalized = name.toLowerCase();
|
|
1965
|
+
if (normalized === 'local' || normalized === 'codexmate-proxy') {
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1968
|
+
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
1969
|
+
return !!(target && target.nonDeletable);
|
|
1970
|
+
},
|
|
1971
|
+
|
|
1972
|
+
shouldShowProviderDelete(provider) {
|
|
1973
|
+
return !this.isReadOnlyProvider(provider) && !this.isNonDeletableProvider(provider);
|
|
1974
|
+
},
|
|
1975
|
+
|
|
1976
|
+
shouldShowProviderEdit(provider) {
|
|
1977
|
+
return !this.isReadOnlyProvider(provider) && !this.isNonDeletableProvider(provider);
|
|
1978
|
+
},
|
|
1979
|
+
|
|
1980
|
+
shouldAllowProviderShare(provider) {
|
|
1981
|
+
return !this.isReadOnlyProvider(provider) && !this.isLocalLikeProvider(provider);
|
|
1566
1982
|
},
|
|
1567
1983
|
|
|
1568
1984
|
async deleteProvider(name) {
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
});
|
|
1985
|
+
if (this.isNonDeletableProvider(name)) {
|
|
1986
|
+
this.showMessage('该 provider 为保留项,不可删除', 'info');
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const res = await api('delete-provider', { name });
|
|
1990
|
+
if (res.error) {
|
|
1991
|
+
this.showMessage(res.error, 'error');
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (res.switched && res.provider) {
|
|
1995
|
+
this.showMessage(`已删除提供商,自动切换到 ${res.provider}${res.model ? ` / ${res.model}` : ''}`, 'success');
|
|
1996
|
+
} else {
|
|
1997
|
+
this.showMessage('操作成功', 'success');
|
|
1998
|
+
}
|
|
1999
|
+
await this.loadAll();
|
|
1574
2000
|
},
|
|
1575
2001
|
|
|
1576
2002
|
openEditModal(provider) {
|
|
2003
|
+
if (!this.shouldShowProviderEdit(provider)) {
|
|
2004
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'info');
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
1577
2007
|
this.editingProvider = {
|
|
1578
2008
|
name: provider.name,
|
|
1579
2009
|
url: provider.url || '',
|
|
1580
|
-
key: ''
|
|
2010
|
+
key: '',
|
|
2011
|
+
readOnly: !!provider.readOnly,
|
|
2012
|
+
nonEditable: this.isNonDeletableProvider(provider)
|
|
1581
2013
|
};
|
|
1582
2014
|
this.showEditModal = true;
|
|
1583
2015
|
},
|
|
1584
2016
|
|
|
1585
2017
|
async updateProvider() {
|
|
2018
|
+
if (this.editingProvider.readOnly || this.editingProvider.nonEditable) {
|
|
2019
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'error');
|
|
2020
|
+
this.closeEditModal();
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
1586
2023
|
if (!this.editingProvider.url) {
|
|
1587
2024
|
return this.showMessage('URL 必填', 'error');
|
|
1588
2025
|
}
|
|
1589
2026
|
|
|
1590
2027
|
const name = this.editingProvider.name;
|
|
1591
|
-
const
|
|
1592
|
-
const
|
|
2028
|
+
const url = this.editingProvider.url.trim();
|
|
2029
|
+
const key = this.editingProvider.key || '';
|
|
1593
2030
|
this.closeEditModal();
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
2031
|
+
try {
|
|
2032
|
+
const res = await api('update-provider', { name, url, key });
|
|
2033
|
+
if (res.error) {
|
|
2034
|
+
this.showMessage(res.error, 'error');
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
this.showMessage('操作成功', 'success');
|
|
2038
|
+
await this.loadAll();
|
|
2039
|
+
} catch (e) {
|
|
2040
|
+
this.showMessage('更新失败', 'error');
|
|
2041
|
+
}
|
|
1598
2042
|
},
|
|
1599
2043
|
|
|
1600
2044
|
closeEditModal() {
|
|
1601
2045
|
this.showEditModal = false;
|
|
1602
|
-
this.editingProvider = { name: '', url: '', key: '' };
|
|
2046
|
+
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false };
|
|
2047
|
+
},
|
|
2048
|
+
|
|
2049
|
+
async resetConfig() {
|
|
2050
|
+
if (this.resetConfigLoading) return;
|
|
2051
|
+
this.resetConfigLoading = true;
|
|
2052
|
+
try {
|
|
2053
|
+
const res = await api('reset-config');
|
|
2054
|
+
if (res.error) {
|
|
2055
|
+
this.showMessage(res.error, 'error');
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
const backup = res.backupFile ? `(已备份: ${res.backupFile})` : '';
|
|
2059
|
+
this.showMessage(`配置已重装${backup}`, 'success');
|
|
2060
|
+
await this.loadAll();
|
|
2061
|
+
} catch (e) {
|
|
2062
|
+
this.showMessage('重装失败', 'error');
|
|
2063
|
+
} finally {
|
|
2064
|
+
this.resetConfigLoading = false;
|
|
2065
|
+
}
|
|
1603
2066
|
},
|
|
1604
2067
|
|
|
1605
2068
|
async addModel() {
|
|
1606
2069
|
if (!this.newModelName || !this.newModelName.trim()) {
|
|
1607
|
-
return this.showMessage('
|
|
2070
|
+
return this.showMessage('请输入模型', 'error');
|
|
1608
2071
|
}
|
|
1609
2072
|
const res = await api('add-model', { model: this.newModelName.trim() });
|
|
1610
2073
|
if (res.error) {
|
|
1611
2074
|
this.showMessage(res.error, 'error');
|
|
1612
2075
|
} else {
|
|
1613
|
-
this.showMessage('
|
|
2076
|
+
this.showMessage('操作成功', 'success');
|
|
1614
2077
|
this.closeModelModal();
|
|
1615
2078
|
await this.loadAll();
|
|
1616
2079
|
}
|
|
1617
2080
|
},
|
|
1618
2081
|
|
|
1619
2082
|
async removeModel(model) {
|
|
1620
|
-
if (!confirm(`确定删除模型 "${model}"?`)) return;
|
|
1621
2083
|
const res = await api('delete-model', { model });
|
|
1622
2084
|
if (res.error) {
|
|
1623
2085
|
this.showMessage(res.error, 'error');
|
|
1624
2086
|
} else {
|
|
1625
|
-
this.showMessage('
|
|
2087
|
+
this.showMessage('操作成功', 'success');
|
|
1626
2088
|
await this.loadAll();
|
|
1627
2089
|
}
|
|
1628
2090
|
},
|
|
@@ -1677,7 +2139,7 @@
|
|
|
1677
2139
|
this.saveClaudeConfigs();
|
|
1678
2140
|
this.updateClaudeModelsCurrent();
|
|
1679
2141
|
if (!this.claudeConfigs[name].apiKey) {
|
|
1680
|
-
this.showMessage('
|
|
2142
|
+
this.showMessage('请先配置 API Key', 'error');
|
|
1681
2143
|
return;
|
|
1682
2144
|
}
|
|
1683
2145
|
this.applyClaudeConfig(name);
|
|
@@ -1707,7 +2169,7 @@
|
|
|
1707
2169
|
hasKey: !!this.editingConfig.apiKey
|
|
1708
2170
|
};
|
|
1709
2171
|
this.saveClaudeConfigs();
|
|
1710
|
-
this.showMessage('
|
|
2172
|
+
this.showMessage('操作成功', 'success');
|
|
1711
2173
|
this.closeEditConfigModal();
|
|
1712
2174
|
if (name === this.currentClaudeConfig) {
|
|
1713
2175
|
this.refreshClaudeModelContext();
|
|
@@ -1731,7 +2193,7 @@
|
|
|
1731
2193
|
|
|
1732
2194
|
const config = this.claudeConfigs[name];
|
|
1733
2195
|
if (!config.apiKey) {
|
|
1734
|
-
this.showMessage('
|
|
2196
|
+
this.showMessage('已保存,未应用', 'info');
|
|
1735
2197
|
this.closeEditConfigModal();
|
|
1736
2198
|
if (name === this.currentClaudeConfig) {
|
|
1737
2199
|
this.refreshClaudeModelContext();
|
|
@@ -1741,7 +2203,7 @@
|
|
|
1741
2203
|
|
|
1742
2204
|
const res = await api('apply-claude-config', { config });
|
|
1743
2205
|
if (res.error || res.success === false) {
|
|
1744
|
-
this.showMessage(res.error || '
|
|
2206
|
+
this.showMessage(res.error || '应用配置失败', 'error');
|
|
1745
2207
|
} else {
|
|
1746
2208
|
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
1747
2209
|
this.showMessage(`已保存并应用到 Claude 配置${targetTip}`, 'success');
|
|
@@ -1754,15 +2216,15 @@
|
|
|
1754
2216
|
|
|
1755
2217
|
addClaudeConfig() {
|
|
1756
2218
|
if (!this.newClaudeConfig.name || !this.newClaudeConfig.name.trim()) {
|
|
1757
|
-
return this.showMessage('
|
|
2219
|
+
return this.showMessage('请输入名称', 'error');
|
|
1758
2220
|
}
|
|
1759
2221
|
const name = this.newClaudeConfig.name.trim();
|
|
1760
2222
|
if (this.claudeConfigs[name]) {
|
|
1761
|
-
return this.showMessage('
|
|
2223
|
+
return this.showMessage('名称已存在', 'error');
|
|
1762
2224
|
}
|
|
1763
2225
|
const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
|
|
1764
2226
|
if (duplicateName) {
|
|
1765
|
-
return this.showMessage('
|
|
2227
|
+
return this.showMessage('配置已存在', 'info');
|
|
1766
2228
|
}
|
|
1767
2229
|
|
|
1768
2230
|
this.claudeConfigs[name] = {
|
|
@@ -1774,14 +2236,14 @@
|
|
|
1774
2236
|
|
|
1775
2237
|
this.currentClaudeConfig = name;
|
|
1776
2238
|
this.saveClaudeConfigs();
|
|
1777
|
-
this.showMessage('
|
|
2239
|
+
this.showMessage('操作成功', 'success');
|
|
1778
2240
|
this.closeClaudeConfigModal();
|
|
1779
2241
|
this.refreshClaudeModelContext();
|
|
1780
2242
|
},
|
|
1781
2243
|
|
|
1782
2244
|
deleteClaudeConfig(name) {
|
|
1783
2245
|
if (Object.keys(this.claudeConfigs).length <= 1) {
|
|
1784
|
-
return this.showMessage('
|
|
2246
|
+
return this.showMessage('至少保留一项', 'error');
|
|
1785
2247
|
}
|
|
1786
2248
|
|
|
1787
2249
|
if (!confirm(`确定删除配置 "${name}"?`)) return;
|
|
@@ -1791,7 +2253,7 @@
|
|
|
1791
2253
|
this.currentClaudeConfig = Object.keys(this.claudeConfigs)[0];
|
|
1792
2254
|
}
|
|
1793
2255
|
this.saveClaudeConfigs();
|
|
1794
|
-
this.showMessage('
|
|
2256
|
+
this.showMessage('操作成功', 'success');
|
|
1795
2257
|
this.refreshClaudeModelContext();
|
|
1796
2258
|
},
|
|
1797
2259
|
|
|
@@ -1801,12 +2263,12 @@
|
|
|
1801
2263
|
const config = this.claudeConfigs[name];
|
|
1802
2264
|
|
|
1803
2265
|
if (!config.apiKey) {
|
|
1804
|
-
return this.showMessage('
|
|
2266
|
+
return this.showMessage('请先配置 API Key', 'error');
|
|
1805
2267
|
}
|
|
1806
2268
|
|
|
1807
2269
|
const res = await api('apply-claude-config', { config });
|
|
1808
2270
|
if (res.error || res.success === false) {
|
|
1809
|
-
this.showMessage(res.error || '
|
|
2271
|
+
this.showMessage(res.error || '应用配置失败', 'error');
|
|
1810
2272
|
} else {
|
|
1811
2273
|
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
1812
2274
|
this.showMessage(`已应用配置到 Claude 设置: ${name}${targetTip}`, 'success');
|
|
@@ -2014,7 +2476,7 @@
|
|
|
2014
2476
|
}
|
|
2015
2477
|
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
2016
2478
|
if (!silent) {
|
|
2017
|
-
this.showMessage('
|
|
2479
|
+
this.showMessage('已读取配置', 'success');
|
|
2018
2480
|
}
|
|
2019
2481
|
return true;
|
|
2020
2482
|
},
|
|
@@ -2110,7 +2572,7 @@
|
|
|
2110
2572
|
this.refreshOpenclawProviders(parsed.data);
|
|
2111
2573
|
this.refreshOpenclawAgentsList(parsed.data);
|
|
2112
2574
|
if (!silent) {
|
|
2113
|
-
this.showMessage('
|
|
2575
|
+
this.showMessage('已刷新配置', 'success');
|
|
2114
2576
|
}
|
|
2115
2577
|
return true;
|
|
2116
2578
|
},
|
|
@@ -2408,7 +2870,7 @@
|
|
|
2408
2870
|
this.refreshOpenclawProviders(config);
|
|
2409
2871
|
this.refreshOpenclawAgentsList(config);
|
|
2410
2872
|
this.fillOpenclawQuickFromConfig(config);
|
|
2411
|
-
this.showMessage('
|
|
2873
|
+
this.showMessage('已写入', 'success');
|
|
2412
2874
|
},
|
|
2413
2875
|
|
|
2414
2876
|
applyOpenclawQuickToText() {
|
|
@@ -2421,7 +2883,7 @@
|
|
|
2421
2883
|
const providerName = (this.openclawQuick.providerName || '').trim();
|
|
2422
2884
|
const modelId = (this.openclawQuick.modelId || '').trim();
|
|
2423
2885
|
if (!providerName) {
|
|
2424
|
-
this.showMessage('
|
|
2886
|
+
this.showMessage('请填写名称', 'error');
|
|
2425
2887
|
return;
|
|
2426
2888
|
}
|
|
2427
2889
|
if (providerName.includes('/')) {
|
|
@@ -2429,7 +2891,7 @@
|
|
|
2429
2891
|
return;
|
|
2430
2892
|
}
|
|
2431
2893
|
if (!modelId) {
|
|
2432
|
-
this.showMessage('请填写模型
|
|
2894
|
+
this.showMessage('请填写模型', 'error');
|
|
2433
2895
|
return;
|
|
2434
2896
|
}
|
|
2435
2897
|
|
|
@@ -2440,7 +2902,7 @@
|
|
|
2440
2902
|
const provider = ensureObject(providers[providerName]);
|
|
2441
2903
|
const baseUrl = (this.openclawQuick.baseUrl || '').trim();
|
|
2442
2904
|
if (!baseUrl && !provider.baseUrl) {
|
|
2443
|
-
this.showMessage('请填写
|
|
2905
|
+
this.showMessage('请填写 URL', 'error');
|
|
2444
2906
|
return;
|
|
2445
2907
|
}
|
|
2446
2908
|
|
|
@@ -2526,7 +2988,7 @@
|
|
|
2526
2988
|
this.fillOpenclawStructured(config);
|
|
2527
2989
|
this.refreshOpenclawProviders(config);
|
|
2528
2990
|
this.refreshOpenclawAgentsList(config);
|
|
2529
|
-
this.showMessage('
|
|
2991
|
+
this.showMessage('配置已写入', 'success');
|
|
2530
2992
|
},
|
|
2531
2993
|
|
|
2532
2994
|
addOpenclawFallback() {
|
|
@@ -2630,6 +3092,144 @@
|
|
|
2630
3092
|
this.resetOpenclawQuick();
|
|
2631
3093
|
},
|
|
2632
3094
|
|
|
3095
|
+
normalizeInstallPackageManager(value) {
|
|
3096
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3097
|
+
if (normalized === 'pnpm' || normalized === 'bun' || normalized === 'npm') {
|
|
3098
|
+
return normalized;
|
|
3099
|
+
}
|
|
3100
|
+
return 'npm';
|
|
3101
|
+
},
|
|
3102
|
+
|
|
3103
|
+
normalizeInstallAction(value) {
|
|
3104
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3105
|
+
if (normalized === 'update' || normalized === 'uninstall' || normalized === 'install') {
|
|
3106
|
+
return normalized;
|
|
3107
|
+
}
|
|
3108
|
+
return 'install';
|
|
3109
|
+
},
|
|
3110
|
+
|
|
3111
|
+
normalizeInstallRegistryPreset(value) {
|
|
3112
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3113
|
+
if (normalized === 'default' || normalized === 'npmmirror' || normalized === 'tencent' || normalized === 'custom') {
|
|
3114
|
+
return normalized;
|
|
3115
|
+
}
|
|
3116
|
+
return 'default';
|
|
3117
|
+
},
|
|
3118
|
+
|
|
3119
|
+
normalizeInstallRegistryUrl(value) {
|
|
3120
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
3121
|
+
if (!normalized) return '';
|
|
3122
|
+
if (!/^https?:\/\//i.test(normalized)) {
|
|
3123
|
+
return '';
|
|
3124
|
+
}
|
|
3125
|
+
return normalized.replace(/\/+$/, '');
|
|
3126
|
+
},
|
|
3127
|
+
|
|
3128
|
+
resolveInstallRegistryUrl(presetValue, customValue) {
|
|
3129
|
+
const preset = this.normalizeInstallRegistryPreset(presetValue);
|
|
3130
|
+
if (preset === 'npmmirror') {
|
|
3131
|
+
return 'https://registry.npmmirror.com';
|
|
3132
|
+
}
|
|
3133
|
+
if (preset === 'tencent') {
|
|
3134
|
+
return 'https://mirrors.cloud.tencent.com/npm';
|
|
3135
|
+
}
|
|
3136
|
+
if (preset === 'custom') {
|
|
3137
|
+
return this.normalizeInstallRegistryUrl(customValue);
|
|
3138
|
+
}
|
|
3139
|
+
return '';
|
|
3140
|
+
},
|
|
3141
|
+
|
|
3142
|
+
appendInstallRegistryOption(command, actionName) {
|
|
3143
|
+
const base = typeof command === 'string' ? command.trim() : '';
|
|
3144
|
+
if (!base) return '';
|
|
3145
|
+
const action = this.normalizeInstallAction(actionName);
|
|
3146
|
+
if (action === 'uninstall') {
|
|
3147
|
+
return base;
|
|
3148
|
+
}
|
|
3149
|
+
const registry = this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
3150
|
+
if (!registry) {
|
|
3151
|
+
return base;
|
|
3152
|
+
}
|
|
3153
|
+
return `${base} --registry=${registry}`;
|
|
3154
|
+
},
|
|
3155
|
+
|
|
3156
|
+
resolveInstallPlatform() {
|
|
3157
|
+
const navPlatform = typeof navigator !== 'undefined' && typeof navigator.platform === 'string'
|
|
3158
|
+
? navigator.platform.trim().toLowerCase()
|
|
3159
|
+
: '';
|
|
3160
|
+
if (navPlatform.includes('win')) return 'win32';
|
|
3161
|
+
if (navPlatform.includes('mac')) return 'darwin';
|
|
3162
|
+
return 'linux';
|
|
3163
|
+
},
|
|
3164
|
+
|
|
3165
|
+
buildInstallCommandMatrix(packageManager) {
|
|
3166
|
+
const manager = this.normalizeInstallPackageManager(packageManager);
|
|
3167
|
+
const matrix = {
|
|
3168
|
+
claude: {
|
|
3169
|
+
install: '',
|
|
3170
|
+
update: '',
|
|
3171
|
+
uninstall: ''
|
|
3172
|
+
},
|
|
3173
|
+
codex: {
|
|
3174
|
+
install: '',
|
|
3175
|
+
update: '',
|
|
3176
|
+
uninstall: ''
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
if (manager === 'pnpm') {
|
|
3180
|
+
matrix.claude.install = 'pnpm add -g @anthropic-ai/claude-code';
|
|
3181
|
+
matrix.claude.update = 'pnpm up -g @anthropic-ai/claude-code';
|
|
3182
|
+
matrix.claude.uninstall = 'pnpm remove -g @anthropic-ai/claude-code';
|
|
3183
|
+
matrix.codex.install = 'pnpm add -g @openai/codex';
|
|
3184
|
+
matrix.codex.update = 'pnpm up -g @openai/codex';
|
|
3185
|
+
matrix.codex.uninstall = 'pnpm remove -g @openai/codex';
|
|
3186
|
+
return matrix;
|
|
3187
|
+
}
|
|
3188
|
+
if (manager === 'bun') {
|
|
3189
|
+
matrix.claude.install = 'bun add -g @anthropic-ai/claude-code';
|
|
3190
|
+
matrix.claude.update = 'bun update -g @anthropic-ai/claude-code';
|
|
3191
|
+
matrix.claude.uninstall = 'bun remove -g @anthropic-ai/claude-code';
|
|
3192
|
+
matrix.codex.install = 'bun add -g @openai/codex';
|
|
3193
|
+
matrix.codex.update = 'bun update -g @openai/codex';
|
|
3194
|
+
matrix.codex.uninstall = 'bun remove -g @openai/codex';
|
|
3195
|
+
return matrix;
|
|
3196
|
+
}
|
|
3197
|
+
matrix.claude.install = 'npm install -g @anthropic-ai/claude-code';
|
|
3198
|
+
matrix.claude.update = 'npm update -g @anthropic-ai/claude-code';
|
|
3199
|
+
matrix.claude.uninstall = 'npm uninstall -g @anthropic-ai/claude-code';
|
|
3200
|
+
matrix.codex.install = 'npm install -g @openai/codex';
|
|
3201
|
+
matrix.codex.update = 'npm update -g @openai/codex';
|
|
3202
|
+
matrix.codex.uninstall = 'npm uninstall -g @openai/codex';
|
|
3203
|
+
return matrix;
|
|
3204
|
+
},
|
|
3205
|
+
|
|
3206
|
+
getInstallCommand(targetId, actionName) {
|
|
3207
|
+
const targetKey = typeof targetId === 'string' ? targetId.trim() : '';
|
|
3208
|
+
if (!targetKey) return '';
|
|
3209
|
+
const action = this.normalizeInstallAction(actionName);
|
|
3210
|
+
const currentMap = this.buildInstallCommandMatrix(this.installPackageManager);
|
|
3211
|
+
const current = currentMap[targetKey] && typeof currentMap[targetKey][action] === 'string'
|
|
3212
|
+
? currentMap[targetKey][action]
|
|
3213
|
+
: '';
|
|
3214
|
+
return this.appendInstallRegistryOption(current, action);
|
|
3215
|
+
},
|
|
3216
|
+
|
|
3217
|
+
setInstallCommandAction(actionName) {
|
|
3218
|
+
this.installCommandAction = this.normalizeInstallAction(actionName);
|
|
3219
|
+
},
|
|
3220
|
+
|
|
3221
|
+
setInstallRegistryPreset(presetName) {
|
|
3222
|
+
this.installRegistryPreset = this.normalizeInstallRegistryPreset(presetName);
|
|
3223
|
+
},
|
|
3224
|
+
|
|
3225
|
+
openInstallModal() {
|
|
3226
|
+
this.showInstallModal = true;
|
|
3227
|
+
},
|
|
3228
|
+
|
|
3229
|
+
closeInstallModal() {
|
|
3230
|
+
this.showInstallModal = false;
|
|
3231
|
+
},
|
|
3232
|
+
|
|
2633
3233
|
async loadOpenclawConfigFromFile(options = {}) {
|
|
2634
3234
|
const silent = !!options.silent;
|
|
2635
3235
|
const force = !!options.force;
|
|
@@ -2655,11 +3255,11 @@
|
|
|
2655
3255
|
}
|
|
2656
3256
|
this.syncOpenclawStructuredFromText({ silent: true });
|
|
2657
3257
|
if (!silent) {
|
|
2658
|
-
this.showMessage('
|
|
3258
|
+
this.showMessage('加载完成', 'success');
|
|
2659
3259
|
}
|
|
2660
3260
|
} catch (e) {
|
|
2661
3261
|
if (!silent) {
|
|
2662
|
-
this.showMessage('
|
|
3262
|
+
this.showMessage('加载配置失败', 'error');
|
|
2663
3263
|
}
|
|
2664
3264
|
} finally {
|
|
2665
3265
|
this.openclawFileLoading = false;
|
|
@@ -2668,12 +3268,12 @@
|
|
|
2668
3268
|
|
|
2669
3269
|
persistOpenclawConfig({ closeModal = true } = {}) {
|
|
2670
3270
|
if (!this.openclawEditing.name || !this.openclawEditing.name.trim()) {
|
|
2671
|
-
this.showMessage('
|
|
3271
|
+
this.showMessage('请输入名称', 'error');
|
|
2672
3272
|
return '';
|
|
2673
3273
|
}
|
|
2674
3274
|
const name = this.openclawEditing.name.trim();
|
|
2675
3275
|
if (!this.openclawEditing.lockName && this.openclawConfigs[name]) {
|
|
2676
|
-
this.showMessage('
|
|
3276
|
+
this.showMessage('名称已存在', 'error');
|
|
2677
3277
|
return '';
|
|
2678
3278
|
}
|
|
2679
3279
|
if (!this.openclawEditing.content || !this.openclawEditing.content.trim()) {
|
|
@@ -2697,7 +3297,7 @@
|
|
|
2697
3297
|
try {
|
|
2698
3298
|
const name = this.persistOpenclawConfig();
|
|
2699
3299
|
if (!name) return;
|
|
2700
|
-
this.showMessage('
|
|
3300
|
+
this.showMessage('操作成功', 'success');
|
|
2701
3301
|
} finally {
|
|
2702
3302
|
this.openclawSaving = false;
|
|
2703
3303
|
}
|
|
@@ -2714,7 +3314,7 @@
|
|
|
2714
3314
|
lineEnding: this.openclawLineEnding
|
|
2715
3315
|
});
|
|
2716
3316
|
if (res.error || res.success === false) {
|
|
2717
|
-
this.showMessage(res.error || '
|
|
3317
|
+
this.showMessage(res.error || '应用配置失败', 'error');
|
|
2718
3318
|
return;
|
|
2719
3319
|
}
|
|
2720
3320
|
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
@@ -2723,7 +3323,7 @@
|
|
|
2723
3323
|
this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
|
|
2724
3324
|
this.closeOpenclawConfigModal();
|
|
2725
3325
|
} catch (e) {
|
|
2726
|
-
this.showMessage('
|
|
3326
|
+
this.showMessage('应用配置失败', 'error');
|
|
2727
3327
|
} finally {
|
|
2728
3328
|
this.openclawApplying = false;
|
|
2729
3329
|
}
|
|
@@ -2731,7 +3331,7 @@
|
|
|
2731
3331
|
|
|
2732
3332
|
deleteOpenclawConfig(name) {
|
|
2733
3333
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
2734
|
-
return this.showMessage('
|
|
3334
|
+
return this.showMessage('至少保留一项', 'error');
|
|
2735
3335
|
}
|
|
2736
3336
|
if (!confirm(`确定删除配置 "${name}"?`)) return;
|
|
2737
3337
|
delete this.openclawConfigs[name];
|
|
@@ -2739,21 +3339,21 @@
|
|
|
2739
3339
|
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
2740
3340
|
}
|
|
2741
3341
|
this.saveOpenclawConfigs();
|
|
2742
|
-
this.showMessage('
|
|
3342
|
+
this.showMessage('操作成功', 'success');
|
|
2743
3343
|
},
|
|
2744
3344
|
|
|
2745
3345
|
async applyOpenclawConfig(name) {
|
|
2746
3346
|
this.currentOpenclawConfig = name;
|
|
2747
3347
|
const config = this.openclawConfigs[name];
|
|
2748
3348
|
if (!this.openclawHasContent(config)) {
|
|
2749
|
-
return this.showMessage('
|
|
3349
|
+
return this.showMessage('配置为空', 'error');
|
|
2750
3350
|
}
|
|
2751
3351
|
const res = await api('apply-openclaw-config', {
|
|
2752
3352
|
content: config.content,
|
|
2753
3353
|
lineEnding: this.openclawLineEnding
|
|
2754
3354
|
});
|
|
2755
3355
|
if (res.error || res.success === false) {
|
|
2756
|
-
this.showMessage(res.error || '
|
|
3356
|
+
this.showMessage(res.error || '应用配置失败', 'error');
|
|
2757
3357
|
} else {
|
|
2758
3358
|
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
2759
3359
|
this.openclawConfigExists = true;
|
|
@@ -2826,6 +3426,427 @@
|
|
|
2826
3426
|
}
|
|
2827
3427
|
},
|
|
2828
3428
|
|
|
3429
|
+
async downloadClaudeDirectory() {
|
|
3430
|
+
if (this.claudeDownloadLoading) return;
|
|
3431
|
+
this.claudeDownloadLoading = true;
|
|
3432
|
+
this.claudeDownloadProgress = 5;
|
|
3433
|
+
this.claudeDownloadTimer = setInterval(() => {
|
|
3434
|
+
if (this.claudeDownloadProgress < 90) {
|
|
3435
|
+
this.claudeDownloadProgress += 5;
|
|
3436
|
+
}
|
|
3437
|
+
}, 400);
|
|
3438
|
+
try {
|
|
3439
|
+
const res = await api('download-claude-dir');
|
|
3440
|
+
if (res && res.error) {
|
|
3441
|
+
this.showMessage(res.error, 'error');
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
if (!res || res.success !== true || !res.fileName) {
|
|
3445
|
+
this.showMessage('备份失败', 'error');
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
this.claudeDownloadProgress = 100;
|
|
3449
|
+
const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
|
|
3450
|
+
const link = document.createElement('a');
|
|
3451
|
+
link.href = downloadUrl;
|
|
3452
|
+
link.download = res.fileName;
|
|
3453
|
+
document.body.appendChild(link);
|
|
3454
|
+
link.click();
|
|
3455
|
+
document.body.removeChild(link);
|
|
3456
|
+
this.showMessage('备份成功,开始下载', 'success');
|
|
3457
|
+
} catch (e) {
|
|
3458
|
+
this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
3459
|
+
} finally {
|
|
3460
|
+
if (this.claudeDownloadTimer) {
|
|
3461
|
+
clearInterval(this.claudeDownloadTimer);
|
|
3462
|
+
this.claudeDownloadTimer = null;
|
|
3463
|
+
}
|
|
3464
|
+
this.claudeDownloadLoading = false;
|
|
3465
|
+
setTimeout(() => {
|
|
3466
|
+
this.claudeDownloadProgress = 0;
|
|
3467
|
+
}, 800);
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
|
|
3471
|
+
async downloadCodexDirectory() {
|
|
3472
|
+
if (this.codexDownloadLoading) return;
|
|
3473
|
+
this.codexDownloadLoading = true;
|
|
3474
|
+
this.codexDownloadProgress = 5;
|
|
3475
|
+
this.codexDownloadTimer = setInterval(() => {
|
|
3476
|
+
if (this.codexDownloadProgress < 90) {
|
|
3477
|
+
this.codexDownloadProgress += 5;
|
|
3478
|
+
}
|
|
3479
|
+
}, 400);
|
|
3480
|
+
try {
|
|
3481
|
+
const res = await api('download-codex-dir');
|
|
3482
|
+
if (res && res.error) {
|
|
3483
|
+
this.showMessage(res.error, 'error');
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
if (!res || res.success !== true || !res.fileName) {
|
|
3487
|
+
this.showMessage('备份失败', 'error');
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
this.codexDownloadProgress = 100;
|
|
3491
|
+
const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
|
|
3492
|
+
const link = document.createElement('a');
|
|
3493
|
+
link.href = downloadUrl;
|
|
3494
|
+
link.download = res.fileName;
|
|
3495
|
+
document.body.appendChild(link);
|
|
3496
|
+
link.click();
|
|
3497
|
+
document.body.removeChild(link);
|
|
3498
|
+
this.showMessage('备份成功,开始下载', 'success');
|
|
3499
|
+
} catch (e) {
|
|
3500
|
+
this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
3501
|
+
} finally {
|
|
3502
|
+
if (this.codexDownloadTimer) {
|
|
3503
|
+
clearInterval(this.codexDownloadTimer);
|
|
3504
|
+
this.codexDownloadTimer = null;
|
|
3505
|
+
}
|
|
3506
|
+
this.codexDownloadLoading = false;
|
|
3507
|
+
setTimeout(() => {
|
|
3508
|
+
this.codexDownloadProgress = 0;
|
|
3509
|
+
}, 800);
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
|
|
3513
|
+
triggerClaudeImport() {
|
|
3514
|
+
const input = this.$refs.claudeImportInput;
|
|
3515
|
+
if (input) {
|
|
3516
|
+
input.value = '';
|
|
3517
|
+
input.click();
|
|
3518
|
+
}
|
|
3519
|
+
},
|
|
3520
|
+
|
|
3521
|
+
triggerCodexImport() {
|
|
3522
|
+
const input = this.$refs.codexImportInput;
|
|
3523
|
+
if (input) {
|
|
3524
|
+
input.value = '';
|
|
3525
|
+
input.click();
|
|
3526
|
+
}
|
|
3527
|
+
},
|
|
3528
|
+
|
|
3529
|
+
handleClaudeImportChange(event) {
|
|
3530
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
3531
|
+
if (file) {
|
|
3532
|
+
void this.importBackupFile('claude', file);
|
|
3533
|
+
}
|
|
3534
|
+
},
|
|
3535
|
+
|
|
3536
|
+
handleCodexImportChange(event) {
|
|
3537
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
3538
|
+
if (file) {
|
|
3539
|
+
void this.importBackupFile('codex', file);
|
|
3540
|
+
}
|
|
3541
|
+
},
|
|
3542
|
+
|
|
3543
|
+
async importBackupFile(type, file) {
|
|
3544
|
+
const maxSize = 200 * 1024 * 1024;
|
|
3545
|
+
const loadingKey = type === 'claude' ? 'claudeImportLoading' : 'codexImportLoading';
|
|
3546
|
+
if (file.size > maxSize) {
|
|
3547
|
+
this.showMessage('备份文件过大,限制 200MB', 'error');
|
|
3548
|
+
this.resetImportInput(type);
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
this[loadingKey] = true;
|
|
3552
|
+
try {
|
|
3553
|
+
const base64 = await this.readFileAsBase64(file);
|
|
3554
|
+
const action = type === 'claude' ? 'restore-claude-dir' : 'restore-codex-dir';
|
|
3555
|
+
const res = await api(action, {
|
|
3556
|
+
fileName: file.name || `${type}-backup.zip`,
|
|
3557
|
+
fileBase64: base64
|
|
3558
|
+
});
|
|
3559
|
+
if (res && res.error) {
|
|
3560
|
+
this.showMessage(res.error, 'error');
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
const backupTip = res && res.backupPath ? `,原配置已备份到临时文件:${res.backupPath}` : '';
|
|
3564
|
+
this.showMessage(`导入成功${backupTip}`, 'success');
|
|
3565
|
+
if (type === 'claude') {
|
|
3566
|
+
await this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
3567
|
+
} else {
|
|
3568
|
+
await this.loadAll();
|
|
3569
|
+
}
|
|
3570
|
+
} catch (e) {
|
|
3571
|
+
this.showMessage('导入失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
3572
|
+
} finally {
|
|
3573
|
+
this[loadingKey] = false;
|
|
3574
|
+
this.resetImportInput(type);
|
|
3575
|
+
}
|
|
3576
|
+
},
|
|
3577
|
+
|
|
3578
|
+
readFileAsBase64(file) {
|
|
3579
|
+
return new Promise((resolve, reject) => {
|
|
3580
|
+
const reader = new FileReader();
|
|
3581
|
+
reader.onload = () => {
|
|
3582
|
+
const result = reader.result;
|
|
3583
|
+
if (result instanceof ArrayBuffer) {
|
|
3584
|
+
resolve(this.arrayBufferToBase64(result));
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
if (typeof result === 'string') {
|
|
3588
|
+
const idx = result.indexOf('base64,');
|
|
3589
|
+
resolve(idx >= 0 ? result.slice(idx + 7) : result);
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
reject(new Error('不支持的文件读取结果'));
|
|
3593
|
+
};
|
|
3594
|
+
reader.onerror = () => reject(new Error('读取文件失败'));
|
|
3595
|
+
reader.readAsArrayBuffer(file);
|
|
3596
|
+
});
|
|
3597
|
+
},
|
|
3598
|
+
|
|
3599
|
+
arrayBufferToBase64(buffer) {
|
|
3600
|
+
const bytes = new Uint8Array(buffer);
|
|
3601
|
+
const chunkSize = 0x8000;
|
|
3602
|
+
let binary = '';
|
|
3603
|
+
for (let i = 0; i < bytes.byteLength; i += chunkSize) {
|
|
3604
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
3605
|
+
}
|
|
3606
|
+
return btoa(binary);
|
|
3607
|
+
},
|
|
3608
|
+
|
|
3609
|
+
resetImportInput(type) {
|
|
3610
|
+
const refName = type === 'claude' ? 'claudeImportInput' : 'codexImportInput';
|
|
3611
|
+
const el = this.$refs[refName];
|
|
3612
|
+
if (el) {
|
|
3613
|
+
el.value = '';
|
|
3614
|
+
}
|
|
3615
|
+
},
|
|
3616
|
+
|
|
3617
|
+
async loadCodexAuthProfiles(options = {}) {
|
|
3618
|
+
const silent = !!options.silent;
|
|
3619
|
+
try {
|
|
3620
|
+
const res = await api('list-auth-profiles');
|
|
3621
|
+
if (res && res.error) {
|
|
3622
|
+
if (!silent) {
|
|
3623
|
+
this.showMessage(res.error, 'error');
|
|
3624
|
+
}
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
const list = Array.isArray(res && res.profiles) ? res.profiles : [];
|
|
3628
|
+
this.codexAuthProfiles = list.sort((a, b) => {
|
|
3629
|
+
if (!!a.current !== !!b.current) {
|
|
3630
|
+
return a.current ? -1 : 1;
|
|
3631
|
+
}
|
|
3632
|
+
return String(a.name || '').localeCompare(String(b.name || ''));
|
|
3633
|
+
});
|
|
3634
|
+
} catch (e) {
|
|
3635
|
+
if (!silent) {
|
|
3636
|
+
this.showMessage('读取认证列表失败', 'error');
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
},
|
|
3640
|
+
|
|
3641
|
+
triggerCodexAuthUpload() {
|
|
3642
|
+
const input = this.$refs.codexAuthImportInput;
|
|
3643
|
+
if (input) {
|
|
3644
|
+
input.value = '';
|
|
3645
|
+
input.click();
|
|
3646
|
+
}
|
|
3647
|
+
},
|
|
3648
|
+
|
|
3649
|
+
handleCodexAuthImportChange(event) {
|
|
3650
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
3651
|
+
if (file) {
|
|
3652
|
+
void this.importCodexAuthFile(file);
|
|
3653
|
+
}
|
|
3654
|
+
},
|
|
3655
|
+
|
|
3656
|
+
resetCodexAuthImportInput() {
|
|
3657
|
+
const el = this.$refs.codexAuthImportInput;
|
|
3658
|
+
if (el) {
|
|
3659
|
+
el.value = '';
|
|
3660
|
+
}
|
|
3661
|
+
},
|
|
3662
|
+
|
|
3663
|
+
async importCodexAuthFile(file) {
|
|
3664
|
+
this.codexAuthImportLoading = true;
|
|
3665
|
+
try {
|
|
3666
|
+
const base64 = await this.readFileAsBase64(file);
|
|
3667
|
+
const res = await api('import-auth-profile', {
|
|
3668
|
+
fileName: file.name || 'codex-auth.json',
|
|
3669
|
+
fileBase64: base64,
|
|
3670
|
+
activate: true
|
|
3671
|
+
});
|
|
3672
|
+
if (res && res.error) {
|
|
3673
|
+
this.showMessage(res.error, 'error');
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
3677
|
+
this.showMessage('认证文件已导入并切换', 'success');
|
|
3678
|
+
} catch (e) {
|
|
3679
|
+
this.showMessage('导入认证文件失败', 'error');
|
|
3680
|
+
} finally {
|
|
3681
|
+
this.codexAuthImportLoading = false;
|
|
3682
|
+
this.resetCodexAuthImportInput();
|
|
3683
|
+
}
|
|
3684
|
+
},
|
|
3685
|
+
|
|
3686
|
+
async switchCodexAuthProfile(name) {
|
|
3687
|
+
const key = String(name || '').trim();
|
|
3688
|
+
if (!key || this.codexAuthSwitching[key]) return;
|
|
3689
|
+
this.codexAuthSwitching[key] = true;
|
|
3690
|
+
try {
|
|
3691
|
+
const res = await api('switch-auth-profile', { name: key });
|
|
3692
|
+
if (res && res.error) {
|
|
3693
|
+
this.showMessage(res.error, 'error');
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
3697
|
+
this.showMessage(`已切换认证: ${key}`, 'success');
|
|
3698
|
+
} catch (e) {
|
|
3699
|
+
this.showMessage('切换认证失败', 'error');
|
|
3700
|
+
} finally {
|
|
3701
|
+
this.codexAuthSwitching[key] = false;
|
|
3702
|
+
}
|
|
3703
|
+
},
|
|
3704
|
+
|
|
3705
|
+
async deleteCodexAuthProfile(name) {
|
|
3706
|
+
const key = String(name || '').trim();
|
|
3707
|
+
if (!key || this.codexAuthDeleting[key]) return;
|
|
3708
|
+
this.codexAuthDeleting[key] = true;
|
|
3709
|
+
try {
|
|
3710
|
+
const res = await api('delete-auth-profile', { name: key });
|
|
3711
|
+
if (res && res.error) {
|
|
3712
|
+
this.showMessage(res.error, 'error');
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
3716
|
+
const switchedTip = res && res.switchedTo ? `,已切换到 ${res.switchedTo}` : '';
|
|
3717
|
+
this.showMessage(`已删除认证${switchedTip}`, 'success');
|
|
3718
|
+
} catch (e) {
|
|
3719
|
+
this.showMessage('删除认证失败', 'error');
|
|
3720
|
+
} finally {
|
|
3721
|
+
this.codexAuthDeleting[key] = false;
|
|
3722
|
+
}
|
|
3723
|
+
},
|
|
3724
|
+
|
|
3725
|
+
mergeProxySettings(nextSettings) {
|
|
3726
|
+
const safe = nextSettings && typeof nextSettings === 'object' ? nextSettings : {};
|
|
3727
|
+
const port = parseInt(String(safe.port), 10);
|
|
3728
|
+
const timeoutMs = parseInt(String(safe.timeoutMs), 10);
|
|
3729
|
+
this.proxySettings = {
|
|
3730
|
+
enabled: safe.enabled !== false,
|
|
3731
|
+
host: typeof safe.host === 'string' && safe.host.trim() ? safe.host.trim() : '127.0.0.1',
|
|
3732
|
+
port: Number.isFinite(port) ? port : 8318,
|
|
3733
|
+
provider: typeof safe.provider === 'string' ? safe.provider.trim() : '',
|
|
3734
|
+
authSource: safe.authSource === 'profile' || safe.authSource === 'none' ? safe.authSource : 'provider',
|
|
3735
|
+
timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 30000
|
|
3736
|
+
};
|
|
3737
|
+
},
|
|
3738
|
+
|
|
3739
|
+
async loadProxyStatus(options = {}) {
|
|
3740
|
+
const silent = !!options.silent;
|
|
3741
|
+
this.proxyLoading = true;
|
|
3742
|
+
try {
|
|
3743
|
+
const res = await api('proxy-status');
|
|
3744
|
+
if (res && res.error) {
|
|
3745
|
+
if (!silent) {
|
|
3746
|
+
this.showMessage(res.error, 'error');
|
|
3747
|
+
}
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
this.mergeProxySettings(res && res.settings ? res.settings : {});
|
|
3751
|
+
this.proxyRuntime = res && res.runtime ? { running: true, ...res.runtime } : null;
|
|
3752
|
+
} catch (e) {
|
|
3753
|
+
if (!silent) {
|
|
3754
|
+
this.showMessage('读取代理状态失败', 'error');
|
|
3755
|
+
}
|
|
3756
|
+
} finally {
|
|
3757
|
+
this.proxyLoading = false;
|
|
3758
|
+
}
|
|
3759
|
+
},
|
|
3760
|
+
|
|
3761
|
+
async saveProxySettings(options = {}) {
|
|
3762
|
+
const silent = !!options.silent;
|
|
3763
|
+
this.proxySaving = true;
|
|
3764
|
+
try {
|
|
3765
|
+
const res = await api('proxy-save-config', this.proxySettings);
|
|
3766
|
+
if (res && res.error) {
|
|
3767
|
+
if (!silent) {
|
|
3768
|
+
this.showMessage(res.error, 'error');
|
|
3769
|
+
}
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
if (res && res.settings) {
|
|
3773
|
+
this.mergeProxySettings(res.settings);
|
|
3774
|
+
}
|
|
3775
|
+
if (!silent) {
|
|
3776
|
+
this.showMessage('代理配置已保存', 'success');
|
|
3777
|
+
}
|
|
3778
|
+
} catch (e) {
|
|
3779
|
+
if (!silent) {
|
|
3780
|
+
this.showMessage('保存代理配置失败', 'error');
|
|
3781
|
+
}
|
|
3782
|
+
} finally {
|
|
3783
|
+
this.proxySaving = false;
|
|
3784
|
+
}
|
|
3785
|
+
},
|
|
3786
|
+
|
|
3787
|
+
async startBuiltinProxy() {
|
|
3788
|
+
this.proxyStarting = true;
|
|
3789
|
+
try {
|
|
3790
|
+
const res = await api('proxy-start', {
|
|
3791
|
+
...this.proxySettings,
|
|
3792
|
+
enabled: true
|
|
3793
|
+
});
|
|
3794
|
+
if (res && res.error) {
|
|
3795
|
+
this.showMessage(res.error, 'error');
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3798
|
+
if (res && res.settings) {
|
|
3799
|
+
this.mergeProxySettings(res.settings);
|
|
3800
|
+
}
|
|
3801
|
+
await this.loadProxyStatus({ silent: true });
|
|
3802
|
+
const listenTip = res && res.listenUrl ? `:${res.listenUrl}` : '';
|
|
3803
|
+
this.showMessage(`代理已启动${listenTip}`, 'success');
|
|
3804
|
+
} catch (e) {
|
|
3805
|
+
this.showMessage('启动代理失败', 'error');
|
|
3806
|
+
} finally {
|
|
3807
|
+
this.proxyStarting = false;
|
|
3808
|
+
}
|
|
3809
|
+
},
|
|
3810
|
+
|
|
3811
|
+
async stopBuiltinProxy() {
|
|
3812
|
+
this.proxyStopping = true;
|
|
3813
|
+
try {
|
|
3814
|
+
const res = await api('proxy-stop');
|
|
3815
|
+
if (res && res.error) {
|
|
3816
|
+
this.showMessage(res.error, 'error');
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
await this.loadProxyStatus({ silent: true });
|
|
3820
|
+
this.showMessage('代理已停止', 'success');
|
|
3821
|
+
} catch (e) {
|
|
3822
|
+
this.showMessage('停止代理失败', 'error');
|
|
3823
|
+
} finally {
|
|
3824
|
+
this.proxyStopping = false;
|
|
3825
|
+
}
|
|
3826
|
+
},
|
|
3827
|
+
|
|
3828
|
+
async applyBuiltinProxyProvider() {
|
|
3829
|
+
this.proxyApplying = true;
|
|
3830
|
+
try {
|
|
3831
|
+
const saveRes = await api('proxy-save-config', this.proxySettings);
|
|
3832
|
+
if (saveRes && saveRes.error) {
|
|
3833
|
+
this.showMessage(saveRes.error, 'error');
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
const res = await api('proxy-apply-provider', { switchToProxy: true });
|
|
3837
|
+
if (res && res.error) {
|
|
3838
|
+
this.showMessage(res.error, 'error');
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
await this.loadAll();
|
|
3842
|
+
this.showMessage('本地代理 provider 已写入并切换', 'success');
|
|
3843
|
+
} catch (e) {
|
|
3844
|
+
this.showMessage('应用代理 provider 失败', 'error');
|
|
3845
|
+
} finally {
|
|
3846
|
+
this.proxyApplying = false;
|
|
3847
|
+
}
|
|
3848
|
+
},
|
|
3849
|
+
|
|
2829
3850
|
showMessage(text, type) {
|
|
2830
3851
|
this.message = text;
|
|
2831
3852
|
this.messageType = type || 'info';
|
|
@@ -2839,3 +3860,4 @@
|
|
|
2839
3860
|
app.mount('#app');
|
|
2840
3861
|
});
|
|
2841
3862
|
|
|
3863
|
+
|