codexmate 0.0.12 → 0.0.14
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.en.md +429 -0
- package/README.md +241 -203
- package/cli.js +10210 -0
- package/doc/CHANGELOG.md +14 -1
- package/doc/CHANGELOG.zh-CN.md +13 -0
- package/lib/cli-utils.js +16 -0
- package/lib/mcp-stdio.js +440 -0
- package/lib/workflow-engine.js +340 -0
- package/package.json +63 -53
- package/web-ui/app.js +1417 -14
- package/web-ui/index.html +585 -67
- package/web-ui/logic.mjs +147 -1
- package/web-ui/styles.css +1049 -66
- package/README.zh-CN.md +0 -397
- package/src/cli.js +0 -5464
- 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
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
formatLatency,
|
|
8
8
|
buildSpeedTestIssue,
|
|
9
9
|
isSessionQueryEnabled,
|
|
10
|
-
buildSessionListParams
|
|
10
|
+
buildSessionListParams,
|
|
11
|
+
normalizeSessionSource,
|
|
12
|
+
normalizeSessionPathFilter,
|
|
13
|
+
buildSessionFilterCacheState,
|
|
14
|
+
buildSessionTimelineNodes,
|
|
15
|
+
normalizeSessionMessageRole
|
|
11
16
|
} from './logic.mjs';
|
|
12
17
|
|
|
13
18
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -81,6 +86,7 @@
|
|
|
81
86
|
showOpenclawConfigModal: false,
|
|
82
87
|
showConfigTemplateModal: false,
|
|
83
88
|
showAgentsModal: false,
|
|
89
|
+
showSkillsModal: false,
|
|
84
90
|
showInstallModal: false,
|
|
85
91
|
configTemplateContent: '',
|
|
86
92
|
configTemplateApplying: false,
|
|
@@ -94,6 +100,17 @@
|
|
|
94
100
|
agentsContext: 'codex',
|
|
95
101
|
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
96
102
|
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
103
|
+
skillsRootPath: '',
|
|
104
|
+
skillsList: [],
|
|
105
|
+
skillsSelectedNames: [],
|
|
106
|
+
skillsLoading: false,
|
|
107
|
+
skillsDeleting: false,
|
|
108
|
+
skillsKeyword: '',
|
|
109
|
+
skillsStatusFilter: 'all',
|
|
110
|
+
skillsImportList: [],
|
|
111
|
+
skillsImportSelectedKeys: [],
|
|
112
|
+
skillsScanningImports: false,
|
|
113
|
+
skillsImporting: false,
|
|
97
114
|
sessionsList: [],
|
|
98
115
|
sessionsLoading: false,
|
|
99
116
|
sessionFilterSource: 'all',
|
|
@@ -124,6 +141,13 @@
|
|
|
124
141
|
activeSessionDetailClipped: false,
|
|
125
142
|
sessionDetailLoading: false,
|
|
126
143
|
sessionDetailRequestSeq: 0,
|
|
144
|
+
sessionTimelineActiveKey: '',
|
|
145
|
+
sessionTimelineRafId: 0,
|
|
146
|
+
sessionMessageRefMap: Object.create(null),
|
|
147
|
+
sessionPreviewScrollEl: null,
|
|
148
|
+
sessionPreviewContainerEl: null,
|
|
149
|
+
sessionPreviewHeaderEl: null,
|
|
150
|
+
sessionPreviewHeaderResizeObserver: null,
|
|
127
151
|
sessionStandalone: false,
|
|
128
152
|
sessionStandaloneError: '',
|
|
129
153
|
sessionStandaloneText: '',
|
|
@@ -137,13 +161,35 @@
|
|
|
137
161
|
claudeSpeedLoading: {},
|
|
138
162
|
claudeShareLoading: {},
|
|
139
163
|
providerShareLoading: {},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
installPackageManager: 'npm',
|
|
165
|
+
installCommandAction: 'install',
|
|
166
|
+
installRegistryPreset: 'default',
|
|
167
|
+
installRegistryCustom: '',
|
|
168
|
+
installStatusTargets: [
|
|
169
|
+
{
|
|
170
|
+
id: 'claude',
|
|
171
|
+
name: 'Claude Code CLI',
|
|
172
|
+
packageName: '@anthropic-ai/claude-code',
|
|
173
|
+
installed: false,
|
|
174
|
+
bin: 'claude',
|
|
175
|
+
version: '',
|
|
176
|
+
commandPath: '',
|
|
177
|
+
error: ''
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'codex',
|
|
181
|
+
name: 'Codex CLI',
|
|
182
|
+
packageName: '@openai/codex',
|
|
183
|
+
installed: false,
|
|
184
|
+
bin: 'codex',
|
|
185
|
+
version: '',
|
|
186
|
+
commandPath: '',
|
|
187
|
+
error: ''
|
|
188
|
+
}
|
|
143
189
|
],
|
|
144
190
|
newProvider: { name: '', url: '', key: '' },
|
|
145
191
|
resetConfigLoading: false,
|
|
146
|
-
editingProvider: { name: '', url: '', key: '' },
|
|
192
|
+
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
147
193
|
newModelName: '',
|
|
148
194
|
currentClaudeConfig: '',
|
|
149
195
|
currentClaudeModel: '',
|
|
@@ -209,7 +255,34 @@
|
|
|
209
255
|
openclawMissingProviders: [],
|
|
210
256
|
healthCheckLoading: false,
|
|
211
257
|
healthCheckResult: null,
|
|
212
|
-
healthCheckRemote: false
|
|
258
|
+
healthCheckRemote: false,
|
|
259
|
+
claudeDownloadLoading: false,
|
|
260
|
+
claudeDownloadProgress: 0,
|
|
261
|
+
claudeDownloadTimer: null,
|
|
262
|
+
codexDownloadLoading: false,
|
|
263
|
+
codexDownloadProgress: 0,
|
|
264
|
+
codexDownloadTimer: null,
|
|
265
|
+
claudeImportLoading: false,
|
|
266
|
+
codexImportLoading: false,
|
|
267
|
+
codexAuthProfiles: [],
|
|
268
|
+
codexAuthImportLoading: false,
|
|
269
|
+
codexAuthSwitching: {},
|
|
270
|
+
codexAuthDeleting: {},
|
|
271
|
+
proxySettings: {
|
|
272
|
+
enabled: false,
|
|
273
|
+
host: '127.0.0.1',
|
|
274
|
+
port: 8318,
|
|
275
|
+
provider: '',
|
|
276
|
+
authSource: 'provider',
|
|
277
|
+
timeoutMs: 30000
|
|
278
|
+
},
|
|
279
|
+
proxyRuntime: null,
|
|
280
|
+
proxyLoading: false,
|
|
281
|
+
proxySaving: false,
|
|
282
|
+
proxyStarting: false,
|
|
283
|
+
proxyStopping: false,
|
|
284
|
+
proxyApplying: false,
|
|
285
|
+
showProxyAdvanced: false
|
|
213
286
|
}
|
|
214
287
|
},
|
|
215
288
|
mounted() {
|
|
@@ -220,6 +293,8 @@
|
|
|
220
293
|
} else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
|
|
221
294
|
this.sessionResumeWithYolo = true;
|
|
222
295
|
}
|
|
296
|
+
this.restoreSessionFilterCache();
|
|
297
|
+
window.addEventListener('resize', this.onWindowResize);
|
|
223
298
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
224
299
|
if (savedConfigs) {
|
|
225
300
|
try {
|
|
@@ -255,11 +330,31 @@
|
|
|
255
330
|
}
|
|
256
331
|
this.loadAll();
|
|
257
332
|
},
|
|
333
|
+
beforeUnmount() {
|
|
334
|
+
this.cancelSessionTimelineSync();
|
|
335
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
336
|
+
window.removeEventListener('resize', this.onWindowResize);
|
|
337
|
+
this.sessionPreviewScrollEl = null;
|
|
338
|
+
this.sessionPreviewContainerEl = null;
|
|
339
|
+
this.sessionPreviewHeaderEl = null;
|
|
340
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
341
|
+
},
|
|
258
342
|
|
|
259
343
|
computed: {
|
|
260
344
|
isSessionQueryEnabled() {
|
|
261
345
|
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
262
346
|
},
|
|
347
|
+
sessionTimelineNodes() {
|
|
348
|
+
return buildSessionTimelineNodes(this.activeSessionMessages, {
|
|
349
|
+
getKey: (message, index) => this.getRecordRenderKey(message, index)
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
sessionTimelineActiveTitle() {
|
|
353
|
+
if (!this.sessionTimelineActiveKey) return '';
|
|
354
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
355
|
+
const matched = nodes.find(node => node.key === this.sessionTimelineActiveKey);
|
|
356
|
+
return matched ? matched.title : '';
|
|
357
|
+
},
|
|
263
358
|
sessionQueryPlaceholder() {
|
|
264
359
|
if (this.isSessionQueryEnabled) {
|
|
265
360
|
return '关键词检索(支持 Codex/Claude,例:claude code)';
|
|
@@ -276,6 +371,241 @@
|
|
|
276
371
|
list.unshift(current);
|
|
277
372
|
}
|
|
278
373
|
return list;
|
|
374
|
+
},
|
|
375
|
+
proxyProviderOptions() {
|
|
376
|
+
const source = Array.isArray(this.providersList) ? this.providersList : [];
|
|
377
|
+
const list = source
|
|
378
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
379
|
+
.filter((name) => name && name !== 'codexmate-proxy');
|
|
380
|
+
return Array.from(new Set(list));
|
|
381
|
+
},
|
|
382
|
+
proxyRuntimeDisplayProvider() {
|
|
383
|
+
if (!this.proxyRuntime) return '';
|
|
384
|
+
const value = typeof this.proxyRuntime.provider === 'string'
|
|
385
|
+
? this.proxyRuntime.provider.trim()
|
|
386
|
+
: '';
|
|
387
|
+
return value || 'local';
|
|
388
|
+
},
|
|
389
|
+
installTargetCards() {
|
|
390
|
+
const targets = Array.isArray(this.installStatusTargets) ? this.installStatusTargets : [];
|
|
391
|
+
const action = this.normalizeInstallAction(this.installCommandAction);
|
|
392
|
+
return targets.map((target) => {
|
|
393
|
+
const id = target && typeof target.id === 'string' ? target.id : '';
|
|
394
|
+
return {
|
|
395
|
+
...target,
|
|
396
|
+
command: this.getInstallCommand(id, action)
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
installRegistryPreview() {
|
|
401
|
+
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
402
|
+
},
|
|
403
|
+
filteredSkillsList() {
|
|
404
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
405
|
+
const keyword = typeof this.skillsKeyword === 'string' ? this.skillsKeyword.trim().toLowerCase() : '';
|
|
406
|
+
const status = typeof this.skillsStatusFilter === 'string' ? this.skillsStatusFilter : 'all';
|
|
407
|
+
return list.filter((item) => {
|
|
408
|
+
const safe = item && typeof item === 'object' ? item : {};
|
|
409
|
+
const hasSkillFile = !!safe.hasSkillFile;
|
|
410
|
+
if (status === 'with-skill-file' && !hasSkillFile) return false;
|
|
411
|
+
if (status === 'missing-skill-file' && hasSkillFile) return false;
|
|
412
|
+
if (!keyword) return true;
|
|
413
|
+
const fields = [
|
|
414
|
+
safe.name,
|
|
415
|
+
safe.displayName,
|
|
416
|
+
safe.description,
|
|
417
|
+
safe.path
|
|
418
|
+
];
|
|
419
|
+
return fields.some((value) => typeof value === 'string' && value.toLowerCase().includes(keyword));
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
skillsSelectableNames() {
|
|
423
|
+
const list = Array.isArray(this.filteredSkillsList) ? this.filteredSkillsList : [];
|
|
424
|
+
return list
|
|
425
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
426
|
+
.filter(Boolean);
|
|
427
|
+
},
|
|
428
|
+
skillsConfiguredCount() {
|
|
429
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
430
|
+
return list.filter((item) => !!(item && item.hasSkillFile)).length;
|
|
431
|
+
},
|
|
432
|
+
skillsMissingSkillFileCount() {
|
|
433
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
434
|
+
return list.filter((item) => !(item && item.hasSkillFile)).length;
|
|
435
|
+
},
|
|
436
|
+
skillsFilterDirty() {
|
|
437
|
+
const keyword = typeof this.skillsKeyword === 'string' ? this.skillsKeyword.trim() : '';
|
|
438
|
+
const status = typeof this.skillsStatusFilter === 'string' ? this.skillsStatusFilter : 'all';
|
|
439
|
+
return keyword.length > 0 || status !== 'all';
|
|
440
|
+
},
|
|
441
|
+
skillsSelectedCount() {
|
|
442
|
+
const selected = Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : [];
|
|
443
|
+
return Array.from(new Set(selected.map((item) => String(item || '').trim()).filter(Boolean))).length;
|
|
444
|
+
},
|
|
445
|
+
skillsVisibleSelectedCount() {
|
|
446
|
+
const selectable = this.skillsSelectableNames;
|
|
447
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
448
|
+
return selectable.filter((name) => selectedSet.has(name)).length;
|
|
449
|
+
},
|
|
450
|
+
skillsAllSelected() {
|
|
451
|
+
const selectable = this.skillsSelectableNames;
|
|
452
|
+
if (!selectable.length) return false;
|
|
453
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
454
|
+
return selectable.every((name) => selectedSet.has(name));
|
|
455
|
+
},
|
|
456
|
+
skillsImportSelectableKeys() {
|
|
457
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
458
|
+
return list
|
|
459
|
+
.map((item) => this.buildSkillImportKey(item))
|
|
460
|
+
.filter(Boolean);
|
|
461
|
+
},
|
|
462
|
+
skillsImportSelectedCount() {
|
|
463
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
464
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
465
|
+
return selectable.filter((key) => selectedSet.has(key)).length;
|
|
466
|
+
},
|
|
467
|
+
skillsImportAllSelected() {
|
|
468
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
469
|
+
if (!selectable.length) return false;
|
|
470
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
471
|
+
return selectable.every((key) => selectedSet.has(key));
|
|
472
|
+
},
|
|
473
|
+
skillsImportConfiguredCount() {
|
|
474
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
475
|
+
return list.filter((item) => !!(item && item.hasSkillFile)).length;
|
|
476
|
+
},
|
|
477
|
+
skillsImportMissingSkillFileCount() {
|
|
478
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
479
|
+
return list.filter((item) => !(item && item.hasSkillFile)).length;
|
|
480
|
+
},
|
|
481
|
+
inspectorMainTabLabel() {
|
|
482
|
+
if (this.mainTab === 'config') return '配置中心';
|
|
483
|
+
if (this.mainTab === 'sessions') return '会话浏览';
|
|
484
|
+
if (this.mainTab === 'settings') return '设置';
|
|
485
|
+
return '未知';
|
|
486
|
+
},
|
|
487
|
+
inspectorConfigModeLabel() {
|
|
488
|
+
if (this.mainTab !== 'config') return '--';
|
|
489
|
+
if (this.configMode === 'codex') return 'Codex';
|
|
490
|
+
if (this.configMode === 'claude') return 'Claude Code';
|
|
491
|
+
if (this.configMode === 'openclaw') return 'OpenClaw';
|
|
492
|
+
return '未选择';
|
|
493
|
+
},
|
|
494
|
+
inspectorCurrentConfigLabel() {
|
|
495
|
+
if (this.mainTab !== 'config') return '--';
|
|
496
|
+
if (this.configMode === 'codex') {
|
|
497
|
+
const provider = typeof this.currentProvider === 'string' ? this.currentProvider.trim() : '';
|
|
498
|
+
return provider || '未选择';
|
|
499
|
+
}
|
|
500
|
+
if (this.configMode === 'claude') {
|
|
501
|
+
const config = typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig.trim() : '';
|
|
502
|
+
return config || '未选择';
|
|
503
|
+
}
|
|
504
|
+
const openclaw = typeof this.currentOpenclawConfig === 'string' ? this.currentOpenclawConfig.trim() : '';
|
|
505
|
+
return openclaw || '未选择';
|
|
506
|
+
},
|
|
507
|
+
inspectorCurrentModelLabel() {
|
|
508
|
+
if (this.mainTab !== 'config') return '--';
|
|
509
|
+
if (this.configMode === 'codex') {
|
|
510
|
+
const model = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
|
|
511
|
+
return model || '未选择';
|
|
512
|
+
}
|
|
513
|
+
if (this.configMode === 'claude') {
|
|
514
|
+
const model = typeof this.currentClaudeModel === 'string' ? this.currentClaudeModel.trim() : '';
|
|
515
|
+
return model || '未选择';
|
|
516
|
+
}
|
|
517
|
+
const model = this.openclawStructured && typeof this.openclawStructured.agentPrimary === 'string'
|
|
518
|
+
? this.openclawStructured.agentPrimary.trim()
|
|
519
|
+
: '';
|
|
520
|
+
return model || '按配置文件';
|
|
521
|
+
},
|
|
522
|
+
inspectorTemplateStatus() {
|
|
523
|
+
if (this.mainTab !== 'config') return '--';
|
|
524
|
+
if (this.configMode === 'codex') {
|
|
525
|
+
if (this.configTemplateApplying || this.codexApplying) {
|
|
526
|
+
return '模板应用中';
|
|
527
|
+
}
|
|
528
|
+
return '模板可编辑(手动确认应用)';
|
|
529
|
+
}
|
|
530
|
+
if (this.configMode === 'claude') {
|
|
531
|
+
return '即时写入 Claude settings';
|
|
532
|
+
}
|
|
533
|
+
if (this.openclawApplying || this.openclawSaving) {
|
|
534
|
+
return 'OpenClaw 保存/应用中';
|
|
535
|
+
}
|
|
536
|
+
return 'JSON5 可保存并应用';
|
|
537
|
+
},
|
|
538
|
+
inspectorBusyStatus() {
|
|
539
|
+
const tasks = [];
|
|
540
|
+
if (this.loading) tasks.push('初始化');
|
|
541
|
+
if (this.sessionsLoading) tasks.push('会话加载');
|
|
542
|
+
if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
|
|
543
|
+
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
544
|
+
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
545
|
+
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting) tasks.push('Skills 管理');
|
|
546
|
+
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
|
|
547
|
+
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
548
|
+
},
|
|
549
|
+
inspectorMessageSummary() {
|
|
550
|
+
const value = typeof this.message === 'string' ? this.message.trim() : '';
|
|
551
|
+
return value || '暂无提示';
|
|
552
|
+
},
|
|
553
|
+
inspectorSessionSourceLabel() {
|
|
554
|
+
if (this.sessionFilterSource === 'codex') return 'Codex';
|
|
555
|
+
if (this.sessionFilterSource === 'claude') return 'Claude Code';
|
|
556
|
+
return '全部';
|
|
557
|
+
},
|
|
558
|
+
inspectorSessionPathLabel() {
|
|
559
|
+
const value = typeof this.sessionPathFilter === 'string' ? this.sessionPathFilter.trim() : '';
|
|
560
|
+
return value || '全部路径';
|
|
561
|
+
},
|
|
562
|
+
inspectorSessionQueryLabel() {
|
|
563
|
+
if (!this.isSessionQueryEnabled) return '当前来源不支持';
|
|
564
|
+
const value = typeof this.sessionQuery === 'string' ? this.sessionQuery.trim() : '';
|
|
565
|
+
return value || '未设置';
|
|
566
|
+
},
|
|
567
|
+
inspectorHealthStatus() {
|
|
568
|
+
if (this.initError) return '读取失败';
|
|
569
|
+
if (this.loading) return '初始化中';
|
|
570
|
+
return '正常';
|
|
571
|
+
},
|
|
572
|
+
inspectorHealthTone() {
|
|
573
|
+
if (this.initError) return 'error';
|
|
574
|
+
if (this.loading) return 'warn';
|
|
575
|
+
return 'ok';
|
|
576
|
+
},
|
|
577
|
+
inspectorModelLoadStatus() {
|
|
578
|
+
if (this.codexModelsLoading || this.claudeModelsLoading) {
|
|
579
|
+
return '加载中';
|
|
580
|
+
}
|
|
581
|
+
if (this.modelsSource === 'error' || this.claudeModelsSource === 'error') {
|
|
582
|
+
return '加载异常';
|
|
583
|
+
}
|
|
584
|
+
return '正常';
|
|
585
|
+
},
|
|
586
|
+
inspectorProxyStatus() {
|
|
587
|
+
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) {
|
|
588
|
+
return '状态更新中';
|
|
589
|
+
}
|
|
590
|
+
if (this.proxyRuntime && this.proxyRuntime.running === true) {
|
|
591
|
+
return `运行中(${this.proxyRuntimeDisplayProvider})`;
|
|
592
|
+
}
|
|
593
|
+
return '未运行';
|
|
594
|
+
},
|
|
595
|
+
installTroubleshootingTips() {
|
|
596
|
+
const platform = this.resolveInstallPlatform();
|
|
597
|
+
if (platform === 'win32') {
|
|
598
|
+
return [
|
|
599
|
+
'PowerShell 报权限不足(EACCES/EPERM)时,请以管理员身份执行安装命令。',
|
|
600
|
+
'安装后若仍提示找不到命令,重开终端并执行:where codex / where claude。',
|
|
601
|
+
'公司网络受限时,可先切换镜像源快捷项(npmmirror / 腾讯云 / 自定义)。'
|
|
602
|
+
];
|
|
603
|
+
}
|
|
604
|
+
return [
|
|
605
|
+
'出现 EACCES 权限错误时,优先修复 Node 全局目录权限,不建议直接 sudo npm。',
|
|
606
|
+
'安装后若命令未生效,重开终端并执行:which codex / which claude。',
|
|
607
|
+
'公司网络受限时,可先切换镜像源快捷项(npmmirror / 腾讯云 / 自定义)。'
|
|
608
|
+
];
|
|
279
609
|
}
|
|
280
610
|
},
|
|
281
611
|
methods: {
|
|
@@ -323,6 +653,15 @@
|
|
|
323
653
|
} catch (e) {
|
|
324
654
|
// loadModelsForProvider 内部已有 toast,这里吞掉防止抛出
|
|
325
655
|
}
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
await Promise.all([
|
|
659
|
+
this.loadCodexAuthProfiles(),
|
|
660
|
+
this.loadProxyStatus()
|
|
661
|
+
]);
|
|
662
|
+
} catch (e) {
|
|
663
|
+
// 认证/代理状态加载失败不阻塞主界面
|
|
664
|
+
}
|
|
326
665
|
},
|
|
327
666
|
|
|
328
667
|
async loadModelsForProvider(providerName) {
|
|
@@ -585,6 +924,9 @@
|
|
|
585
924
|
this.activeSessionMessages = [];
|
|
586
925
|
this.activeSessionDetailError = '';
|
|
587
926
|
this.activeSessionDetailClipped = false;
|
|
927
|
+
this.cancelSessionTimelineSync();
|
|
928
|
+
this.sessionTimelineActiveKey = '';
|
|
929
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
588
930
|
this.sessionStandaloneError = '';
|
|
589
931
|
this.sessionStandaloneText = '';
|
|
590
932
|
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
@@ -704,12 +1046,39 @@
|
|
|
704
1046
|
this.showMessage('复制失败', 'error');
|
|
705
1047
|
},
|
|
706
1048
|
|
|
707
|
-
|
|
1049
|
+
exportAgentsContent() {
|
|
1050
|
+
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
1051
|
+
if (!text) {
|
|
1052
|
+
this.showMessage('没有可导出内容', 'info');
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const now = new Date();
|
|
1056
|
+
const year = String(now.getFullYear());
|
|
1057
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
1058
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
1059
|
+
const hour = String(now.getHours()).padStart(2, '0');
|
|
1060
|
+
const minute = String(now.getMinutes()).padStart(2, '0');
|
|
1061
|
+
const second = String(now.getSeconds()).padStart(2, '0');
|
|
1062
|
+
const fileName = `agent-${year}${month}${day}-${hour}${minute}${second}.txt`;
|
|
1063
|
+
this.downloadTextFile(fileName, text, 'text/plain;charset=utf-8');
|
|
1064
|
+
this.showMessage(`已导出 ${fileName}`, 'success');
|
|
1065
|
+
},
|
|
1066
|
+
|
|
1067
|
+
async copyInstallCommand(cmd) {
|
|
708
1068
|
const text = typeof cmd === 'string' ? cmd.trim() : '';
|
|
709
1069
|
if (!text) {
|
|
710
1070
|
this.showMessage('没有可复制内容', 'info');
|
|
711
1071
|
return;
|
|
712
1072
|
}
|
|
1073
|
+
try {
|
|
1074
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
1075
|
+
await navigator.clipboard.writeText(text);
|
|
1076
|
+
this.showMessage('已复制命令', 'success');
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
// fallback to legacy copy path
|
|
1081
|
+
}
|
|
713
1082
|
const ok = this.fallbackCopyText(text);
|
|
714
1083
|
if (ok) {
|
|
715
1084
|
this.showMessage('已复制命令', 'success');
|
|
@@ -778,6 +1147,10 @@
|
|
|
778
1147
|
this.showMessage('参数无效', 'error');
|
|
779
1148
|
return;
|
|
780
1149
|
}
|
|
1150
|
+
if (!this.shouldAllowProviderShare(provider)) {
|
|
1151
|
+
this.showMessage('本地入口不可分享', 'info');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
781
1154
|
if (this.providerShareLoading[name]) {
|
|
782
1155
|
return;
|
|
783
1156
|
}
|
|
@@ -922,8 +1295,7 @@
|
|
|
922
1295
|
},
|
|
923
1296
|
|
|
924
1297
|
normalizeSessionPathValue(value) {
|
|
925
|
-
|
|
926
|
-
return value.trim();
|
|
1298
|
+
return normalizeSessionPathFilter(value);
|
|
927
1299
|
},
|
|
928
1300
|
|
|
929
1301
|
mergeSessionPathOptions(baseList = [], incomingList = []) {
|
|
@@ -1032,13 +1404,32 @@
|
|
|
1032
1404
|
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
1033
1405
|
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
1034
1406
|
},
|
|
1407
|
+
restoreSessionFilterCache() {
|
|
1408
|
+
const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
|
|
1409
|
+
const pathCache = localStorage.getItem('codexmateSessionPathFilter');
|
|
1410
|
+
const cached = buildSessionFilterCacheState(sourceCache, pathCache);
|
|
1411
|
+
this.sessionFilterSource = cached.source;
|
|
1412
|
+
this.sessionPathFilter = cached.pathFilter;
|
|
1413
|
+
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
1414
|
+
},
|
|
1415
|
+
persistSessionFilterCache() {
|
|
1416
|
+
const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
|
|
1417
|
+
localStorage.setItem('codexmateSessionFilterSource', cached.source);
|
|
1418
|
+
if (cached.pathFilter) {
|
|
1419
|
+
localStorage.setItem('codexmateSessionPathFilter', cached.pathFilter);
|
|
1420
|
+
} else {
|
|
1421
|
+
localStorage.removeItem('codexmateSessionPathFilter');
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1035
1424
|
|
|
1036
1425
|
async onSessionSourceChange() {
|
|
1037
1426
|
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
1427
|
+
this.persistSessionFilterCache();
|
|
1038
1428
|
await this.loadSessions();
|
|
1039
1429
|
},
|
|
1040
1430
|
|
|
1041
1431
|
async onSessionPathFilterChange() {
|
|
1432
|
+
this.persistSessionFilterCache();
|
|
1042
1433
|
await this.loadSessions();
|
|
1043
1434
|
},
|
|
1044
1435
|
|
|
@@ -1052,8 +1443,156 @@
|
|
|
1052
1443
|
this.sessionQuery = '';
|
|
1053
1444
|
this.sessionRoleFilter = 'all';
|
|
1054
1445
|
this.sessionTimePreset = 'all';
|
|
1446
|
+
this.persistSessionFilterCache();
|
|
1055
1447
|
await this.onSessionSourceChange();
|
|
1056
1448
|
},
|
|
1449
|
+
setSessionPreviewContainerRef(el) {
|
|
1450
|
+
this.sessionPreviewContainerEl = el || null;
|
|
1451
|
+
this.updateSessionTimelineOffset();
|
|
1452
|
+
},
|
|
1453
|
+
disconnectSessionPreviewHeaderResizeObserver() {
|
|
1454
|
+
if (!this.sessionPreviewHeaderResizeObserver) return;
|
|
1455
|
+
this.sessionPreviewHeaderResizeObserver.disconnect();
|
|
1456
|
+
this.sessionPreviewHeaderResizeObserver = null;
|
|
1457
|
+
},
|
|
1458
|
+
observeSessionPreviewHeaderResize() {
|
|
1459
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
1460
|
+
if (!this.sessionPreviewHeaderEl || typeof ResizeObserver !== 'function') return;
|
|
1461
|
+
this.sessionPreviewHeaderResizeObserver = new ResizeObserver(() => {
|
|
1462
|
+
this.updateSessionTimelineOffset();
|
|
1463
|
+
});
|
|
1464
|
+
this.sessionPreviewHeaderResizeObserver.observe(this.sessionPreviewHeaderEl);
|
|
1465
|
+
},
|
|
1466
|
+
setSessionPreviewHeaderRef(el) {
|
|
1467
|
+
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
1468
|
+
this.sessionPreviewHeaderEl = el || null;
|
|
1469
|
+
this.observeSessionPreviewHeaderResize();
|
|
1470
|
+
this.updateSessionTimelineOffset();
|
|
1471
|
+
},
|
|
1472
|
+
setSessionPreviewScrollRef(el) {
|
|
1473
|
+
this.sessionPreviewScrollEl = el || null;
|
|
1474
|
+
if (this.sessionPreviewScrollEl) {
|
|
1475
|
+
this.scheduleSessionTimelineSync();
|
|
1476
|
+
} else {
|
|
1477
|
+
this.cancelSessionTimelineSync();
|
|
1478
|
+
}
|
|
1479
|
+
this.updateSessionTimelineOffset();
|
|
1480
|
+
},
|
|
1481
|
+
updateSessionTimelineOffset() {
|
|
1482
|
+
const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
|
|
1483
|
+
if (!container || !container.style) return;
|
|
1484
|
+
const header = this.sessionPreviewHeaderEl
|
|
1485
|
+
|| (this.sessionPreviewScrollEl ? this.sessionPreviewScrollEl.querySelector('.session-preview-header') : null)
|
|
1486
|
+
|| container.querySelector('.session-preview-header');
|
|
1487
|
+
const headerHeight = header ? Math.ceil(header.getBoundingClientRect().height) : 0;
|
|
1488
|
+
const offset = headerHeight > 0 ? (headerHeight + 12) : 72;
|
|
1489
|
+
container.style.setProperty('--session-preview-header-offset', `${offset}px`);
|
|
1490
|
+
},
|
|
1491
|
+
bindSessionMessageRef(messageKey, el) {
|
|
1492
|
+
if (!messageKey) return;
|
|
1493
|
+
if (el) {
|
|
1494
|
+
this.sessionMessageRefMap[messageKey] = el;
|
|
1495
|
+
} else {
|
|
1496
|
+
delete this.sessionMessageRefMap[messageKey];
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
cancelSessionTimelineSync() {
|
|
1500
|
+
if (!this.sessionTimelineRafId) return;
|
|
1501
|
+
if (typeof cancelAnimationFrame === 'function') {
|
|
1502
|
+
cancelAnimationFrame(this.sessionTimelineRafId);
|
|
1503
|
+
}
|
|
1504
|
+
this.sessionTimelineRafId = 0;
|
|
1505
|
+
},
|
|
1506
|
+
scheduleSessionTimelineSync() {
|
|
1507
|
+
if (this.sessionTimelineRafId) return;
|
|
1508
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1509
|
+
this.sessionTimelineRafId = requestAnimationFrame(() => {
|
|
1510
|
+
this.sessionTimelineRafId = 0;
|
|
1511
|
+
this.syncSessionTimelineActiveFromScroll();
|
|
1512
|
+
});
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
this.syncSessionTimelineActiveFromScroll();
|
|
1516
|
+
},
|
|
1517
|
+
onSessionPreviewScroll() {
|
|
1518
|
+
this.scheduleSessionTimelineSync();
|
|
1519
|
+
},
|
|
1520
|
+
onWindowResize() {
|
|
1521
|
+
this.updateSessionTimelineOffset();
|
|
1522
|
+
this.scheduleSessionTimelineSync();
|
|
1523
|
+
},
|
|
1524
|
+
syncSessionTimelineActiveFromScroll() {
|
|
1525
|
+
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1526
|
+
if (!nodes.length) {
|
|
1527
|
+
this.sessionTimelineActiveKey = '';
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1531
|
+
if (!scrollEl) {
|
|
1532
|
+
this.sessionTimelineActiveKey = nodes[0].key;
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1536
|
+
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1537
|
+
const headerHeight = headerEl ? headerEl.getBoundingClientRect().height : 0;
|
|
1538
|
+
const anchorLine = scrollRect.top + headerHeight + 8;
|
|
1539
|
+
let activeKey = nodes[0].key;
|
|
1540
|
+
for (const node of nodes) {
|
|
1541
|
+
const messageEl = this.sessionMessageRefMap[node.key];
|
|
1542
|
+
if (!messageEl) continue;
|
|
1543
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
1544
|
+
if (messageRect.top <= anchorLine) {
|
|
1545
|
+
activeKey = node.key;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
this.sessionTimelineActiveKey = activeKey;
|
|
1551
|
+
},
|
|
1552
|
+
jumpToSessionTimelineNode(messageKey) {
|
|
1553
|
+
if (!messageKey) return;
|
|
1554
|
+
const scrollEl = this.sessionPreviewScrollEl || this.$refs.sessionPreviewScroll;
|
|
1555
|
+
if (!scrollEl) return;
|
|
1556
|
+
const messageEl = this.sessionMessageRefMap[messageKey];
|
|
1557
|
+
if (!messageEl) return;
|
|
1558
|
+
const headerEl = scrollEl.querySelector('.session-preview-header');
|
|
1559
|
+
const stickyOffset = headerEl ? (headerEl.offsetHeight + 8) : 8;
|
|
1560
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
1561
|
+
const messageRect = messageEl.getBoundingClientRect();
|
|
1562
|
+
const targetScrollTop = scrollEl.scrollTop + (messageRect.top - scrollRect.top) - stickyOffset;
|
|
1563
|
+
this.sessionTimelineActiveKey = messageKey;
|
|
1564
|
+
if (typeof scrollEl.scrollTo === 'function') {
|
|
1565
|
+
scrollEl.scrollTo({
|
|
1566
|
+
top: Math.max(0, targetScrollTop),
|
|
1567
|
+
behavior: 'smooth'
|
|
1568
|
+
});
|
|
1569
|
+
} else {
|
|
1570
|
+
messageEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
|
|
1574
|
+
normalizeSessionMessage(message) {
|
|
1575
|
+
const fallback = {
|
|
1576
|
+
role: 'assistant',
|
|
1577
|
+
normalizedRole: 'assistant',
|
|
1578
|
+
roleLabel: 'Assistant',
|
|
1579
|
+
text: typeof message === 'string' ? message : '',
|
|
1580
|
+
timestamp: ''
|
|
1581
|
+
};
|
|
1582
|
+
const safeMessage = message && typeof message === 'object' ? message : fallback;
|
|
1583
|
+
const normalizedRole = normalizeSessionMessageRole(
|
|
1584
|
+
safeMessage.normalizedRole || safeMessage.role
|
|
1585
|
+
);
|
|
1586
|
+
const roleLabel = normalizedRole === 'user'
|
|
1587
|
+
? 'User'
|
|
1588
|
+
: (normalizedRole === 'system' ? 'System' : 'Assistant');
|
|
1589
|
+
return {
|
|
1590
|
+
...safeMessage,
|
|
1591
|
+
role: normalizedRole,
|
|
1592
|
+
normalizedRole,
|
|
1593
|
+
roleLabel
|
|
1594
|
+
};
|
|
1595
|
+
},
|
|
1057
1596
|
|
|
1058
1597
|
getRecordKey(message) {
|
|
1059
1598
|
if (!message || !Number.isInteger(message.recordLineIndex) || message.recordLineIndex < 0) {
|
|
@@ -1102,6 +1641,9 @@
|
|
|
1102
1641
|
this.activeSession = null;
|
|
1103
1642
|
this.activeSessionMessages = [];
|
|
1104
1643
|
this.activeSessionDetailClipped = false;
|
|
1644
|
+
this.cancelSessionTimelineSync();
|
|
1645
|
+
this.sessionTimelineActiveKey = '';
|
|
1646
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1105
1647
|
} else {
|
|
1106
1648
|
this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
|
|
1107
1649
|
this.syncSessionPathOptionsForSource(
|
|
@@ -1113,10 +1655,19 @@
|
|
|
1113
1655
|
this.activeSession = null;
|
|
1114
1656
|
this.activeSessionMessages = [];
|
|
1115
1657
|
this.activeSessionDetailClipped = false;
|
|
1658
|
+
this.cancelSessionTimelineSync();
|
|
1659
|
+
this.sessionTimelineActiveKey = '';
|
|
1660
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1116
1661
|
} else {
|
|
1117
1662
|
const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1118
1663
|
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
|
|
1119
1664
|
this.activeSession = matched || this.sessionsList[0];
|
|
1665
|
+
this.activeSessionMessages = [];
|
|
1666
|
+
this.activeSessionDetailError = '';
|
|
1667
|
+
this.activeSessionDetailClipped = false;
|
|
1668
|
+
this.cancelSessionTimelineSync();
|
|
1669
|
+
this.sessionTimelineActiveKey = '';
|
|
1670
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1120
1671
|
await this.loadActiveSessionDetail();
|
|
1121
1672
|
}
|
|
1122
1673
|
void this.loadSessionPathOptions({ source: this.sessionFilterSource });
|
|
@@ -1126,6 +1677,9 @@
|
|
|
1126
1677
|
this.activeSession = null;
|
|
1127
1678
|
this.activeSessionMessages = [];
|
|
1128
1679
|
this.activeSessionDetailClipped = false;
|
|
1680
|
+
this.cancelSessionTimelineSync();
|
|
1681
|
+
this.sessionTimelineActiveKey = '';
|
|
1682
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1129
1683
|
this.showMessage('加载会话失败', 'error');
|
|
1130
1684
|
} finally {
|
|
1131
1685
|
this.sessionsLoading = false;
|
|
@@ -1139,6 +1693,9 @@
|
|
|
1139
1693
|
this.activeSessionMessages = [];
|
|
1140
1694
|
this.activeSessionDetailError = '';
|
|
1141
1695
|
this.activeSessionDetailClipped = false;
|
|
1696
|
+
this.cancelSessionTimelineSync();
|
|
1697
|
+
this.sessionTimelineActiveKey = '';
|
|
1698
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1142
1699
|
await this.loadActiveSessionDetail();
|
|
1143
1700
|
},
|
|
1144
1701
|
|
|
@@ -1192,6 +1749,9 @@
|
|
|
1192
1749
|
this.activeSessionMessages = [];
|
|
1193
1750
|
this.activeSessionDetailError = '';
|
|
1194
1751
|
this.activeSessionDetailClipped = false;
|
|
1752
|
+
this.cancelSessionTimelineSync();
|
|
1753
|
+
this.sessionTimelineActiveKey = '';
|
|
1754
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1195
1755
|
return;
|
|
1196
1756
|
}
|
|
1197
1757
|
|
|
@@ -1214,10 +1774,14 @@
|
|
|
1214
1774
|
this.activeSessionMessages = [];
|
|
1215
1775
|
this.activeSessionDetailClipped = false;
|
|
1216
1776
|
this.activeSessionDetailError = res.error;
|
|
1777
|
+
this.cancelSessionTimelineSync();
|
|
1778
|
+
this.sessionTimelineActiveKey = '';
|
|
1779
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1217
1780
|
return;
|
|
1218
1781
|
}
|
|
1219
1782
|
|
|
1220
|
-
|
|
1783
|
+
const rawMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
1784
|
+
this.activeSessionMessages = rawMessages.map((message) => this.normalizeSessionMessage(message));
|
|
1221
1785
|
this.activeSessionDetailClipped = !!res.clipped;
|
|
1222
1786
|
if (this.activeSession) {
|
|
1223
1787
|
if (res.sourceLabel) {
|
|
@@ -1242,6 +1806,10 @@
|
|
|
1242
1806
|
if (Number.isFinite(res.totalMessages)) {
|
|
1243
1807
|
this.syncActiveSessionMessageCount(res.totalMessages);
|
|
1244
1808
|
}
|
|
1809
|
+
this.$nextTick(() => {
|
|
1810
|
+
this.updateSessionTimelineOffset();
|
|
1811
|
+
this.scheduleSessionTimelineSync();
|
|
1812
|
+
});
|
|
1245
1813
|
} catch (e) {
|
|
1246
1814
|
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1247
1815
|
return;
|
|
@@ -1249,6 +1817,9 @@
|
|
|
1249
1817
|
this.activeSessionMessages = [];
|
|
1250
1818
|
this.activeSessionDetailClipped = false;
|
|
1251
1819
|
this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
|
|
1820
|
+
this.cancelSessionTimelineSync();
|
|
1821
|
+
this.sessionTimelineActiveKey = '';
|
|
1822
|
+
this.sessionMessageRefMap = Object.create(null);
|
|
1252
1823
|
} finally {
|
|
1253
1824
|
if (requestSeq === this.sessionDetailRequestSeq) {
|
|
1254
1825
|
this.sessionDetailLoading = false;
|
|
@@ -1256,10 +1827,10 @@
|
|
|
1256
1827
|
}
|
|
1257
1828
|
},
|
|
1258
1829
|
|
|
1259
|
-
downloadTextFile(fileName, content) {
|
|
1830
|
+
downloadTextFile(fileName, content, mimeType = 'text/markdown;charset=utf-8') {
|
|
1260
1831
|
// 使用 UTF-8 BOM 确保文本编辑器正确识别编码
|
|
1261
1832
|
const BOM = '\uFEFF';
|
|
1262
|
-
const blob = new Blob([BOM + content], { type:
|
|
1833
|
+
const blob = new Blob([BOM + content], { type: mimeType });
|
|
1263
1834
|
const url = URL.createObjectURL(blob);
|
|
1264
1835
|
const link = document.createElement('a');
|
|
1265
1836
|
link.href = url;
|
|
@@ -1509,6 +2080,191 @@
|
|
|
1509
2080
|
}
|
|
1510
2081
|
},
|
|
1511
2082
|
|
|
2083
|
+
async openSkillsManager() {
|
|
2084
|
+
this.skillsSelectedNames = [];
|
|
2085
|
+
this.skillsKeyword = '';
|
|
2086
|
+
this.skillsStatusFilter = 'all';
|
|
2087
|
+
this.skillsImportList = [];
|
|
2088
|
+
this.skillsImportSelectedKeys = [];
|
|
2089
|
+
this.showSkillsModal = true;
|
|
2090
|
+
await this.refreshSkillsList({ silent: false });
|
|
2091
|
+
},
|
|
2092
|
+
|
|
2093
|
+
closeSkillsModal() {
|
|
2094
|
+
this.showSkillsModal = false;
|
|
2095
|
+
this.skillsSelectedNames = [];
|
|
2096
|
+
this.skillsImportSelectedKeys = [];
|
|
2097
|
+
},
|
|
2098
|
+
|
|
2099
|
+
async refreshSkillsList(options = {}) {
|
|
2100
|
+
this.skillsLoading = true;
|
|
2101
|
+
try {
|
|
2102
|
+
const res = await api('list-codex-skills');
|
|
2103
|
+
if (res.error) {
|
|
2104
|
+
this.showMessage(res.error, 'error');
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
this.skillsRootPath = res.root || '';
|
|
2108
|
+
this.skillsList = Array.isArray(res.items) ? res.items : [];
|
|
2109
|
+
const currentNames = new Set((Array.isArray(this.skillsList) ? this.skillsList : [])
|
|
2110
|
+
.map((item) => (item && typeof item.name === 'string' ? item.name.trim() : ''))
|
|
2111
|
+
.filter(Boolean));
|
|
2112
|
+
this.skillsSelectedNames = (Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : [])
|
|
2113
|
+
.filter((name) => currentNames.has(name));
|
|
2114
|
+
if (!options.silent) {
|
|
2115
|
+
const exists = res.exists !== false;
|
|
2116
|
+
if (!exists) {
|
|
2117
|
+
this.showMessage('skills 目录不存在,已按空列表显示', 'info');
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
} catch (e) {
|
|
2121
|
+
this.showMessage('加载 skills 失败', 'error');
|
|
2122
|
+
} finally {
|
|
2123
|
+
this.skillsLoading = false;
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
|
|
2127
|
+
resetSkillsFilters() {
|
|
2128
|
+
this.skillsKeyword = '';
|
|
2129
|
+
this.skillsStatusFilter = 'all';
|
|
2130
|
+
},
|
|
2131
|
+
|
|
2132
|
+
toggleAllSkillsSelection() {
|
|
2133
|
+
const selectable = this.skillsSelectableNames;
|
|
2134
|
+
if (this.skillsAllSelected) {
|
|
2135
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
2136
|
+
selectable.forEach((name) => selectedSet.delete(name));
|
|
2137
|
+
this.skillsSelectedNames = Array.from(selectedSet);
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const selectedSet = new Set(Array.isArray(this.skillsSelectedNames) ? this.skillsSelectedNames : []);
|
|
2141
|
+
selectable.forEach((name) => selectedSet.add(name));
|
|
2142
|
+
this.skillsSelectedNames = Array.from(selectedSet);
|
|
2143
|
+
},
|
|
2144
|
+
|
|
2145
|
+
buildSkillImportKey(item) {
|
|
2146
|
+
const safe = item && typeof item === 'object' ? item : {};
|
|
2147
|
+
const sourceApp = typeof safe.sourceApp === 'string' ? safe.sourceApp.trim().toLowerCase() : '';
|
|
2148
|
+
const name = typeof safe.name === 'string' ? safe.name.trim() : '';
|
|
2149
|
+
if (!sourceApp || !name) return '';
|
|
2150
|
+
return `${sourceApp}:${name}`;
|
|
2151
|
+
},
|
|
2152
|
+
|
|
2153
|
+
toggleAllSkillsImportSelection() {
|
|
2154
|
+
const selectable = this.skillsImportSelectableKeys;
|
|
2155
|
+
if (this.skillsImportAllSelected) {
|
|
2156
|
+
this.skillsImportSelectedKeys = [];
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
this.skillsImportSelectedKeys = [...selectable];
|
|
2160
|
+
},
|
|
2161
|
+
|
|
2162
|
+
async scanImportableSkills() {
|
|
2163
|
+
if (this.skillsScanningImports || this.skillsImporting) return;
|
|
2164
|
+
this.skillsScanningImports = true;
|
|
2165
|
+
try {
|
|
2166
|
+
const res = await api('scan-unmanaged-codex-skills');
|
|
2167
|
+
if (res.error) {
|
|
2168
|
+
this.showMessage(res.error, 'error');
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
this.skillsImportList = Array.isArray(res.items) ? res.items : [];
|
|
2172
|
+
const availableKeys = new Set(this.skillsImportSelectableKeys);
|
|
2173
|
+
this.skillsImportSelectedKeys = (Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : [])
|
|
2174
|
+
.filter((key) => availableKeys.has(key));
|
|
2175
|
+
if (this.skillsImportList.length === 0) {
|
|
2176
|
+
this.showMessage('未扫描到可导入 skill', 'info');
|
|
2177
|
+
} else {
|
|
2178
|
+
this.showMessage(`扫描到 ${this.skillsImportList.length} 个可导入 skill`, 'success');
|
|
2179
|
+
}
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
this.showMessage('扫描可导入 skill 失败', 'error');
|
|
2182
|
+
} finally {
|
|
2183
|
+
this.skillsScanningImports = false;
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
|
|
2187
|
+
async importSelectedSkills() {
|
|
2188
|
+
if (this.skillsImporting) return;
|
|
2189
|
+
const selectedSet = new Set(Array.isArray(this.skillsImportSelectedKeys) ? this.skillsImportSelectedKeys : []);
|
|
2190
|
+
const selectedItems = (Array.isArray(this.skillsImportList) ? this.skillsImportList : [])
|
|
2191
|
+
.filter((item) => selectedSet.has(this.buildSkillImportKey(item)))
|
|
2192
|
+
.map((item) => ({
|
|
2193
|
+
name: item.name,
|
|
2194
|
+
sourceApp: item.sourceApp
|
|
2195
|
+
}));
|
|
2196
|
+
if (!selectedItems.length) {
|
|
2197
|
+
this.showMessage('请先选择要导入的 skill', 'error');
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
this.skillsImporting = true;
|
|
2202
|
+
try {
|
|
2203
|
+
const res = await api('import-codex-skills', { items: selectedItems });
|
|
2204
|
+
if (res.error) {
|
|
2205
|
+
this.showMessage(res.error, 'error');
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
const importedCount = Array.isArray(res.imported) ? res.imported.length : 0;
|
|
2209
|
+
const failedCount = Array.isArray(res.failed) ? res.failed.length : 0;
|
|
2210
|
+
if (failedCount > 0 && importedCount > 0) {
|
|
2211
|
+
this.showMessage(`已导入 ${importedCount} 个,失败 ${failedCount} 个`, 'error');
|
|
2212
|
+
} else if (failedCount > 0) {
|
|
2213
|
+
const first = res.failed[0] && res.failed[0].error ? res.failed[0].error : '导入失败';
|
|
2214
|
+
this.showMessage(first, 'error');
|
|
2215
|
+
} else {
|
|
2216
|
+
this.showMessage(`已导入 ${importedCount} 个 skill`, 'success');
|
|
2217
|
+
}
|
|
2218
|
+
await this.refreshSkillsList({ silent: true });
|
|
2219
|
+
} catch (e) {
|
|
2220
|
+
this.showMessage('导入 skill 失败', 'error');
|
|
2221
|
+
} finally {
|
|
2222
|
+
this.skillsImporting = false;
|
|
2223
|
+
await this.scanImportableSkills();
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
|
|
2227
|
+
async deleteSelectedSkills() {
|
|
2228
|
+
if (this.skillsDeleting) return;
|
|
2229
|
+
const selected = Array.isArray(this.skillsSelectedNames)
|
|
2230
|
+
? Array.from(new Set(this.skillsSelectedNames.map((item) => String(item || '').trim()).filter(Boolean)))
|
|
2231
|
+
: [];
|
|
2232
|
+
if (!selected.length) {
|
|
2233
|
+
this.showMessage('请先选择要删除的 skill', 'error');
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
const confirmed = window.confirm(`确认删除 ${selected.length} 个 skill 吗?此操作不可撤销。`);
|
|
2237
|
+
if (!confirmed) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
this.skillsDeleting = true;
|
|
2242
|
+
try {
|
|
2243
|
+
const res = await api('delete-codex-skills', { names: selected });
|
|
2244
|
+
if (res.error) {
|
|
2245
|
+
this.showMessage(res.error, 'error');
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
const deletedCount = Array.isArray(res.deleted) ? res.deleted.length : 0;
|
|
2250
|
+
const failedList = Array.isArray(res.failed) ? res.failed : [];
|
|
2251
|
+
const failedCount = failedList.length;
|
|
2252
|
+
if (failedCount > 0 && deletedCount > 0) {
|
|
2253
|
+
this.showMessage(`已删除 ${deletedCount} 个,失败 ${failedCount} 个`, 'error');
|
|
2254
|
+
} else if (failedCount > 0) {
|
|
2255
|
+
const first = failedList[0] && failedList[0].error ? failedList[0].error : '删除失败';
|
|
2256
|
+
this.showMessage(first, 'error');
|
|
2257
|
+
} else {
|
|
2258
|
+
this.showMessage(`已删除 ${deletedCount} 个 skill`, 'success');
|
|
2259
|
+
}
|
|
2260
|
+
await this.refreshSkillsList({ silent: true });
|
|
2261
|
+
} catch (e) {
|
|
2262
|
+
this.showMessage('删除 skill 失败', 'error');
|
|
2263
|
+
} finally {
|
|
2264
|
+
this.skillsDeleting = false;
|
|
2265
|
+
}
|
|
2266
|
+
},
|
|
2267
|
+
|
|
1512
2268
|
async openOpenclawAgentsEditor() {
|
|
1513
2269
|
this.setAgentsModalContext('openclaw');
|
|
1514
2270
|
this.agentsLoading = true;
|
|
@@ -1632,6 +2388,9 @@
|
|
|
1632
2388
|
if (!name) {
|
|
1633
2389
|
return this.showMessage('名称不能为空', 'error');
|
|
1634
2390
|
}
|
|
2391
|
+
if (name.toLowerCase() === 'local') {
|
|
2392
|
+
return this.showMessage('local provider 为系统保留名称,不可新增', 'error');
|
|
2393
|
+
}
|
|
1635
2394
|
if (this.providersList.some(item => item.name === name)) {
|
|
1636
2395
|
return this.showMessage('名称已存在', 'error');
|
|
1637
2396
|
}
|
|
@@ -1655,7 +2414,89 @@
|
|
|
1655
2414
|
}
|
|
1656
2415
|
},
|
|
1657
2416
|
|
|
2417
|
+
getCurrentCodexAuthProfile() {
|
|
2418
|
+
const list = Array.isArray(this.codexAuthProfiles) ? this.codexAuthProfiles : [];
|
|
2419
|
+
return list.find((item) => !!(item && item.current)) || null;
|
|
2420
|
+
},
|
|
2421
|
+
|
|
2422
|
+
isLocalLikeProvider(providerOrName) {
|
|
2423
|
+
if (!providerOrName) return false;
|
|
2424
|
+
const rawName = typeof providerOrName === 'object'
|
|
2425
|
+
? String(providerOrName.name || '')
|
|
2426
|
+
: String(providerOrName);
|
|
2427
|
+
const normalized = rawName.trim().toLowerCase();
|
|
2428
|
+
return normalized === 'local' || normalized === 'codexmate-proxy';
|
|
2429
|
+
},
|
|
2430
|
+
|
|
2431
|
+
providerPillState(provider) {
|
|
2432
|
+
if (this.isLocalLikeProvider(provider)) {
|
|
2433
|
+
const currentProfile = this.getCurrentCodexAuthProfile();
|
|
2434
|
+
return currentProfile
|
|
2435
|
+
? { configured: true, text: '已登录' }
|
|
2436
|
+
: { configured: false, text: '未登录' };
|
|
2437
|
+
}
|
|
2438
|
+
const configured = !!(provider && provider.hasKey);
|
|
2439
|
+
return {
|
|
2440
|
+
configured,
|
|
2441
|
+
text: configured ? '已配置' : '未配置'
|
|
2442
|
+
};
|
|
2443
|
+
},
|
|
2444
|
+
|
|
2445
|
+
providerPillConfigured(provider) {
|
|
2446
|
+
return this.providerPillState(provider).configured;
|
|
2447
|
+
},
|
|
2448
|
+
|
|
2449
|
+
providerPillText(provider) {
|
|
2450
|
+
return this.providerPillState(provider).text;
|
|
2451
|
+
},
|
|
2452
|
+
|
|
2453
|
+
isReadOnlyProvider(providerOrName) {
|
|
2454
|
+
if (!providerOrName) return false;
|
|
2455
|
+
if (typeof providerOrName === 'object') {
|
|
2456
|
+
return !!providerOrName.readOnly;
|
|
2457
|
+
}
|
|
2458
|
+
const name = String(providerOrName).trim();
|
|
2459
|
+
if (!name) return false;
|
|
2460
|
+
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
2461
|
+
return !!(target && target.readOnly);
|
|
2462
|
+
},
|
|
2463
|
+
|
|
2464
|
+
isNonDeletableProvider(providerOrName) {
|
|
2465
|
+
if (!providerOrName) return false;
|
|
2466
|
+
if (typeof providerOrName === 'object') {
|
|
2467
|
+
const directName = String(providerOrName.name || '').trim().toLowerCase();
|
|
2468
|
+
if (directName === 'local' || directName === 'codexmate-proxy') {
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
return !!providerOrName.nonDeletable;
|
|
2472
|
+
}
|
|
2473
|
+
const name = String(providerOrName).trim();
|
|
2474
|
+
if (!name) return false;
|
|
2475
|
+
const normalized = name.toLowerCase();
|
|
2476
|
+
if (normalized === 'local' || normalized === 'codexmate-proxy') {
|
|
2477
|
+
return true;
|
|
2478
|
+
}
|
|
2479
|
+
const target = (this.providersList || []).find((item) => item && item.name === name);
|
|
2480
|
+
return !!(target && target.nonDeletable);
|
|
2481
|
+
},
|
|
2482
|
+
|
|
2483
|
+
shouldShowProviderDelete(provider) {
|
|
2484
|
+
return !this.isReadOnlyProvider(provider) && !this.isNonDeletableProvider(provider);
|
|
2485
|
+
},
|
|
2486
|
+
|
|
2487
|
+
shouldShowProviderEdit(provider) {
|
|
2488
|
+
return !this.isReadOnlyProvider(provider) && !this.isNonDeletableProvider(provider);
|
|
2489
|
+
},
|
|
2490
|
+
|
|
2491
|
+
shouldAllowProviderShare(provider) {
|
|
2492
|
+
return !this.isReadOnlyProvider(provider) && !this.isLocalLikeProvider(provider);
|
|
2493
|
+
},
|
|
2494
|
+
|
|
1658
2495
|
async deleteProvider(name) {
|
|
2496
|
+
if (this.isNonDeletableProvider(name)) {
|
|
2497
|
+
this.showMessage('该 provider 为保留项,不可删除', 'info');
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
1659
2500
|
const res = await api('delete-provider', { name });
|
|
1660
2501
|
if (res.error) {
|
|
1661
2502
|
this.showMessage(res.error, 'error');
|
|
@@ -1670,15 +2511,26 @@
|
|
|
1670
2511
|
},
|
|
1671
2512
|
|
|
1672
2513
|
openEditModal(provider) {
|
|
2514
|
+
if (!this.shouldShowProviderEdit(provider)) {
|
|
2515
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'info');
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
1673
2518
|
this.editingProvider = {
|
|
1674
2519
|
name: provider.name,
|
|
1675
2520
|
url: provider.url || '',
|
|
1676
|
-
key: ''
|
|
2521
|
+
key: '',
|
|
2522
|
+
readOnly: !!provider.readOnly,
|
|
2523
|
+
nonEditable: this.isNonDeletableProvider(provider)
|
|
1677
2524
|
};
|
|
1678
2525
|
this.showEditModal = true;
|
|
1679
2526
|
},
|
|
1680
2527
|
|
|
1681
2528
|
async updateProvider() {
|
|
2529
|
+
if (this.editingProvider.readOnly || this.editingProvider.nonEditable) {
|
|
2530
|
+
this.showMessage('该 provider 为保留项,不可编辑', 'error');
|
|
2531
|
+
this.closeEditModal();
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
1682
2534
|
if (!this.editingProvider.url) {
|
|
1683
2535
|
return this.showMessage('URL 必填', 'error');
|
|
1684
2536
|
}
|
|
@@ -1702,7 +2554,7 @@
|
|
|
1702
2554
|
|
|
1703
2555
|
closeEditModal() {
|
|
1704
2556
|
this.showEditModal = false;
|
|
1705
|
-
this.editingProvider = { name: '', url: '', key: '' };
|
|
2557
|
+
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false };
|
|
1706
2558
|
},
|
|
1707
2559
|
|
|
1708
2560
|
async resetConfig() {
|
|
@@ -2751,6 +3603,136 @@
|
|
|
2751
3603
|
this.resetOpenclawQuick();
|
|
2752
3604
|
},
|
|
2753
3605
|
|
|
3606
|
+
normalizeInstallPackageManager(value) {
|
|
3607
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3608
|
+
if (normalized === 'pnpm' || normalized === 'bun' || normalized === 'npm') {
|
|
3609
|
+
return normalized;
|
|
3610
|
+
}
|
|
3611
|
+
return 'npm';
|
|
3612
|
+
},
|
|
3613
|
+
|
|
3614
|
+
normalizeInstallAction(value) {
|
|
3615
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3616
|
+
if (normalized === 'update' || normalized === 'uninstall' || normalized === 'install') {
|
|
3617
|
+
return normalized;
|
|
3618
|
+
}
|
|
3619
|
+
return 'install';
|
|
3620
|
+
},
|
|
3621
|
+
|
|
3622
|
+
normalizeInstallRegistryPreset(value) {
|
|
3623
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3624
|
+
if (normalized === 'default' || normalized === 'npmmirror' || normalized === 'tencent' || normalized === 'custom') {
|
|
3625
|
+
return normalized;
|
|
3626
|
+
}
|
|
3627
|
+
return 'default';
|
|
3628
|
+
},
|
|
3629
|
+
|
|
3630
|
+
normalizeInstallRegistryUrl(value) {
|
|
3631
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
3632
|
+
if (!normalized) return '';
|
|
3633
|
+
if (!/^https?:\/\//i.test(normalized)) {
|
|
3634
|
+
return '';
|
|
3635
|
+
}
|
|
3636
|
+
return normalized.replace(/\/+$/, '');
|
|
3637
|
+
},
|
|
3638
|
+
|
|
3639
|
+
resolveInstallRegistryUrl(presetValue, customValue) {
|
|
3640
|
+
const preset = this.normalizeInstallRegistryPreset(presetValue);
|
|
3641
|
+
if (preset === 'npmmirror') {
|
|
3642
|
+
return 'https://registry.npmmirror.com';
|
|
3643
|
+
}
|
|
3644
|
+
if (preset === 'tencent') {
|
|
3645
|
+
return 'https://mirrors.cloud.tencent.com/npm';
|
|
3646
|
+
}
|
|
3647
|
+
if (preset === 'custom') {
|
|
3648
|
+
return this.normalizeInstallRegistryUrl(customValue);
|
|
3649
|
+
}
|
|
3650
|
+
return '';
|
|
3651
|
+
},
|
|
3652
|
+
|
|
3653
|
+
appendInstallRegistryOption(command, actionName) {
|
|
3654
|
+
const base = typeof command === 'string' ? command.trim() : '';
|
|
3655
|
+
if (!base) return '';
|
|
3656
|
+
const action = this.normalizeInstallAction(actionName);
|
|
3657
|
+
if (action === 'uninstall') {
|
|
3658
|
+
return base;
|
|
3659
|
+
}
|
|
3660
|
+
const registry = this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
3661
|
+
if (!registry) {
|
|
3662
|
+
return base;
|
|
3663
|
+
}
|
|
3664
|
+
return `${base} --registry=${registry}`;
|
|
3665
|
+
},
|
|
3666
|
+
|
|
3667
|
+
resolveInstallPlatform() {
|
|
3668
|
+
const navPlatform = typeof navigator !== 'undefined' && typeof navigator.platform === 'string'
|
|
3669
|
+
? navigator.platform.trim().toLowerCase()
|
|
3670
|
+
: '';
|
|
3671
|
+
if (navPlatform.includes('win')) return 'win32';
|
|
3672
|
+
if (navPlatform.includes('mac')) return 'darwin';
|
|
3673
|
+
return 'linux';
|
|
3674
|
+
},
|
|
3675
|
+
|
|
3676
|
+
buildInstallCommandMatrix(packageManager) {
|
|
3677
|
+
const manager = this.normalizeInstallPackageManager(packageManager);
|
|
3678
|
+
const matrix = {
|
|
3679
|
+
claude: {
|
|
3680
|
+
install: '',
|
|
3681
|
+
update: '',
|
|
3682
|
+
uninstall: ''
|
|
3683
|
+
},
|
|
3684
|
+
codex: {
|
|
3685
|
+
install: '',
|
|
3686
|
+
update: '',
|
|
3687
|
+
uninstall: ''
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
if (manager === 'pnpm') {
|
|
3691
|
+
matrix.claude.install = 'pnpm add -g @anthropic-ai/claude-code';
|
|
3692
|
+
matrix.claude.update = 'pnpm up -g @anthropic-ai/claude-code';
|
|
3693
|
+
matrix.claude.uninstall = 'pnpm remove -g @anthropic-ai/claude-code';
|
|
3694
|
+
matrix.codex.install = 'pnpm add -g @openai/codex';
|
|
3695
|
+
matrix.codex.update = 'pnpm up -g @openai/codex';
|
|
3696
|
+
matrix.codex.uninstall = 'pnpm remove -g @openai/codex';
|
|
3697
|
+
return matrix;
|
|
3698
|
+
}
|
|
3699
|
+
if (manager === 'bun') {
|
|
3700
|
+
matrix.claude.install = 'bun add -g @anthropic-ai/claude-code';
|
|
3701
|
+
matrix.claude.update = 'bun update -g @anthropic-ai/claude-code';
|
|
3702
|
+
matrix.claude.uninstall = 'bun remove -g @anthropic-ai/claude-code';
|
|
3703
|
+
matrix.codex.install = 'bun add -g @openai/codex';
|
|
3704
|
+
matrix.codex.update = 'bun update -g @openai/codex';
|
|
3705
|
+
matrix.codex.uninstall = 'bun remove -g @openai/codex';
|
|
3706
|
+
return matrix;
|
|
3707
|
+
}
|
|
3708
|
+
matrix.claude.install = 'npm install -g @anthropic-ai/claude-code';
|
|
3709
|
+
matrix.claude.update = 'npm update -g @anthropic-ai/claude-code';
|
|
3710
|
+
matrix.claude.uninstall = 'npm uninstall -g @anthropic-ai/claude-code';
|
|
3711
|
+
matrix.codex.install = 'npm install -g @openai/codex';
|
|
3712
|
+
matrix.codex.update = 'npm update -g @openai/codex';
|
|
3713
|
+
matrix.codex.uninstall = 'npm uninstall -g @openai/codex';
|
|
3714
|
+
return matrix;
|
|
3715
|
+
},
|
|
3716
|
+
|
|
3717
|
+
getInstallCommand(targetId, actionName) {
|
|
3718
|
+
const targetKey = typeof targetId === 'string' ? targetId.trim() : '';
|
|
3719
|
+
if (!targetKey) return '';
|
|
3720
|
+
const action = this.normalizeInstallAction(actionName);
|
|
3721
|
+
const currentMap = this.buildInstallCommandMatrix(this.installPackageManager);
|
|
3722
|
+
const current = currentMap[targetKey] && typeof currentMap[targetKey][action] === 'string'
|
|
3723
|
+
? currentMap[targetKey][action]
|
|
3724
|
+
: '';
|
|
3725
|
+
return this.appendInstallRegistryOption(current, action);
|
|
3726
|
+
},
|
|
3727
|
+
|
|
3728
|
+
setInstallCommandAction(actionName) {
|
|
3729
|
+
this.installCommandAction = this.normalizeInstallAction(actionName);
|
|
3730
|
+
},
|
|
3731
|
+
|
|
3732
|
+
setInstallRegistryPreset(presetName) {
|
|
3733
|
+
this.installRegistryPreset = this.normalizeInstallRegistryPreset(presetName);
|
|
3734
|
+
},
|
|
3735
|
+
|
|
2754
3736
|
openInstallModal() {
|
|
2755
3737
|
this.showInstallModal = true;
|
|
2756
3738
|
},
|
|
@@ -2955,6 +3937,427 @@
|
|
|
2955
3937
|
}
|
|
2956
3938
|
},
|
|
2957
3939
|
|
|
3940
|
+
async downloadClaudeDirectory() {
|
|
3941
|
+
if (this.claudeDownloadLoading) return;
|
|
3942
|
+
this.claudeDownloadLoading = true;
|
|
3943
|
+
this.claudeDownloadProgress = 5;
|
|
3944
|
+
this.claudeDownloadTimer = setInterval(() => {
|
|
3945
|
+
if (this.claudeDownloadProgress < 90) {
|
|
3946
|
+
this.claudeDownloadProgress += 5;
|
|
3947
|
+
}
|
|
3948
|
+
}, 400);
|
|
3949
|
+
try {
|
|
3950
|
+
const res = await api('download-claude-dir');
|
|
3951
|
+
if (res && res.error) {
|
|
3952
|
+
this.showMessage(res.error, 'error');
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
if (!res || res.success !== true || !res.fileName) {
|
|
3956
|
+
this.showMessage('备份失败', 'error');
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3959
|
+
this.claudeDownloadProgress = 100;
|
|
3960
|
+
const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
|
|
3961
|
+
const link = document.createElement('a');
|
|
3962
|
+
link.href = downloadUrl;
|
|
3963
|
+
link.download = res.fileName;
|
|
3964
|
+
document.body.appendChild(link);
|
|
3965
|
+
link.click();
|
|
3966
|
+
document.body.removeChild(link);
|
|
3967
|
+
this.showMessage('备份成功,开始下载', 'success');
|
|
3968
|
+
} catch (e) {
|
|
3969
|
+
this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
3970
|
+
} finally {
|
|
3971
|
+
if (this.claudeDownloadTimer) {
|
|
3972
|
+
clearInterval(this.claudeDownloadTimer);
|
|
3973
|
+
this.claudeDownloadTimer = null;
|
|
3974
|
+
}
|
|
3975
|
+
this.claudeDownloadLoading = false;
|
|
3976
|
+
setTimeout(() => {
|
|
3977
|
+
this.claudeDownloadProgress = 0;
|
|
3978
|
+
}, 800);
|
|
3979
|
+
}
|
|
3980
|
+
},
|
|
3981
|
+
|
|
3982
|
+
async downloadCodexDirectory() {
|
|
3983
|
+
if (this.codexDownloadLoading) return;
|
|
3984
|
+
this.codexDownloadLoading = true;
|
|
3985
|
+
this.codexDownloadProgress = 5;
|
|
3986
|
+
this.codexDownloadTimer = setInterval(() => {
|
|
3987
|
+
if (this.codexDownloadProgress < 90) {
|
|
3988
|
+
this.codexDownloadProgress += 5;
|
|
3989
|
+
}
|
|
3990
|
+
}, 400);
|
|
3991
|
+
try {
|
|
3992
|
+
const res = await api('download-codex-dir');
|
|
3993
|
+
if (res && res.error) {
|
|
3994
|
+
this.showMessage(res.error, 'error');
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
if (!res || res.success !== true || !res.fileName) {
|
|
3998
|
+
this.showMessage('备份失败', 'error');
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
this.codexDownloadProgress = 100;
|
|
4002
|
+
const downloadUrl = `/download/${encodeURIComponent(res.fileName)}`;
|
|
4003
|
+
const link = document.createElement('a');
|
|
4004
|
+
link.href = downloadUrl;
|
|
4005
|
+
link.download = res.fileName;
|
|
4006
|
+
document.body.appendChild(link);
|
|
4007
|
+
link.click();
|
|
4008
|
+
document.body.removeChild(link);
|
|
4009
|
+
this.showMessage('备份成功,开始下载', 'success');
|
|
4010
|
+
} catch (e) {
|
|
4011
|
+
this.showMessage('备份失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
4012
|
+
} finally {
|
|
4013
|
+
if (this.codexDownloadTimer) {
|
|
4014
|
+
clearInterval(this.codexDownloadTimer);
|
|
4015
|
+
this.codexDownloadTimer = null;
|
|
4016
|
+
}
|
|
4017
|
+
this.codexDownloadLoading = false;
|
|
4018
|
+
setTimeout(() => {
|
|
4019
|
+
this.codexDownloadProgress = 0;
|
|
4020
|
+
}, 800);
|
|
4021
|
+
}
|
|
4022
|
+
},
|
|
4023
|
+
|
|
4024
|
+
triggerClaudeImport() {
|
|
4025
|
+
const input = this.$refs.claudeImportInput;
|
|
4026
|
+
if (input) {
|
|
4027
|
+
input.value = '';
|
|
4028
|
+
input.click();
|
|
4029
|
+
}
|
|
4030
|
+
},
|
|
4031
|
+
|
|
4032
|
+
triggerCodexImport() {
|
|
4033
|
+
const input = this.$refs.codexImportInput;
|
|
4034
|
+
if (input) {
|
|
4035
|
+
input.value = '';
|
|
4036
|
+
input.click();
|
|
4037
|
+
}
|
|
4038
|
+
},
|
|
4039
|
+
|
|
4040
|
+
handleClaudeImportChange(event) {
|
|
4041
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
4042
|
+
if (file) {
|
|
4043
|
+
void this.importBackupFile('claude', file);
|
|
4044
|
+
}
|
|
4045
|
+
},
|
|
4046
|
+
|
|
4047
|
+
handleCodexImportChange(event) {
|
|
4048
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
4049
|
+
if (file) {
|
|
4050
|
+
void this.importBackupFile('codex', file);
|
|
4051
|
+
}
|
|
4052
|
+
},
|
|
4053
|
+
|
|
4054
|
+
async importBackupFile(type, file) {
|
|
4055
|
+
const maxSize = 200 * 1024 * 1024;
|
|
4056
|
+
const loadingKey = type === 'claude' ? 'claudeImportLoading' : 'codexImportLoading';
|
|
4057
|
+
if (file.size > maxSize) {
|
|
4058
|
+
this.showMessage('备份文件过大,限制 200MB', 'error');
|
|
4059
|
+
this.resetImportInput(type);
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
4062
|
+
this[loadingKey] = true;
|
|
4063
|
+
try {
|
|
4064
|
+
const base64 = await this.readFileAsBase64(file);
|
|
4065
|
+
const action = type === 'claude' ? 'restore-claude-dir' : 'restore-codex-dir';
|
|
4066
|
+
const res = await api(action, {
|
|
4067
|
+
fileName: file.name || `${type}-backup.zip`,
|
|
4068
|
+
fileBase64: base64
|
|
4069
|
+
});
|
|
4070
|
+
if (res && res.error) {
|
|
4071
|
+
this.showMessage(res.error, 'error');
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
4074
|
+
const backupTip = res && res.backupPath ? `,原配置已备份到临时文件:${res.backupPath}` : '';
|
|
4075
|
+
this.showMessage(`导入成功${backupTip}`, 'success');
|
|
4076
|
+
if (type === 'claude') {
|
|
4077
|
+
await this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
4078
|
+
} else {
|
|
4079
|
+
await this.loadAll();
|
|
4080
|
+
}
|
|
4081
|
+
} catch (e) {
|
|
4082
|
+
this.showMessage('导入失败:' + (e && e.message ? e.message : '未知错误'), 'error');
|
|
4083
|
+
} finally {
|
|
4084
|
+
this[loadingKey] = false;
|
|
4085
|
+
this.resetImportInput(type);
|
|
4086
|
+
}
|
|
4087
|
+
},
|
|
4088
|
+
|
|
4089
|
+
readFileAsBase64(file) {
|
|
4090
|
+
return new Promise((resolve, reject) => {
|
|
4091
|
+
const reader = new FileReader();
|
|
4092
|
+
reader.onload = () => {
|
|
4093
|
+
const result = reader.result;
|
|
4094
|
+
if (result instanceof ArrayBuffer) {
|
|
4095
|
+
resolve(this.arrayBufferToBase64(result));
|
|
4096
|
+
return;
|
|
4097
|
+
}
|
|
4098
|
+
if (typeof result === 'string') {
|
|
4099
|
+
const idx = result.indexOf('base64,');
|
|
4100
|
+
resolve(idx >= 0 ? result.slice(idx + 7) : result);
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
reject(new Error('不支持的文件读取结果'));
|
|
4104
|
+
};
|
|
4105
|
+
reader.onerror = () => reject(new Error('读取文件失败'));
|
|
4106
|
+
reader.readAsArrayBuffer(file);
|
|
4107
|
+
});
|
|
4108
|
+
},
|
|
4109
|
+
|
|
4110
|
+
arrayBufferToBase64(buffer) {
|
|
4111
|
+
const bytes = new Uint8Array(buffer);
|
|
4112
|
+
const chunkSize = 0x8000;
|
|
4113
|
+
let binary = '';
|
|
4114
|
+
for (let i = 0; i < bytes.byteLength; i += chunkSize) {
|
|
4115
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
4116
|
+
}
|
|
4117
|
+
return btoa(binary);
|
|
4118
|
+
},
|
|
4119
|
+
|
|
4120
|
+
resetImportInput(type) {
|
|
4121
|
+
const refName = type === 'claude' ? 'claudeImportInput' : 'codexImportInput';
|
|
4122
|
+
const el = this.$refs[refName];
|
|
4123
|
+
if (el) {
|
|
4124
|
+
el.value = '';
|
|
4125
|
+
}
|
|
4126
|
+
},
|
|
4127
|
+
|
|
4128
|
+
async loadCodexAuthProfiles(options = {}) {
|
|
4129
|
+
const silent = !!options.silent;
|
|
4130
|
+
try {
|
|
4131
|
+
const res = await api('list-auth-profiles');
|
|
4132
|
+
if (res && res.error) {
|
|
4133
|
+
if (!silent) {
|
|
4134
|
+
this.showMessage(res.error, 'error');
|
|
4135
|
+
}
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
const list = Array.isArray(res && res.profiles) ? res.profiles : [];
|
|
4139
|
+
this.codexAuthProfiles = list.sort((a, b) => {
|
|
4140
|
+
if (!!a.current !== !!b.current) {
|
|
4141
|
+
return a.current ? -1 : 1;
|
|
4142
|
+
}
|
|
4143
|
+
return String(a.name || '').localeCompare(String(b.name || ''));
|
|
4144
|
+
});
|
|
4145
|
+
} catch (e) {
|
|
4146
|
+
if (!silent) {
|
|
4147
|
+
this.showMessage('读取认证列表失败', 'error');
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
},
|
|
4151
|
+
|
|
4152
|
+
triggerCodexAuthUpload() {
|
|
4153
|
+
const input = this.$refs.codexAuthImportInput;
|
|
4154
|
+
if (input) {
|
|
4155
|
+
input.value = '';
|
|
4156
|
+
input.click();
|
|
4157
|
+
}
|
|
4158
|
+
},
|
|
4159
|
+
|
|
4160
|
+
handleCodexAuthImportChange(event) {
|
|
4161
|
+
const file = event && event.target && event.target.files ? event.target.files[0] : null;
|
|
4162
|
+
if (file) {
|
|
4163
|
+
void this.importCodexAuthFile(file);
|
|
4164
|
+
}
|
|
4165
|
+
},
|
|
4166
|
+
|
|
4167
|
+
resetCodexAuthImportInput() {
|
|
4168
|
+
const el = this.$refs.codexAuthImportInput;
|
|
4169
|
+
if (el) {
|
|
4170
|
+
el.value = '';
|
|
4171
|
+
}
|
|
4172
|
+
},
|
|
4173
|
+
|
|
4174
|
+
async importCodexAuthFile(file) {
|
|
4175
|
+
this.codexAuthImportLoading = true;
|
|
4176
|
+
try {
|
|
4177
|
+
const base64 = await this.readFileAsBase64(file);
|
|
4178
|
+
const res = await api('import-auth-profile', {
|
|
4179
|
+
fileName: file.name || 'codex-auth.json',
|
|
4180
|
+
fileBase64: base64,
|
|
4181
|
+
activate: true
|
|
4182
|
+
});
|
|
4183
|
+
if (res && res.error) {
|
|
4184
|
+
this.showMessage(res.error, 'error');
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
4188
|
+
this.showMessage('认证文件已导入并切换', 'success');
|
|
4189
|
+
} catch (e) {
|
|
4190
|
+
this.showMessage('导入认证文件失败', 'error');
|
|
4191
|
+
} finally {
|
|
4192
|
+
this.codexAuthImportLoading = false;
|
|
4193
|
+
this.resetCodexAuthImportInput();
|
|
4194
|
+
}
|
|
4195
|
+
},
|
|
4196
|
+
|
|
4197
|
+
async switchCodexAuthProfile(name) {
|
|
4198
|
+
const key = String(name || '').trim();
|
|
4199
|
+
if (!key || this.codexAuthSwitching[key]) return;
|
|
4200
|
+
this.codexAuthSwitching[key] = true;
|
|
4201
|
+
try {
|
|
4202
|
+
const res = await api('switch-auth-profile', { name: key });
|
|
4203
|
+
if (res && res.error) {
|
|
4204
|
+
this.showMessage(res.error, 'error');
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
4208
|
+
this.showMessage(`已切换认证: ${key}`, 'success');
|
|
4209
|
+
} catch (e) {
|
|
4210
|
+
this.showMessage('切换认证失败', 'error');
|
|
4211
|
+
} finally {
|
|
4212
|
+
this.codexAuthSwitching[key] = false;
|
|
4213
|
+
}
|
|
4214
|
+
},
|
|
4215
|
+
|
|
4216
|
+
async deleteCodexAuthProfile(name) {
|
|
4217
|
+
const key = String(name || '').trim();
|
|
4218
|
+
if (!key || this.codexAuthDeleting[key]) return;
|
|
4219
|
+
this.codexAuthDeleting[key] = true;
|
|
4220
|
+
try {
|
|
4221
|
+
const res = await api('delete-auth-profile', { name: key });
|
|
4222
|
+
if (res && res.error) {
|
|
4223
|
+
this.showMessage(res.error, 'error');
|
|
4224
|
+
return;
|
|
4225
|
+
}
|
|
4226
|
+
await this.loadCodexAuthProfiles({ silent: true });
|
|
4227
|
+
const switchedTip = res && res.switchedTo ? `,已切换到 ${res.switchedTo}` : '';
|
|
4228
|
+
this.showMessage(`已删除认证${switchedTip}`, 'success');
|
|
4229
|
+
} catch (e) {
|
|
4230
|
+
this.showMessage('删除认证失败', 'error');
|
|
4231
|
+
} finally {
|
|
4232
|
+
this.codexAuthDeleting[key] = false;
|
|
4233
|
+
}
|
|
4234
|
+
},
|
|
4235
|
+
|
|
4236
|
+
mergeProxySettings(nextSettings) {
|
|
4237
|
+
const safe = nextSettings && typeof nextSettings === 'object' ? nextSettings : {};
|
|
4238
|
+
const port = parseInt(String(safe.port), 10);
|
|
4239
|
+
const timeoutMs = parseInt(String(safe.timeoutMs), 10);
|
|
4240
|
+
this.proxySettings = {
|
|
4241
|
+
enabled: safe.enabled !== false,
|
|
4242
|
+
host: typeof safe.host === 'string' && safe.host.trim() ? safe.host.trim() : '127.0.0.1',
|
|
4243
|
+
port: Number.isFinite(port) ? port : 8318,
|
|
4244
|
+
provider: typeof safe.provider === 'string' ? safe.provider.trim() : '',
|
|
4245
|
+
authSource: safe.authSource === 'profile' || safe.authSource === 'none' ? safe.authSource : 'provider',
|
|
4246
|
+
timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 30000
|
|
4247
|
+
};
|
|
4248
|
+
},
|
|
4249
|
+
|
|
4250
|
+
async loadProxyStatus(options = {}) {
|
|
4251
|
+
const silent = !!options.silent;
|
|
4252
|
+
this.proxyLoading = true;
|
|
4253
|
+
try {
|
|
4254
|
+
const res = await api('proxy-status');
|
|
4255
|
+
if (res && res.error) {
|
|
4256
|
+
if (!silent) {
|
|
4257
|
+
this.showMessage(res.error, 'error');
|
|
4258
|
+
}
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
this.mergeProxySettings(res && res.settings ? res.settings : {});
|
|
4262
|
+
this.proxyRuntime = res && res.runtime ? { running: true, ...res.runtime } : null;
|
|
4263
|
+
} catch (e) {
|
|
4264
|
+
if (!silent) {
|
|
4265
|
+
this.showMessage('读取代理状态失败', 'error');
|
|
4266
|
+
}
|
|
4267
|
+
} finally {
|
|
4268
|
+
this.proxyLoading = false;
|
|
4269
|
+
}
|
|
4270
|
+
},
|
|
4271
|
+
|
|
4272
|
+
async saveProxySettings(options = {}) {
|
|
4273
|
+
const silent = !!options.silent;
|
|
4274
|
+
this.proxySaving = true;
|
|
4275
|
+
try {
|
|
4276
|
+
const res = await api('proxy-save-config', this.proxySettings);
|
|
4277
|
+
if (res && res.error) {
|
|
4278
|
+
if (!silent) {
|
|
4279
|
+
this.showMessage(res.error, 'error');
|
|
4280
|
+
}
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
if (res && res.settings) {
|
|
4284
|
+
this.mergeProxySettings(res.settings);
|
|
4285
|
+
}
|
|
4286
|
+
if (!silent) {
|
|
4287
|
+
this.showMessage('代理配置已保存', 'success');
|
|
4288
|
+
}
|
|
4289
|
+
} catch (e) {
|
|
4290
|
+
if (!silent) {
|
|
4291
|
+
this.showMessage('保存代理配置失败', 'error');
|
|
4292
|
+
}
|
|
4293
|
+
} finally {
|
|
4294
|
+
this.proxySaving = false;
|
|
4295
|
+
}
|
|
4296
|
+
},
|
|
4297
|
+
|
|
4298
|
+
async startBuiltinProxy() {
|
|
4299
|
+
this.proxyStarting = true;
|
|
4300
|
+
try {
|
|
4301
|
+
const res = await api('proxy-start', {
|
|
4302
|
+
...this.proxySettings,
|
|
4303
|
+
enabled: true
|
|
4304
|
+
});
|
|
4305
|
+
if (res && res.error) {
|
|
4306
|
+
this.showMessage(res.error, 'error');
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
if (res && res.settings) {
|
|
4310
|
+
this.mergeProxySettings(res.settings);
|
|
4311
|
+
}
|
|
4312
|
+
await this.loadProxyStatus({ silent: true });
|
|
4313
|
+
const listenTip = res && res.listenUrl ? `:${res.listenUrl}` : '';
|
|
4314
|
+
this.showMessage(`代理已启动${listenTip}`, 'success');
|
|
4315
|
+
} catch (e) {
|
|
4316
|
+
this.showMessage('启动代理失败', 'error');
|
|
4317
|
+
} finally {
|
|
4318
|
+
this.proxyStarting = false;
|
|
4319
|
+
}
|
|
4320
|
+
},
|
|
4321
|
+
|
|
4322
|
+
async stopBuiltinProxy() {
|
|
4323
|
+
this.proxyStopping = true;
|
|
4324
|
+
try {
|
|
4325
|
+
const res = await api('proxy-stop');
|
|
4326
|
+
if (res && res.error) {
|
|
4327
|
+
this.showMessage(res.error, 'error');
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
await this.loadProxyStatus({ silent: true });
|
|
4331
|
+
this.showMessage('代理已停止', 'success');
|
|
4332
|
+
} catch (e) {
|
|
4333
|
+
this.showMessage('停止代理失败', 'error');
|
|
4334
|
+
} finally {
|
|
4335
|
+
this.proxyStopping = false;
|
|
4336
|
+
}
|
|
4337
|
+
},
|
|
4338
|
+
|
|
4339
|
+
async applyBuiltinProxyProvider() {
|
|
4340
|
+
this.proxyApplying = true;
|
|
4341
|
+
try {
|
|
4342
|
+
const saveRes = await api('proxy-save-config', this.proxySettings);
|
|
4343
|
+
if (saveRes && saveRes.error) {
|
|
4344
|
+
this.showMessage(saveRes.error, 'error');
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
const res = await api('proxy-apply-provider', { switchToProxy: true });
|
|
4348
|
+
if (res && res.error) {
|
|
4349
|
+
this.showMessage(res.error, 'error');
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4352
|
+
await this.loadAll();
|
|
4353
|
+
this.showMessage('本地代理 provider 已写入并切换', 'success');
|
|
4354
|
+
} catch (e) {
|
|
4355
|
+
this.showMessage('应用代理 provider 失败', 'error');
|
|
4356
|
+
} finally {
|
|
4357
|
+
this.proxyApplying = false;
|
|
4358
|
+
}
|
|
4359
|
+
},
|
|
4360
|
+
|
|
2958
4361
|
showMessage(text, type) {
|
|
2959
4362
|
this.message = text;
|
|
2960
4363
|
this.messageType = type || 'info';
|