codexmate 0.0.38 → 0.0.39
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/cli/builtin-proxy.js +626 -207
- package/cli/openai-bridge.js +541 -210
- package/cli.js +19 -1
- package/package.json +1 -1
- package/web-ui/app.js +12 -3
- package/web-ui/modules/app.computed.main-tabs.mjs +37 -30
- package/web-ui/modules/app.methods.claude-config.mjs +111 -9
- package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
- package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
- package/web-ui/modules/app.methods.providers.mjs +36 -10
- package/web-ui/modules/app.methods.runtime.mjs +76 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +1 -0
- package/web-ui/modules/config-mode.computed.mjs +3 -3
- package/web-ui/modules/i18n.dict.mjs +13 -0
- package/web-ui/modules/i18n.mjs +65 -16
- package/web-ui/partials/index/layout-header.html +16 -46
- package/web-ui/partials/index/modal-openclaw-config.html +135 -71
- package/web-ui/partials/index/modal-webhook.html +8 -8
- package/web-ui/partials/index/modals-basic.html +56 -16
- package/web-ui/partials/index/panel-config-claude.html +20 -20
- package/web-ui/partials/index/panel-config-codex.html +5 -5
- package/web-ui/partials/index/panel-config-openclaw.html +70 -64
- package/web-ui/partials/index/panel-dashboard.html +62 -77
- package/web-ui/partials/index/panel-settings.html +28 -7
- package/web-ui/partials/index/panel-trash.html +14 -14
- package/web-ui/res/web-ui-render.precompiled.js +846 -539
- package/web-ui/styles/controls-forms.css +6 -0
- package/web-ui/styles/dashboard.css +46 -14
- package/web-ui/styles/layout-shell.css +45 -0
- package/web-ui/styles/navigation-panels.css +3 -3
- package/web-ui/styles/openclaw-structured.css +383 -33
- package/web-ui/styles/responsive.css +68 -0
- package/web-ui/styles/sessions-usage.css +105 -9
- package/web-ui/styles/settings-panel.css +4 -0
package/cli.js
CHANGED
|
@@ -2081,12 +2081,22 @@ function addProviderToConfig(params = {}) {
|
|
|
2081
2081
|
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
2082
2082
|
const url = typeof params.url === 'string' ? params.url.trim() : '';
|
|
2083
2083
|
const key = typeof params.key === 'string' ? params.key.trim() : '';
|
|
2084
|
+
const requireModel = !!params.requireModel;
|
|
2085
|
+
const fallbackModel = (() => {
|
|
2086
|
+
if (requireModel) return '';
|
|
2087
|
+
const list = readModels();
|
|
2088
|
+
return Array.isArray(list) && typeof list[0] === 'string' ? list[0].trim() : '';
|
|
2089
|
+
})();
|
|
2090
|
+
const model = typeof params.model === 'string' && params.model.trim()
|
|
2091
|
+
? params.model.trim()
|
|
2092
|
+
: fallbackModel;
|
|
2084
2093
|
const useTransform = !!params.useTransform;
|
|
2085
2094
|
const allowManaged = !!params.allowManaged;
|
|
2086
2095
|
const normalizedUrl = normalizeBaseUrl(url);
|
|
2087
2096
|
|
|
2088
2097
|
if (!name) return { error: '名称不能为空' };
|
|
2089
2098
|
if (!url) return { error: 'URL 不能为空' };
|
|
2099
|
+
if (!model) return { error: '模型名称不能为空' };
|
|
2090
2100
|
if (!isValidProviderName(name)) {
|
|
2091
2101
|
return { error: '名称仅支持字母/数字/._-' };
|
|
2092
2102
|
}
|
|
@@ -2163,6 +2173,7 @@ function addProviderToConfig(params = {}) {
|
|
|
2163
2173
|
`wire_api = "responses"`,
|
|
2164
2174
|
`requires_openai_auth = ${requiresOpenaiAuth ? 'true' : 'false'}`,
|
|
2165
2175
|
`preferred_auth_method = "${safeKey}"`,
|
|
2176
|
+
`models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
|
|
2166
2177
|
...extraLines,
|
|
2167
2178
|
`request_max_retries = 4`,
|
|
2168
2179
|
`stream_max_retries = 10`,
|
|
@@ -2173,6 +2184,13 @@ function addProviderToConfig(params = {}) {
|
|
|
2173
2184
|
|
|
2174
2185
|
try {
|
|
2175
2186
|
writeConfig(newContent);
|
|
2187
|
+
const models = readModels();
|
|
2188
|
+
if (!models.includes(model)) {
|
|
2189
|
+
writeModels([...models, model]);
|
|
2190
|
+
}
|
|
2191
|
+
const currentModels = readCurrentModels();
|
|
2192
|
+
currentModels[name] = model;
|
|
2193
|
+
writeCurrentModels(currentModels);
|
|
2176
2194
|
} catch (e) {
|
|
2177
2195
|
return { error: `写入配置失败: ${e.message}` };
|
|
2178
2196
|
}
|
|
@@ -10866,7 +10884,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
10866
10884
|
result = buildConfigTemplateDiff(params || {});
|
|
10867
10885
|
break;
|
|
10868
10886
|
case 'add-provider':
|
|
10869
|
-
result = addProviderToConfig(params || {});
|
|
10887
|
+
result = addProviderToConfig({ ...(params || {}), requireModel: true });
|
|
10870
10888
|
break;
|
|
10871
10889
|
case 'update-provider':
|
|
10872
10890
|
result = updateProviderInConfig(params || {});
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -62,11 +62,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
62
62
|
messageType: '',
|
|
63
63
|
showAddModal: false,
|
|
64
64
|
showEditModal: false,
|
|
65
|
+
showAddProviderKey: false,
|
|
65
66
|
showEditProviderKey: false,
|
|
66
67
|
showModelModal: false,
|
|
67
68
|
showModelListModal: false,
|
|
68
69
|
showClaudeConfigModal: false,
|
|
69
70
|
showEditConfigModal: false,
|
|
71
|
+
showAddClaudeConfigKey: false,
|
|
70
72
|
showEditClaudeConfigKey: false,
|
|
71
73
|
showOpenclawConfigModal: false,
|
|
72
74
|
showConfigTemplateModal: false,
|
|
@@ -268,7 +270,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
268
270
|
installRegistryPreset: 'default',
|
|
269
271
|
installRegistryCustom: '',
|
|
270
272
|
installStatusTargets: null,
|
|
271
|
-
newProvider: { name: '', url: '', key: '',
|
|
273
|
+
newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
|
|
272
274
|
resetConfigLoading: false,
|
|
273
275
|
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
274
276
|
newModelName: '',
|
|
@@ -293,7 +295,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
293
295
|
currentOpenclawConfig: '',
|
|
294
296
|
openclawConfigs: {
|
|
295
297
|
'默认配置': {
|
|
296
|
-
content: DEFAULT_OPENCLAW_TEMPLATE
|
|
298
|
+
content: DEFAULT_OPENCLAW_TEMPLATE,
|
|
299
|
+
isDefault: true
|
|
297
300
|
}
|
|
298
301
|
},
|
|
299
302
|
openclawEditing: { name: '', content: '', lockName: false },
|
|
@@ -343,6 +346,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
343
346
|
overrideModels: true,
|
|
344
347
|
showKey: false
|
|
345
348
|
},
|
|
349
|
+
openclawAccordionStep: 1,
|
|
350
|
+
openclawValidation: {
|
|
351
|
+
providerName: { valid: true, message: '' },
|
|
352
|
+
modelId: { valid: true, message: '' }
|
|
353
|
+
},
|
|
346
354
|
openclawAgentsList: [],
|
|
347
355
|
openclawProviders: [],
|
|
348
356
|
openclawMissingProviders: [],
|
|
@@ -567,7 +575,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
567
575
|
: { content: DEFAULT_OPENCLAW_TEMPLATE };
|
|
568
576
|
const normalized = {
|
|
569
577
|
'默认配置': {
|
|
570
|
-
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE
|
|
578
|
+
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : DEFAULT_OPENCLAW_TEMPLATE,
|
|
579
|
+
isDefault: true
|
|
571
580
|
}
|
|
572
581
|
};
|
|
573
582
|
for (const [name, value] of Object.entries(source)) {
|
|
@@ -43,93 +43,97 @@ function readTaskOrchestrationDraftMetrics(taskOrchestration) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
46
|
+
function translateTaskText(t, key, fallback, params = null) {
|
|
47
|
+
return typeof t === 'function' ? t(key, params) : fallback;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createTaskDraftChecklist(metrics, t = null) {
|
|
47
51
|
const workflowReady = metrics.engine !== 'workflow' || metrics.workflowCount > 0;
|
|
48
52
|
const scopeReady = metrics.hasNotes || !metrics.allowWrite;
|
|
49
53
|
const previewReady = metrics.hasPlan && metrics.planIssues.length === 0;
|
|
50
54
|
return [
|
|
51
55
|
{
|
|
52
56
|
key: 'target',
|
|
53
|
-
label: '目标',
|
|
57
|
+
label: translateTaskText(t, 'orchestration.readiness.target.label', '目标'),
|
|
54
58
|
done: metrics.hasTarget,
|
|
55
|
-
detail: metrics.hasTarget ? '已写目标' : '还没写目标'
|
|
59
|
+
detail: metrics.hasTarget ? translateTaskText(t, 'orchestration.readiness.target.done', '已写目标') : translateTaskText(t, 'orchestration.readiness.target.missing', '还没写目标')
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
key: 'engine',
|
|
59
|
-
label: metrics.engine === 'workflow' ? 'Workflow' : '执行策略',
|
|
63
|
+
label: metrics.engine === 'workflow' ? 'Workflow' : translateTaskText(t, 'orchestration.readiness.engine.label', '执行策略'),
|
|
60
64
|
done: workflowReady,
|
|
61
65
|
detail: metrics.engine === 'workflow'
|
|
62
|
-
? (metrics.workflowCount > 0 ? `已选 ${metrics.workflowCount} 个 Workflow
|
|
63
|
-
: '使用 Codex 规划节点'
|
|
66
|
+
? (metrics.workflowCount > 0 ? translateTaskText(t, 'orchestration.readiness.workflow.done', `已选 ${metrics.workflowCount} 个 Workflow`, { count: metrics.workflowCount }) : translateTaskText(t, 'orchestration.readiness.workflow.missing', '还没选 Workflow ID'))
|
|
67
|
+
: translateTaskText(t, 'orchestration.readiness.engine.codex', '使用 Codex 规划节点')
|
|
64
68
|
},
|
|
65
69
|
{
|
|
66
70
|
key: 'scope',
|
|
67
|
-
label: '边界',
|
|
71
|
+
label: translateTaskText(t, 'orchestration.readiness.scope.label', '边界'),
|
|
68
72
|
done: scopeReady,
|
|
69
73
|
detail: metrics.hasNotes
|
|
70
|
-
? '已补充说明'
|
|
71
|
-
: (metrics.allowWrite ? '建议补说明后再写入' : '当前是只读,可直接试')
|
|
74
|
+
? translateTaskText(t, 'orchestration.readiness.scope.done', '已补充说明')
|
|
75
|
+
: (metrics.allowWrite ? translateTaskText(t, 'orchestration.readiness.scope.writeHint', '建议补说明后再写入') : translateTaskText(t, 'orchestration.readiness.scope.readonlyHint', '当前是只读,可直接试'))
|
|
72
76
|
},
|
|
73
77
|
{
|
|
74
78
|
key: 'preview',
|
|
75
|
-
label: '预览',
|
|
79
|
+
label: translateTaskText(t, 'orchestration.readiness.preview.label', '预览'),
|
|
76
80
|
done: previewReady,
|
|
77
81
|
detail: !metrics.hasPlan
|
|
78
|
-
? '还没生成计划'
|
|
79
|
-
: (metrics.planIssues.length > 0 ? `有 ${metrics.planIssues.length}
|
|
82
|
+
? translateTaskText(t, 'orchestration.readiness.preview.missing', '还没生成计划')
|
|
83
|
+
: (metrics.planIssues.length > 0 ? translateTaskText(t, 'orchestration.readiness.preview.blocked', `有 ${metrics.planIssues.length} 个阻塞项`, { count: metrics.planIssues.length }) : translateTaskText(t, 'orchestration.readiness.preview.ready', `计划可用,${metrics.planNodeCount} 个节点`, { count: metrics.planNodeCount }))
|
|
80
84
|
}
|
|
81
85
|
];
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
function createTaskDraftReadiness(metrics) {
|
|
88
|
+
function createTaskDraftReadiness(metrics, t = null) {
|
|
85
89
|
if (!metrics.hasTarget) {
|
|
86
90
|
return {
|
|
87
91
|
tone: 'neutral',
|
|
88
|
-
title: '先写目标',
|
|
89
|
-
summary: '先把想完成的结果写清楚,再让编排器拆节点。'
|
|
92
|
+
title: translateTaskText(t, 'orchestration.readiness.empty.title', '先写目标'),
|
|
93
|
+
summary: translateTaskText(t, 'orchestration.readiness.empty.summary', '先把想完成的结果写清楚,再让编排器拆节点。')
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
if (metrics.engine === 'workflow' && metrics.workflowCount === 0) {
|
|
93
97
|
return {
|
|
94
98
|
tone: 'warn',
|
|
95
|
-
title: '缺少 Workflow',
|
|
96
|
-
summary: '你已经选了 Workflow 模式,但还没指定可复用流程。'
|
|
99
|
+
title: translateTaskText(t, 'orchestration.readiness.workflow.title', '缺少 Workflow'),
|
|
100
|
+
summary: translateTaskText(t, 'orchestration.readiness.workflow.summary', '你已经选了 Workflow 模式,但还没指定可复用流程。')
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
103
|
if (!metrics.hasPlan) {
|
|
100
104
|
return {
|
|
101
105
|
tone: 'warn',
|
|
102
|
-
title: '建议先预览',
|
|
103
|
-
summary: '草稿已成形,先生成一次计划,确认节点和依赖再执行。'
|
|
106
|
+
title: translateTaskText(t, 'orchestration.readiness.preview.title', '建议先预览'),
|
|
107
|
+
summary: translateTaskText(t, 'orchestration.readiness.preview.summary', '草稿已成形,先生成一次计划,确认节点和依赖再执行。')
|
|
104
108
|
};
|
|
105
109
|
}
|
|
106
110
|
if (metrics.planIssues.length > 0) {
|
|
107
111
|
return {
|
|
108
112
|
tone: 'error',
|
|
109
|
-
title: '预览有阻塞',
|
|
110
|
-
summary: `当前计划里还有 ${metrics.planIssues.length}
|
|
113
|
+
title: translateTaskText(t, 'orchestration.readiness.blocked.title', '预览有阻塞'),
|
|
114
|
+
summary: translateTaskText(t, 'orchestration.readiness.blocked.summary', `当前计划里还有 ${metrics.planIssues.length} 个阻塞项,先处理它们。`, { count: metrics.planIssues.length })
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
117
|
if (metrics.planWarnings.length > 0) {
|
|
114
118
|
return {
|
|
115
119
|
tone: 'warn',
|
|
116
|
-
title: '可以执行,但有提醒',
|
|
117
|
-
summary: `计划已生成,但还有 ${metrics.planWarnings.length}
|
|
120
|
+
title: translateTaskText(t, 'orchestration.readiness.warn.title', '可以执行,但有提醒'),
|
|
121
|
+
summary: translateTaskText(t, 'orchestration.readiness.warn.summary', `计划已生成,但还有 ${metrics.planWarnings.length} 条提醒值得先看一眼。`, { count: metrics.planWarnings.length })
|
|
118
122
|
};
|
|
119
123
|
}
|
|
120
124
|
if (metrics.dryRun) {
|
|
121
125
|
return {
|
|
122
126
|
tone: 'success',
|
|
123
|
-
title: '适合先预演',
|
|
124
|
-
summary: '现在可以安全地跑一次仅预演,先看结果再决定是否真实执行。'
|
|
127
|
+
title: translateTaskText(t, 'orchestration.readiness.dryRun.title', '适合先预演'),
|
|
128
|
+
summary: translateTaskText(t, 'orchestration.readiness.dryRun.summary', '现在可以安全地跑一次仅预演,先看结果再决定是否真实执行。')
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
131
|
return {
|
|
128
132
|
tone: 'success',
|
|
129
|
-
title: '可以执行',
|
|
133
|
+
title: translateTaskText(t, 'orchestration.readiness.ready.title', '可以执行'),
|
|
130
134
|
summary: metrics.followUpCount > 0
|
|
131
|
-
? `主目标和收尾动作都已具备,可以直接执行或入队。`
|
|
132
|
-
: '主目标已经够清楚了,可以直接执行或入队。'
|
|
135
|
+
? translateTaskText(t, 'orchestration.readiness.ready.withFollowUps', `主目标和收尾动作都已具备,可以直接执行或入队。`)
|
|
136
|
+
: translateTaskText(t, 'orchestration.readiness.ready.summary', '主目标已经够清楚了,可以直接执行或入队。')
|
|
133
137
|
};
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -144,6 +148,7 @@ export function createMainTabsComputed() {
|
|
|
144
148
|
if (this.mainTab === 'market') return this.t('kicker.market');
|
|
145
149
|
if (this.mainTab === 'plugins') return this.t('kicker.plugins');
|
|
146
150
|
if (this.mainTab === 'docs') return this.t('kicker.docs');
|
|
151
|
+
if (this.mainTab === 'trash') return this.t('kicker.trash');
|
|
147
152
|
return this.t('kicker.settings');
|
|
148
153
|
},
|
|
149
154
|
mainTabTitle() {
|
|
@@ -155,6 +160,7 @@ export function createMainTabsComputed() {
|
|
|
155
160
|
if (this.mainTab === 'market') return this.t('title.market');
|
|
156
161
|
if (this.mainTab === 'plugins') return this.t('title.plugins');
|
|
157
162
|
if (this.mainTab === 'docs') return this.t('title.docs');
|
|
163
|
+
if (this.mainTab === 'trash') return this.t('settings.trash.title');
|
|
158
164
|
return this.t('title.settings');
|
|
159
165
|
},
|
|
160
166
|
mainTabSubtitle() {
|
|
@@ -166,6 +172,7 @@ export function createMainTabsComputed() {
|
|
|
166
172
|
if (this.mainTab === 'market') return this.t('subtitle.market');
|
|
167
173
|
if (this.mainTab === 'plugins') return this.t('subtitle.plugins');
|
|
168
174
|
if (this.mainTab === 'docs') return this.t('subtitle.docs');
|
|
175
|
+
if (this.mainTab === 'trash') return this.t('settings.trash.meta');
|
|
169
176
|
return this.t('subtitle.settings');
|
|
170
177
|
},
|
|
171
178
|
taskOrchestrationSelectedRun() {
|
|
@@ -196,10 +203,10 @@ export function createMainTabsComputed() {
|
|
|
196
203
|
return readTaskOrchestrationDraftMetrics(this.taskOrchestration);
|
|
197
204
|
},
|
|
198
205
|
taskOrchestrationDraftChecklist() {
|
|
199
|
-
return createTaskDraftChecklist(this.taskOrchestrationDraftMetrics);
|
|
206
|
+
return createTaskDraftChecklist(this.taskOrchestrationDraftMetrics, this.t && this.t.bind(this));
|
|
200
207
|
},
|
|
201
208
|
taskOrchestrationDraftReadiness() {
|
|
202
|
-
return createTaskDraftReadiness(this.taskOrchestrationDraftMetrics);
|
|
209
|
+
return createTaskDraftReadiness(this.taskOrchestrationDraftMetrics, this.t && this.t.bind(this));
|
|
203
210
|
}
|
|
204
211
|
};
|
|
205
212
|
}
|
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
function normalizeClaudeText(value) {
|
|
2
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function normalizeClaudeBaseUrl(value) {
|
|
6
|
+
return normalizeClaudeText(value).replace(/\/+$/g, '');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isValidClaudeHttpUrl(value) {
|
|
10
|
+
if (!value) return false;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(value);
|
|
13
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
14
|
+
} catch (_) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getClaudeConfigValidationForContext(vm, mode = 'add') {
|
|
20
|
+
const draft = mode === 'edit' ? vm.editingConfig : vm.newClaudeConfig;
|
|
21
|
+
const name = normalizeClaudeText(draft && draft.name);
|
|
22
|
+
const apiKey = normalizeClaudeText(draft && draft.apiKey);
|
|
23
|
+
const externalCredentialType = normalizeClaudeText(draft && draft.externalCredentialType);
|
|
24
|
+
const baseUrl = normalizeClaudeBaseUrl(draft && draft.baseUrl);
|
|
25
|
+
const model = normalizeClaudeText(draft && draft.model);
|
|
26
|
+
const errors = {
|
|
27
|
+
name: '',
|
|
28
|
+
apiKey: '',
|
|
29
|
+
baseUrl: '',
|
|
30
|
+
model: ''
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (!name) {
|
|
34
|
+
errors.name = '配置名称不能为空';
|
|
35
|
+
} else if (mode === 'add' && vm.claudeConfigs && vm.claudeConfigs[name]) {
|
|
36
|
+
errors.name = '名称已存在';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!apiKey && !externalCredentialType) {
|
|
40
|
+
errors.apiKey = 'API Key 必填';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!baseUrl) {
|
|
44
|
+
errors.baseUrl = 'Base URL 必填';
|
|
45
|
+
} else if (!isValidClaudeHttpUrl(baseUrl)) {
|
|
46
|
+
errors.baseUrl = 'Base URL 仅支持 http/https';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!model) {
|
|
50
|
+
errors.model = '模型名称必填';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
mode,
|
|
55
|
+
name,
|
|
56
|
+
apiKey,
|
|
57
|
+
externalCredentialType,
|
|
58
|
+
baseUrl,
|
|
59
|
+
model,
|
|
60
|
+
errors,
|
|
61
|
+
ok: !errors.name && !errors.apiKey && !errors.baseUrl && !errors.model
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
1
65
|
export function createClaudeConfigMethods(options = {}) {
|
|
2
66
|
const { api } = options;
|
|
3
67
|
|
|
@@ -54,14 +118,31 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
54
118
|
baseUrl: config.baseUrl || '',
|
|
55
119
|
model: config.model || ''
|
|
56
120
|
};
|
|
121
|
+
this.showAddClaudeConfigKey = false;
|
|
57
122
|
this.showClaudeConfigModal = true;
|
|
58
123
|
},
|
|
59
124
|
|
|
125
|
+
getClaudeConfigValidation(mode = 'add') {
|
|
126
|
+
return getClaudeConfigValidationForContext(this, mode);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
claudeConfigFieldError(mode, fieldName) {
|
|
130
|
+
const validation = getClaudeConfigValidationForContext(this, mode);
|
|
131
|
+
return validation && validation.errors && typeof validation.errors[fieldName] === 'string'
|
|
132
|
+
? validation.errors[fieldName]
|
|
133
|
+
: '';
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
canSubmitClaudeConfig(mode = 'add') {
|
|
137
|
+
return getClaudeConfigValidationForContext(this, mode).ok;
|
|
138
|
+
},
|
|
139
|
+
|
|
60
140
|
openEditConfigModal(name) {
|
|
61
141
|
const config = this.claudeConfigs[name];
|
|
62
142
|
this.editingConfig = {
|
|
63
143
|
name: name,
|
|
64
144
|
apiKey: config.apiKey || '',
|
|
145
|
+
externalCredentialType: config.externalCredentialType || '',
|
|
65
146
|
baseUrl: config.baseUrl || '',
|
|
66
147
|
model: config.model || ''
|
|
67
148
|
};
|
|
@@ -70,7 +151,14 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
70
151
|
},
|
|
71
152
|
|
|
72
153
|
updateConfig() {
|
|
73
|
-
const
|
|
154
|
+
const validation = getClaudeConfigValidationForContext(this, 'edit');
|
|
155
|
+
if (!validation.ok) {
|
|
156
|
+
return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
|
|
157
|
+
}
|
|
158
|
+
const name = validation.name;
|
|
159
|
+
this.editingConfig.apiKey = validation.apiKey;
|
|
160
|
+
this.editingConfig.baseUrl = validation.baseUrl;
|
|
161
|
+
this.editingConfig.model = validation.model;
|
|
74
162
|
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
75
163
|
this.saveClaudeConfigs();
|
|
76
164
|
this.showMessage('操作成功', 'success');
|
|
@@ -83,7 +171,7 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
83
171
|
closeEditConfigModal() {
|
|
84
172
|
this.showEditConfigModal = false;
|
|
85
173
|
this.showEditClaudeConfigKey = false;
|
|
86
|
-
this.editingConfig = { name: '', apiKey: '', baseUrl: '', model: '' };
|
|
174
|
+
this.editingConfig = { name: '', apiKey: '', externalCredentialType: '', baseUrl: '', model: '' };
|
|
87
175
|
},
|
|
88
176
|
|
|
89
177
|
toggleEditClaudeConfigKey() {
|
|
@@ -91,7 +179,14 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
91
179
|
},
|
|
92
180
|
|
|
93
181
|
async saveAndApplyConfig() {
|
|
94
|
-
const
|
|
182
|
+
const validation = getClaudeConfigValidationForContext(this, 'edit');
|
|
183
|
+
if (!validation.ok) {
|
|
184
|
+
return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
|
|
185
|
+
}
|
|
186
|
+
const name = validation.name;
|
|
187
|
+
this.editingConfig.apiKey = validation.apiKey;
|
|
188
|
+
this.editingConfig.baseUrl = validation.baseUrl;
|
|
189
|
+
this.editingConfig.model = validation.model;
|
|
95
190
|
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
96
191
|
this.saveClaudeConfigs();
|
|
97
192
|
|
|
@@ -125,13 +220,15 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
125
220
|
},
|
|
126
221
|
|
|
127
222
|
addClaudeConfig() {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const name = this.newClaudeConfig.name.trim();
|
|
132
|
-
if (this.claudeConfigs[name]) {
|
|
133
|
-
return this.showMessage('名称已存在', 'error');
|
|
223
|
+
const validation = getClaudeConfigValidationForContext(this, 'add');
|
|
224
|
+
if (!validation.ok) {
|
|
225
|
+
return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
|
|
134
226
|
}
|
|
227
|
+
this.newClaudeConfig.name = validation.name;
|
|
228
|
+
this.newClaudeConfig.apiKey = validation.apiKey;
|
|
229
|
+
this.newClaudeConfig.baseUrl = validation.baseUrl;
|
|
230
|
+
this.newClaudeConfig.model = validation.model;
|
|
231
|
+
const name = validation.name;
|
|
135
232
|
const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
|
|
136
233
|
if (duplicateName) {
|
|
137
234
|
return this.showMessage('配置已存在', 'info');
|
|
@@ -199,6 +296,7 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
199
296
|
|
|
200
297
|
closeClaudeConfigModal() {
|
|
201
298
|
this.showClaudeConfigModal = false;
|
|
299
|
+
this.showAddClaudeConfigKey = false;
|
|
202
300
|
this.newClaudeConfig = {
|
|
203
301
|
name: '',
|
|
204
302
|
apiKey: '',
|
|
@@ -207,6 +305,10 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
207
305
|
};
|
|
208
306
|
},
|
|
209
307
|
|
|
308
|
+
toggleAddClaudeConfigKey() {
|
|
309
|
+
this.showAddClaudeConfigKey = !this.showAddClaudeConfigKey;
|
|
310
|
+
},
|
|
311
|
+
|
|
210
312
|
async loadClaudeLocalBridgeStatus() {
|
|
211
313
|
try {
|
|
212
314
|
const res = await api('claude-local-bridge-status');
|
|
@@ -367,6 +367,54 @@ export function createOpenclawEditingMethods() {
|
|
|
367
367
|
this.showMessage('保存本地 OpenClaw 配置失败', 'error');
|
|
368
368
|
return false;
|
|
369
369
|
}
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
// Accordion stepper methods
|
|
373
|
+
toggleAccordionStep(step) {
|
|
374
|
+
if (this.openclawAccordionStep === step) {
|
|
375
|
+
// Don't allow collapsing the current step
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.openclawAccordionStep = step;
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
nextAccordionStep() {
|
|
382
|
+
if (this.openclawAccordionStep < 3) {
|
|
383
|
+
this.openclawAccordionStep++;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
prevAccordionStep() {
|
|
388
|
+
if (this.openclawAccordionStep > 1) {
|
|
389
|
+
this.openclawAccordionStep--;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
finishAccordionStep() {
|
|
394
|
+
this.openclawAccordionStep = 4; // Mark as complete
|
|
395
|
+
this.applyOpenclawQuickToText();
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
validateProviderName() {
|
|
399
|
+
const name = (this.openclawQuick.providerName || '').trim();
|
|
400
|
+
if (!name) {
|
|
401
|
+
this.openclawValidation.providerName = { valid: false, message: '必填' };
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (name.includes('/')) {
|
|
405
|
+
this.openclawValidation.providerName = { valid: false, message: '不能包含 "/"' };
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.openclawValidation.providerName = { valid: true, message: '' };
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
validateModelId() {
|
|
412
|
+
const id = (this.openclawQuick.modelId || '').trim();
|
|
413
|
+
if (!id) {
|
|
414
|
+
this.openclawValidation.modelId = { valid: false, message: '必填' };
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.openclawValidation.modelId = { valid: true, message: '' };
|
|
370
418
|
}
|
|
371
419
|
};
|
|
372
420
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const DEFAULT_OPENCLAW_CONFIG_NAME = '默认配置';
|
|
1
|
+
export const DEFAULT_OPENCLAW_CONFIG_NAME = '默认配置';
|
|
2
2
|
|
|
3
3
|
function buildNormalizedOpenclawConfigs(configs, defaultContent = '') {
|
|
4
4
|
const source = configs && typeof configs === 'object' && !Array.isArray(configs)
|
|
@@ -11,7 +11,8 @@ function buildNormalizedOpenclawConfigs(configs, defaultContent = '') {
|
|
|
11
11
|
: { content: defaultContent };
|
|
12
12
|
const normalized = {
|
|
13
13
|
[DEFAULT_OPENCLAW_CONFIG_NAME]: {
|
|
14
|
-
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : defaultContent
|
|
14
|
+
content: typeof defaultEntry.content === 'string' ? defaultEntry.content : defaultContent,
|
|
15
|
+
isDefault: true
|
|
15
16
|
}
|
|
16
17
|
};
|
|
17
18
|
for (const [name, value] of Object.entries(source)) {
|
|
@@ -25,7 +26,8 @@ function syncDefaultOpenclawConfigState(vm, content, options = {}) {
|
|
|
25
26
|
const nextContent = typeof content === 'string' ? content : '';
|
|
26
27
|
vm.openclawConfigs = buildNormalizedOpenclawConfigs(vm.openclawConfigs, nextContent);
|
|
27
28
|
vm.openclawConfigs[DEFAULT_OPENCLAW_CONFIG_NAME] = {
|
|
28
|
-
content: nextContent
|
|
29
|
+
content: nextContent,
|
|
30
|
+
isDefault: true
|
|
29
31
|
};
|
|
30
32
|
if (typeof options.path === 'string') {
|
|
31
33
|
vm.openclawConfigPath = options.path;
|
|
@@ -51,6 +53,10 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
51
53
|
} = options;
|
|
52
54
|
|
|
53
55
|
return {
|
|
56
|
+
isDefaultOpenclawConfig(name, config = null) {
|
|
57
|
+
return !!(config && config.isDefault === true) || name === DEFAULT_OPENCLAW_CONFIG_NAME;
|
|
58
|
+
},
|
|
59
|
+
|
|
54
60
|
syncDefaultOpenclawConfigEntry(options = {}) {
|
|
55
61
|
const silent = !!options.silent;
|
|
56
62
|
return api('get-openclaw-config')
|
|
@@ -104,7 +110,7 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
104
110
|
|
|
105
111
|
openOpenclawEditModal(name) {
|
|
106
112
|
const existing = this.openclawConfigs[name];
|
|
107
|
-
const isDefaultConfig = name
|
|
113
|
+
const isDefaultConfig = this.isDefaultOpenclawConfig(name, existing);
|
|
108
114
|
const modalToken = (Number(this.openclawModalLoadToken || 0) + 1);
|
|
109
115
|
this.openclawModalLoadToken = modalToken;
|
|
110
116
|
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
@@ -146,7 +152,7 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
146
152
|
const force = !!options.force;
|
|
147
153
|
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
148
154
|
const syncDefaultEntry = options.syncDefaultEntry === true
|
|
149
|
-
|| (this.openclawEditing && this.openclawEditing.lockName && this.openclawEditing.name
|
|
155
|
+
|| (this.openclawEditing && this.openclawEditing.lockName && this.isDefaultOpenclawConfig(this.openclawEditing.name));
|
|
150
156
|
const modalToken = Number(options.modalToken || this.openclawModalLoadToken || 0);
|
|
151
157
|
const expectedEditorContent = typeof options.expectedEditorContent === 'string'
|
|
152
158
|
? options.expectedEditorContent
|
|
@@ -260,7 +266,7 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
260
266
|
if (this.openclawSaving || this.openclawApplying) {
|
|
261
267
|
return;
|
|
262
268
|
}
|
|
263
|
-
if (this.openclawEditing && this.openclawEditing.lockName && this.openclawEditing.name
|
|
269
|
+
if (this.openclawEditing && this.openclawEditing.lockName && this.isDefaultOpenclawConfig(this.openclawEditing.name)) {
|
|
264
270
|
this.showMessage('默认配置代表当前系统配置,请使用“保存并应用”', 'info');
|
|
265
271
|
return;
|
|
266
272
|
}
|
|
@@ -312,7 +318,7 @@ export function createOpenclawPersistMethods(options = {}) {
|
|
|
312
318
|
},
|
|
313
319
|
|
|
314
320
|
async deleteOpenclawConfig(name) {
|
|
315
|
-
if (name
|
|
321
|
+
if (this.isDefaultOpenclawConfig(name, this.openclawConfigs && this.openclawConfigs[name])) {
|
|
316
322
|
return this.showMessage('默认配置始终映射当前系统配置,不可删除', 'info');
|
|
317
323
|
}
|
|
318
324
|
if (Object.keys(this.openclawConfigs).length <= 1) {
|