codexmate 0.0.14 → 0.0.15
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 +146 -362
- package/README.md +146 -361
- package/cli.js +662 -36
- package/doc/CHANGELOG.md +14 -9
- package/doc/CHANGELOG.zh-CN.md +7 -0
- package/package.json +3 -3
- package/web-ui/app.js +24 -324
- package/web-ui/index.html +45 -22
- package/web-ui/modules/config-mode.computed.mjs +123 -0
- package/web-ui/modules/skills.computed.mjs +82 -0
- package/web-ui/modules/skills.methods.mjs +344 -0
package/doc/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.0.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
- Release: bump package version to 0.0.15
|
|
6
|
+
- Docs: sync README / README.en with current release marker
|
|
7
|
+
|
|
8
|
+
## 0.0.14
|
|
9
|
+
|
|
10
|
+
- Skills Manager: polish modal layout with overview counters and clearer section structure
|
|
11
|
+
- Skills Manager: unify status select style and refine list scrollbar density
|
|
12
|
+
- Docs: sync README / README.en release notes for 0.0.14
|
|
13
|
+
|
|
14
|
+
## 0.0.13
|
|
10
15
|
|
|
11
16
|
- Web UI: switch to IDE-style three-column layout with a fixed status inspector panel
|
|
12
17
|
- AGENTS editor: add "Export" action to download current content as `agent-<timestamp>.txt`
|
package/doc/CHANGELOG.zh-CN.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
{
|
|
2
2
|
"name": "codexmate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Codex/Claude Code 配置与会话管理 CLI + Web 工具",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@iarna/toml": "^2.2.5",
|
|
39
39
|
"json5": "^2.2.3",
|
|
40
|
+
"yauzl": "^3.2.1",
|
|
40
41
|
"zip-lib": "^1.2.1"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
@@ -60,4 +61,3 @@
|
|
|
60
61
|
"vitepress": "^1.6.4"
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
|
package/web-ui/app.js
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
buildSessionTimelineNodes,
|
|
15
15
|
normalizeSessionMessageRole
|
|
16
16
|
} from './logic.mjs';
|
|
17
|
+
import {
|
|
18
|
+
CONFIG_MODE_SET,
|
|
19
|
+
getProviderConfigModeMeta,
|
|
20
|
+
createConfigModeComputed
|
|
21
|
+
} from './modules/config-mode.computed.mjs';
|
|
22
|
+
import { createSkillsComputed } from './modules/skills.computed.mjs';
|
|
23
|
+
import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
17
24
|
|
|
18
25
|
document.addEventListener('DOMContentLoaded', () => {
|
|
19
26
|
if (typeof Vue === 'undefined') {
|
|
@@ -111,6 +118,8 @@
|
|
|
111
118
|
skillsImportSelectedKeys: [],
|
|
112
119
|
skillsScanningImports: false,
|
|
113
120
|
skillsImporting: false,
|
|
121
|
+
skillsZipImporting: false,
|
|
122
|
+
skillsExporting: false,
|
|
114
123
|
sessionsList: [],
|
|
115
124
|
sessionsLoading: false,
|
|
116
125
|
sessionFilterSource: 'all',
|
|
@@ -400,141 +409,10 @@
|
|
|
400
409
|
installRegistryPreview() {
|
|
401
410
|
return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
|
|
402
411
|
},
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
},
|
|
412
|
+
...createSkillsComputed(),
|
|
413
|
+
|
|
414
|
+
...createConfigModeComputed(),
|
|
415
|
+
|
|
538
416
|
inspectorBusyStatus() {
|
|
539
417
|
const tasks = [];
|
|
540
418
|
if (this.loading) tasks.push('初始化');
|
|
@@ -542,7 +420,7 @@
|
|
|
542
420
|
if (this.codexModelsLoading || this.claudeModelsLoading) tasks.push('模型加载');
|
|
543
421
|
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
544
422
|
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
545
|
-
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting) tasks.push('Skills 管理');
|
|
423
|
+
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push('Skills 管理');
|
|
546
424
|
if (this.proxySaving || this.proxyApplying || this.proxyStarting || this.proxyStopping) tasks.push('代理更新');
|
|
547
425
|
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
548
426
|
},
|
|
@@ -846,9 +724,12 @@
|
|
|
846
724
|
},
|
|
847
725
|
|
|
848
726
|
switchConfigMode(mode) {
|
|
727
|
+
const normalizedMode = typeof mode === 'string'
|
|
728
|
+
? mode.trim().toLowerCase()
|
|
729
|
+
: '';
|
|
849
730
|
this.mainTab = 'config';
|
|
850
|
-
this.configMode =
|
|
851
|
-
if (
|
|
731
|
+
this.configMode = CONFIG_MODE_SET.has(normalizedMode) ? normalizedMode : 'codex';
|
|
732
|
+
if (this.configMode === 'claude') {
|
|
852
733
|
this.refreshClaudeModelContext();
|
|
853
734
|
}
|
|
854
735
|
},
|
|
@@ -1876,7 +1757,9 @@
|
|
|
1876
1757
|
if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
|
|
1877
1758
|
this.currentModel = this.models[0];
|
|
1878
1759
|
}
|
|
1879
|
-
|
|
1760
|
+
if (getProviderConfigModeMeta(this.configMode)) {
|
|
1761
|
+
await this.applyCodexConfigDirect({ silent: true });
|
|
1762
|
+
}
|
|
1880
1763
|
},
|
|
1881
1764
|
|
|
1882
1765
|
async onModelChange() {
|
|
@@ -2080,191 +1963,8 @@
|
|
|
2080
1963
|
}
|
|
2081
1964
|
},
|
|
2082
1965
|
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
-
|
|
1966
|
+
...createSkillsMethods({ api }),
|
|
1967
|
+
|
|
2268
1968
|
async openOpenclawAgentsEditor() {
|
|
2269
1969
|
this.setAgentsModalContext('openclaw');
|
|
2270
1970
|
this.agentsLoading = true;
|
package/web-ui/index.html
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
60
60
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
61
61
|
:aria-pressed="mainTab === 'config' && configMode === 'codex'"
|
|
62
|
-
aria-controls="panel-config-
|
|
62
|
+
aria-controls="panel-config-provider"
|
|
63
63
|
:class="{ active: mainTab === 'config' && configMode === 'codex' }"
|
|
64
64
|
@click="switchConfigMode('codex')">Codex 配置</button>
|
|
65
65
|
<button class="top-tab"
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
<button
|
|
138
138
|
role="tab"
|
|
139
139
|
id="side-tab-config-codex"
|
|
140
|
-
aria-controls="panel-config-
|
|
140
|
+
aria-controls="panel-config-provider"
|
|
141
141
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
142
142
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
143
143
|
:aria-pressed="mainTab === 'config' && configMode === 'codex'"
|
|
@@ -233,13 +233,13 @@
|
|
|
233
233
|
</div>
|
|
234
234
|
|
|
235
235
|
<div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
|
|
236
|
-
<template v-if="
|
|
236
|
+
<template v-if="isProviderConfigMode">
|
|
237
237
|
<div class="status-chip">
|
|
238
|
-
<span class="label">
|
|
238
|
+
<span class="label">{{ activeProviderConfigChipLabel }}</span>
|
|
239
239
|
<span class="value">{{ currentProvider || '未选择' }}</span>
|
|
240
240
|
</div>
|
|
241
241
|
<div class="status-chip">
|
|
242
|
-
<span class="label">
|
|
242
|
+
<span class="label">{{ activeProviderModelChipLabel }}</span>
|
|
243
243
|
<span class="value">{{ currentModel || '未选择' }}</span>
|
|
244
244
|
</div>
|
|
245
245
|
</template>
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
<span class="value">{{ currentClaudeModel || '未选择' }}</span>
|
|
254
254
|
</div>
|
|
255
255
|
</template>
|
|
256
|
-
<template v-else>
|
|
256
|
+
<template v-else-if="configMode === 'openclaw'">
|
|
257
257
|
<div class="status-chip">
|
|
258
258
|
<span class="label">OpenClaw 配置</span>
|
|
259
259
|
<span class="value">{{ currentOpenclawConfig || '未选择' }}</span>
|
|
@@ -263,6 +263,12 @@
|
|
|
263
263
|
<span class="value">{{ openclawWorkspaceFileName || '未选择' }}</span>
|
|
264
264
|
</div>
|
|
265
265
|
</template>
|
|
266
|
+
<template v-else>
|
|
267
|
+
<div class="status-chip">
|
|
268
|
+
<span class="label">配置模式</span>
|
|
269
|
+
<span class="value">未选择</span>
|
|
270
|
+
</div>
|
|
271
|
+
</template>
|
|
266
272
|
</div>
|
|
267
273
|
<div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'sessions'">
|
|
268
274
|
<div class="status-chip">
|
|
@@ -291,13 +297,13 @@
|
|
|
291
297
|
|
|
292
298
|
<!-- 内容包裹器 - 稳定布局 -->
|
|
293
299
|
<div class="content-wrapper">
|
|
294
|
-
<!-- Codex
|
|
300
|
+
<!-- Provider 配置模式(Codex) -->
|
|
295
301
|
<div
|
|
296
|
-
v-show="mainTab === 'config' &&
|
|
302
|
+
v-show="mainTab === 'config' && isProviderConfigMode"
|
|
297
303
|
class="mode-content mode-cards"
|
|
298
|
-
id="panel-config-
|
|
304
|
+
id="panel-config-provider"
|
|
299
305
|
role="tabpanel"
|
|
300
|
-
:aria-labelledby="'tab-config-
|
|
306
|
+
:aria-labelledby="'tab-config-' + configMode">
|
|
301
307
|
<!-- 添加提供商按钮 -->
|
|
302
308
|
<button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
|
|
303
309
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -330,7 +336,7 @@
|
|
|
330
336
|
class="model-input"
|
|
331
337
|
v-model="currentModel"
|
|
332
338
|
@blur="onModelChange"
|
|
333
|
-
placeholder="
|
|
339
|
+
:placeholder="activeProviderModelPlaceholder"
|
|
334
340
|
>
|
|
335
341
|
<div class="config-template-hint" v-if="modelsSource === 'unlimited'">
|
|
336
342
|
当前提供商未提供模型列表,视为不限。模型可手动输入。
|
|
@@ -339,16 +345,20 @@
|
|
|
339
345
|
模型列表获取失败,请检查接口或手动输入。
|
|
340
346
|
</div>
|
|
341
347
|
<div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
|
|
342
|
-
当前模型不在接口列表中,请手动输入或在模板中调整。
|
|
348
|
+
{{ isCodexConfigMode ? '当前模型不在接口列表中,请手动输入或在模板中调整。' : '当前模型不在接口列表中,请手动输入。' }}
|
|
343
349
|
</div>
|
|
344
|
-
<div class="config-template-hint">
|
|
350
|
+
<div class="config-template-hint" v-if="isCodexConfigMode">
|
|
345
351
|
Codex 配置需先改模板,再手动应用。
|
|
346
352
|
</div>
|
|
347
|
-
<
|
|
353
|
+
<div class="config-template-hint" v-else-if="activeProviderBridgeHint">
|
|
354
|
+
{{ activeProviderBridgeHint }} 模板、认证和代理仅在 Codex 模式下可编辑。
|
|
355
|
+
</div>
|
|
356
|
+
<button class="btn-tool btn-template-editor" v-if="isCodexConfigMode" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
|
|
348
357
|
打开 Config 模板编辑器
|
|
349
358
|
</button>
|
|
350
359
|
</div>
|
|
351
360
|
|
|
361
|
+
<template v-if="isCodexConfigMode">
|
|
352
362
|
<div class="selector-section">
|
|
353
363
|
<div class="selector-header">
|
|
354
364
|
<span class="selector-title">服务档位</span>
|
|
@@ -390,8 +400,8 @@
|
|
|
390
400
|
<div class="selector-header">
|
|
391
401
|
<span class="selector-title">Skills 管理</span>
|
|
392
402
|
</div>
|
|
393
|
-
<div class="config-template-hint skills-hint-line">管理 <code>~/.codex/skills</code> 自定义 skills
|
|
394
|
-
<button class="btn-tool" @click="openSkillsManager" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
403
|
+
<div class="config-template-hint skills-hint-line">管理 <code>~/.codex/skills</code> 自定义 skills,弹窗提供统计概览、筛选检索、多选删除、ZIP 导入与导出。</div>
|
|
404
|
+
<button class="btn-tool" @click="openSkillsManager" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
395
405
|
{{ skillsLoading ? '加载中...' : '打开 Skills 管理' }}
|
|
396
406
|
</button>
|
|
397
407
|
</div>
|
|
@@ -502,6 +512,7 @@
|
|
|
502
512
|
<span v-if="proxyRuntime.upstreamProvider">(上游 <code>{{ proxyRuntime.upstreamProvider }}</code>)</span>
|
|
503
513
|
</div>
|
|
504
514
|
</div>
|
|
515
|
+
</template>
|
|
505
516
|
|
|
506
517
|
<div class="selector-section">
|
|
507
518
|
<div class="selector-header">
|
|
@@ -1672,10 +1683,10 @@
|
|
|
1672
1683
|
<div class="modal-header skills-modal-header">
|
|
1673
1684
|
<div>
|
|
1674
1685
|
<div class="modal-title">Skills 管理</div>
|
|
1675
|
-
<div class="skills-modal-subtitle"
|
|
1686
|
+
<div class="skills-modal-subtitle">集中管理本地技能目录,支持检索筛选、多选删除、跨应用导入、ZIP 导入与导出。</div>
|
|
1676
1687
|
</div>
|
|
1677
1688
|
<div class="modal-header-actions skills-modal-actions">
|
|
1678
|
-
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
1689
|
+
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1679
1690
|
{{ skillsLoading ? '刷新中...' : '刷新' }}
|
|
1680
1691
|
</button>
|
|
1681
1692
|
</div>
|
|
@@ -1771,7 +1782,7 @@
|
|
|
1771
1782
|
<div class="skills-import-title">跨应用导入(对齐 cc-switch 能力)</div>
|
|
1772
1783
|
<div class="skills-panel-note">从其他应用扫描并导入未托管技能,支持多选批量导入。</div>
|
|
1773
1784
|
</div>
|
|
1774
|
-
<button class="btn-mini" @click="scanImportableSkills" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting">
|
|
1785
|
+
<button class="btn-mini" @click="scanImportableSkills" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1775
1786
|
{{ skillsScanningImports ? '扫描中...' : '扫描可导入' }}
|
|
1776
1787
|
</button>
|
|
1777
1788
|
</div>
|
|
@@ -1806,14 +1817,26 @@
|
|
|
1806
1817
|
</div>
|
|
1807
1818
|
|
|
1808
1819
|
<div class="btn-group">
|
|
1809
|
-
<button class="btn btn-cancel" @click="closeSkillsModal" :disabled="skillsDeleting || skillsImporting || skillsScanningImports">关闭</button>
|
|
1810
|
-
<button class="btn btn-
|
|
1820
|
+
<button class="btn btn-cancel" @click="closeSkillsModal" :disabled="skillsLoading || skillsDeleting || skillsImporting || skillsScanningImports || skillsZipImporting || skillsExporting">关闭</button>
|
|
1821
|
+
<button class="btn btn-cancel" @click="triggerSkillsZipImport" :disabled="skillsZipImporting || skillsDeleting || skillsImporting || skillsScanningImports || skillsExporting">
|
|
1822
|
+
{{ skillsZipImporting ? 'ZIP 导入中...' : '导入 ZIP' }}
|
|
1823
|
+
</button>
|
|
1824
|
+
<button class="btn btn-confirm" @click="exportSelectedSkills" :disabled="skillsExporting || skillsSelectedCount === 0 || skillsDeleting || skillsImporting || skillsScanningImports || skillsZipImporting">
|
|
1825
|
+
{{ skillsExporting ? '导出中...' : '导出选中' }}
|
|
1826
|
+
</button>
|
|
1827
|
+
<button class="btn btn-confirm" @click="importSelectedSkills" :disabled="skillsImporting || skillsImportSelectedCount === 0 || skillsZipImporting || skillsExporting || skillsDeleting">
|
|
1811
1828
|
{{ skillsImporting ? '导入中...' : '导入选中' }}
|
|
1812
1829
|
</button>
|
|
1813
|
-
<button class="btn btn-confirm btn-danger" @click="deleteSelectedSkills" :disabled="skillsDeleting || skillsSelectedCount === 0">
|
|
1830
|
+
<button class="btn btn-confirm btn-danger" @click="deleteSelectedSkills" :disabled="skillsDeleting || skillsSelectedCount === 0 || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1814
1831
|
{{ skillsDeleting ? '删除中...' : '删除选中' }}
|
|
1815
1832
|
</button>
|
|
1816
1833
|
</div>
|
|
1834
|
+
<input
|
|
1835
|
+
ref="skillsZipImportInput"
|
|
1836
|
+
type="file"
|
|
1837
|
+
accept=".zip,application/zip"
|
|
1838
|
+
style="display:none"
|
|
1839
|
+
@change="handleSkillsZipImportChange">
|
|
1817
1840
|
</div>
|
|
1818
1841
|
</div>
|
|
1819
1842
|
|