codexmate 0.0.12 → 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 +24 -2
- package/README.zh-CN.md +24 -2
- package/{src/cli.js → cli.js} +2689 -256
- package/doc/CHANGELOG.md +6 -0
- package/doc/CHANGELOG.zh-CN.md +6 -0
- package/lib/mcp-stdio.js +440 -0
- package/package.json +56 -53
- package/web-ui/app.js +903 -10
- package/web-ui/index.html +350 -55
- package/web-ui/styles.css +394 -49
- package/src/lib/cli-file-utils.js +0 -151
- package/src/lib/cli-models-utils.js +0 -152
- package/src/lib/cli-network-utils.js +0 -148
- package/src/lib/cli-session-utils.js +0 -121
- package/src/lib/cli-utils.js +0 -139
- package/src/res/json5.min.js +0 -1
- package/src/res/logo.png +0 -0
- package/src/res/screenshot.png +0 -0
- package/src/res/vue.global.js +0 -18552
- package/src/web-ui/app.js +0 -2970
- package/src/web-ui/index.html +0 -1310
- package/src/web-ui/logic.mjs +0 -157
- package/src/web-ui/styles.css +0 -2868
- /package/{src/web-ui.html → web-ui.html} +0 -0
package/web-ui/app.js
CHANGED
|
@@ -137,13 +137,35 @@
|
|
|
137
137
|
claudeSpeedLoading: {},
|
|
138
138
|
claudeShareLoading: {},
|
|
139
139
|
providerShareLoading: {},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
165
|
],
|
|
144
166
|
newProvider: { name: '', url: '', key: '' },
|
|
145
167
|
resetConfigLoading: false,
|
|
146
|
-
editingProvider: { name: '', url: '', key: '' },
|
|
168
|
+
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
147
169
|
newModelName: '',
|
|
148
170
|
currentClaudeConfig: '',
|
|
149
171
|
currentClaudeModel: '',
|
|
@@ -209,7 +231,34 @@
|
|
|
209
231
|
openclawMissingProviders: [],
|
|
210
232
|
healthCheckLoading: false,
|
|
211
233
|
healthCheckResult: null,
|
|
212
|
-
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
|
|
213
262
|
}
|
|
214
263
|
},
|
|
215
264
|
mounted() {
|
|
@@ -276,6 +325,162 @@
|
|
|
276
325
|
list.unshift(current);
|
|
277
326
|
}
|
|
278
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
|
+
];
|
|
279
484
|
}
|
|
280
485
|
},
|
|
281
486
|
methods: {
|
|
@@ -323,6 +528,15 @@
|
|
|
323
528
|
} catch (e) {
|
|
324
529
|
// loadModelsForProvider 内部已有 toast,这里吞掉防止抛出
|
|
325
530
|
}
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
await Promise.all([
|
|
534
|
+
this.loadCodexAuthProfiles(),
|
|
535
|
+
this.loadProxyStatus()
|
|
536
|
+
]);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
// 认证/代理状态加载失败不阻塞主界面
|
|
539
|
+
}
|
|
326
540
|
},
|
|
327
541
|
|
|
328
542
|
async loadModelsForProvider(providerName) {
|
|
@@ -704,12 +918,39 @@
|
|
|
704
918
|
this.showMessage('复制失败', 'error');
|
|
705
919
|
},
|
|
706
920
|
|
|
707
|
-
|
|
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) {
|
|
708
940
|
const text = typeof cmd === 'string' ? cmd.trim() : '';
|
|
709
941
|
if (!text) {
|
|
710
942
|
this.showMessage('没有可复制内容', 'info');
|
|
711
943
|
return;
|
|
712
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
|
+
}
|
|
713
954
|
const ok = this.fallbackCopyText(text);
|
|
714
955
|
if (ok) {
|
|
715
956
|
this.showMessage('已复制命令', 'success');
|
|
@@ -778,6 +1019,10 @@
|
|
|
778
1019
|
this.showMessage('参数无效', 'error');
|
|
779
1020
|
return;
|
|
780
1021
|
}
|
|
1022
|
+
if (!this.shouldAllowProviderShare(provider)) {
|
|
1023
|
+
this.showMessage('本地入口不可分享', 'info');
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
781
1026
|
if (this.providerShareLoading[name]) {
|
|
782
1027
|
return;
|
|
783
1028
|
}
|
|
@@ -1256,10 +1501,10 @@
|
|
|
1256
1501
|
}
|
|
1257
1502
|
},
|
|
1258
1503
|
|
|
1259
|
-
downloadTextFile(fileName, content) {
|
|
1504
|
+
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
1260
1505
|
// 使用 UTF-8 BOM 确保文本编辑器正确识别编码
|
|
1261
1506
|
const BOM = '\uFEFF';
|
|
1262
|
-
const blob = new Blob([BOM + content], { type:
|
|
1507
|
+
const blob = new Blob([BOM + content], { type: mimeType });
|
|
1263
1508
|
const url = URL.createObjectURL(blob);
|
|
1264
1509
|
const link = document.createElement('a');
|
|
1265
1510
|
link.href = url;
|
|
@@ -1632,6 +1877,9 @@
|
|
|
1632
1877
|
if (!name) {
|
|
1633
1878
|
return this.showMessage('名称不能为空', 'error');
|
|
1634
1879
|
}
|
|
1880
|
+
if (name.toLowerCase() === 'local') {
|
|
1881
|
+
return this.showMessage('local provider 为系统保留名称,不可新增', 'error');
|
|
1882
|
+
}
|
|
1635
1883
|
if (this.providersList.some(item => item.name === name)) {
|
|
1636
1884
|
return this.showMessage('名称已存在', 'error');
|
|
1637
1885
|
}
|
|
@@ -1655,7 +1903,89 @@
|
|
|
1655
1903
|
}
|
|
1656
1904
|
},
|
|
1657
1905
|
|
|
1906
|
+
getCurrentCodexAuthProfile() {
|
|
1907
|
+
const list = Array.isArray(this.codexAuthProfiles) ? this.codexAuthProfiles : [];
|
|
1908
|
+
return list.find((item) => !!(item && item.current)) || null;
|
|
1909
|
+
},
|
|
1910
|
+
|
|
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);
|
|
1982
|
+
},
|
|
1983
|
+
|
|
1658
1984
|
async deleteProvider(name) {
|
|
1985
|
+
if (this.isNonDeletableProvider(name)) {
|
|
1986
|
+
this.showMessage('该 provider 为保留项,不可删除', 'info');
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1659
1989
|
const res = await api('delete-provider', { name });
|
|
1660
1990
|
if (res.error) {
|
|
1661
1991
|
this.showMessage(res.error, 'error');
|
|
@@ -1670,15 +2000,26 @@
|
|
|
1670
2000
|
},
|
|
1671
2001
|
|
|
1672
2002
|
openEditModal(provider) {
|
|
2003
|
+
if (!this.shouldShowProviderEdit(provider)) {
|
|
2004
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'info');
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
1673
2007
|
this.editingProvider = {
|
|
1674
2008
|
name: provider.name,
|
|
1675
2009
|
url: provider.url || '',
|
|
1676
|
-
key: ''
|
|
2010
|
+
key: '',
|
|
2011
|
+
readOnly: !!provider.readOnly,
|
|
2012
|
+
nonEditable: this.isNonDeletableProvider(provider)
|
|
1677
2013
|
};
|
|
1678
2014
|
this.showEditModal = true;
|
|
1679
2015
|
},
|
|
1680
2016
|
|
|
1681
2017
|
async updateProvider() {
|
|
2018
|
+
if (this.editingProvider.readOnly || this.editingProvider.nonEditable) {
|
|
2019
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'error');
|
|
2020
|
+
this.closeEditModal();
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
1682
2023
|
if (!this.editingProvider.url) {
|
|
1683
2024
|
return this.showMessage('URL 必填', 'error');
|
|
1684
2025
|
}
|
|
@@ -1702,7 +2043,7 @@
|
|
|
1702
2043
|
|
|
1703
2044
|
closeEditModal() {
|
|
1704
2045
|
this.showEditModal = false;
|
|
1705
|
-
this.editingProvider = { name: '', url: '', key: '' };
|
|
2046
|
+
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false };
|
|
1706
2047
|
},
|
|
1707
2048
|
|
|
1708
2049
|
async resetConfig() {
|
|
@@ -2751,6 +3092,136 @@
|
|
|
2751
3092
|
this.resetOpenclawQuick();
|
|
2752
3093
|
},
|
|
2753
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
|
+
|
|
2754
3225
|
openInstallModal() {
|
|
2755
3226
|
this.showInstallModal = true;
|
|
2756
3227
|
},
|
|
@@ -2955,6 +3426,427 @@
|
|
|
2955
3426
|
}
|
|
2956
3427
|
},
|
|
2957
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
|
+
|
|
2958
3850
|
showMessage(text, type) {
|
|
2959
3851
|
this.message = text;
|
|
2960
3852
|
this.messageType = type || 'info';
|
|
@@ -2968,3 +3860,4 @@
|
|
|
2968
3860
|
app.mount('#app');
|
|
2969
3861
|
});
|
|
2970
3862
|
|
|
3863
|
+
|