codexmate 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/README.zh-CN.md +24 -2
- package/{src/cli.js → cli.js} +2689 -256
- package/doc/CHANGELOG.md +6 -0
- package/doc/CHANGELOG.zh-CN.md +6 -0
- package/lib/mcp-stdio.js +440 -0
- package/package.json +56 -53
- package/web-ui/app.js +903 -10
- package/web-ui/index.html +350 -55
- package/web-ui/styles.css +394 -49
- package/src/lib/cli-file-utils.js +0 -151
- package/src/lib/cli-models-utils.js +0 -152
- package/src/lib/cli-network-utils.js +0 -148
- package/src/lib/cli-session-utils.js +0 -121
- package/src/lib/cli-utils.js +0 -139
- package/src/res/json5.min.js +0 -1
- package/src/res/logo.png +0 -0
- package/src/res/screenshot.png +0 -0
- package/src/res/vue.global.js +0 -18552
- package/src/web-ui/app.js +0 -2970
- package/src/web-ui/index.html +0 -1310
- package/src/web-ui/logic.mjs +0 -157
- package/src/web-ui/styles.css +0 -2868
- /package/{src/web-ui.html → web-ui.html} +0 -0
package/src/web-ui/app.js
DELETED
|
@@ -1,2970 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeClaudeValue,
|
|
3
|
-
normalizeClaudeConfig,
|
|
4
|
-
normalizeClaudeSettingsEnv,
|
|
5
|
-
matchClaudeConfigFromSettings,
|
|
6
|
-
findDuplicateClaudeConfigName,
|
|
7
|
-
formatLatency,
|
|
8
|
-
buildSpeedTestIssue,
|
|
9
|
-
isSessionQueryEnabled,
|
|
10
|
-
buildSessionListParams
|
|
11
|
-
} from './logic.mjs';
|
|
12
|
-
|
|
13
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
14
|
-
if (typeof Vue === 'undefined') {
|
|
15
|
-
console.error('Vue 库未能在 DOMContentLoaded 触发前加载完成。');
|
|
16
|
-
const fallbackTarget = document.querySelector('#app') || document.querySelector('[v-cloak]');
|
|
17
|
-
if (fallbackTarget) {
|
|
18
|
-
fallbackTarget.removeAttribute('v-cloak');
|
|
19
|
-
fallbackTarget.classList.remove('v-cloak');
|
|
20
|
-
fallbackTarget.innerHTML = '';
|
|
21
|
-
const notice = document.createElement('div');
|
|
22
|
-
notice.className = 'fallback-message';
|
|
23
|
-
notice.textContent = 'Web UI 加载失败:Vue 未加载。请检查网络或刷新页面。';
|
|
24
|
-
fallbackTarget.appendChild(notice);
|
|
25
|
-
}
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const { createApp } = Vue;
|
|
29
|
-
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
30
|
-
? location.origin
|
|
31
|
-
: 'http://localhost:3737';
|
|
32
|
-
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
33
|
-
// OpenClaw config (JSON5)
|
|
34
|
-
agent: {
|
|
35
|
-
model: "gpt-4.1"
|
|
36
|
-
},
|
|
37
|
-
agents: {
|
|
38
|
-
defaults: {
|
|
39
|
-
workspace: "~/.openclaw/workspace"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}`;
|
|
43
|
-
|
|
44
|
-
async function api(action, params = {}) {
|
|
45
|
-
const res = await fetch(`${API_BASE}/api`, {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
headers: { 'Content-Type': 'application/json' },
|
|
48
|
-
body: JSON.stringify({ action, params })
|
|
49
|
-
});
|
|
50
|
-
return await res.json();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const app = createApp({
|
|
54
|
-
data() {
|
|
55
|
-
return {
|
|
56
|
-
mainTab: 'config',
|
|
57
|
-
configMode: 'codex',
|
|
58
|
-
currentProvider: '',
|
|
59
|
-
currentModel: '',
|
|
60
|
-
serviceTier: 'fast',
|
|
61
|
-
modelReasoningEffort: 'high',
|
|
62
|
-
providersList: [],
|
|
63
|
-
models: [],
|
|
64
|
-
codexModelsLoading: false,
|
|
65
|
-
modelsSource: 'remote',
|
|
66
|
-
modelsHasCurrent: true,
|
|
67
|
-
claudeModels: [],
|
|
68
|
-
claudeModelsSource: 'idle',
|
|
69
|
-
claudeModelsHasCurrent: true,
|
|
70
|
-
claudeModelsLoading: false,
|
|
71
|
-
loading: true,
|
|
72
|
-
initError: '',
|
|
73
|
-
message: '',
|
|
74
|
-
messageType: '',
|
|
75
|
-
showAddModal: false,
|
|
76
|
-
showEditModal: false,
|
|
77
|
-
showModelModal: false,
|
|
78
|
-
showModelListModal: false,
|
|
79
|
-
showClaudeConfigModal: false,
|
|
80
|
-
showEditConfigModal: false,
|
|
81
|
-
showOpenclawConfigModal: false,
|
|
82
|
-
showConfigTemplateModal: false,
|
|
83
|
-
showAgentsModal: false,
|
|
84
|
-
showInstallModal: false,
|
|
85
|
-
configTemplateContent: '',
|
|
86
|
-
configTemplateApplying: false,
|
|
87
|
-
codexApplying: false,
|
|
88
|
-
agentsContent: '',
|
|
89
|
-
agentsPath: '',
|
|
90
|
-
agentsExists: false,
|
|
91
|
-
agentsLineEnding: '\n',
|
|
92
|
-
agentsLoading: false,
|
|
93
|
-
agentsSaving: false,
|
|
94
|
-
agentsContext: 'codex',
|
|
95
|
-
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
96
|
-
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
97
|
-
sessionsList: [],
|
|
98
|
-
sessionsLoading: false,
|
|
99
|
-
sessionFilterSource: 'all',
|
|
100
|
-
sessionPathFilter: '',
|
|
101
|
-
sessionQuery: '',
|
|
102
|
-
sessionRoleFilter: 'all',
|
|
103
|
-
sessionTimePreset: 'all',
|
|
104
|
-
sessionResumeWithYolo: true,
|
|
105
|
-
sessionPathOptions: [],
|
|
106
|
-
sessionPathOptionsLoading: false,
|
|
107
|
-
sessionPathOptionsMap: {
|
|
108
|
-
all: [],
|
|
109
|
-
codex: [],
|
|
110
|
-
claude: []
|
|
111
|
-
},
|
|
112
|
-
sessionPathOptionsLoadedMap: {
|
|
113
|
-
all: false,
|
|
114
|
-
codex: false,
|
|
115
|
-
claude: false
|
|
116
|
-
},
|
|
117
|
-
sessionPathRequestSeq: 0,
|
|
118
|
-
sessionExporting: {},
|
|
119
|
-
sessionCloning: {},
|
|
120
|
-
sessionDeleting: {},
|
|
121
|
-
activeSession: null,
|
|
122
|
-
activeSessionMessages: [],
|
|
123
|
-
activeSessionDetailError: '',
|
|
124
|
-
activeSessionDetailClipped: false,
|
|
125
|
-
sessionDetailLoading: false,
|
|
126
|
-
sessionDetailRequestSeq: 0,
|
|
127
|
-
sessionStandalone: false,
|
|
128
|
-
sessionStandaloneError: '',
|
|
129
|
-
sessionStandaloneText: '',
|
|
130
|
-
sessionStandaloneTitle: '',
|
|
131
|
-
sessionStandaloneSourceLabel: '',
|
|
132
|
-
sessionStandaloneLoading: false,
|
|
133
|
-
sessionStandaloneRequestSeq: 0,
|
|
134
|
-
speedResults: {},
|
|
135
|
-
speedLoading: {},
|
|
136
|
-
claudeSpeedResults: {},
|
|
137
|
-
claudeSpeedLoading: {},
|
|
138
|
-
claudeShareLoading: {},
|
|
139
|
-
providerShareLoading: {},
|
|
140
|
-
installCommands: [
|
|
141
|
-
'npm install -g @anthropic-ai/claude-code',
|
|
142
|
-
'npm i -g @openai/codex'
|
|
143
|
-
],
|
|
144
|
-
newProvider: { name: '', url: '', key: '' },
|
|
145
|
-
resetConfigLoading: false,
|
|
146
|
-
editingProvider: { name: '', url: '', key: '' },
|
|
147
|
-
newModelName: '',
|
|
148
|
-
currentClaudeConfig: '',
|
|
149
|
-
currentClaudeModel: '',
|
|
150
|
-
editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
|
|
151
|
-
claudeConfigs: {
|
|
152
|
-
'智谱GLM': {
|
|
153
|
-
apiKey: '',
|
|
154
|
-
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
155
|
-
model: 'glm-4.7',
|
|
156
|
-
hasKey: false
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
newClaudeConfig: {
|
|
160
|
-
name: '',
|
|
161
|
-
apiKey: '',
|
|
162
|
-
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
163
|
-
model: 'glm-4.7'
|
|
164
|
-
},
|
|
165
|
-
currentOpenclawConfig: '',
|
|
166
|
-
openclawConfigs: {
|
|
167
|
-
'默认配置': {
|
|
168
|
-
content: DEFAULT_OPENCLAW_TEMPLATE
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
openclawEditing: { name: '', content: '', lockName: false },
|
|
172
|
-
openclawEditorTitle: '添加 OpenClaw 配置',
|
|
173
|
-
openclawConfigPath: '',
|
|
174
|
-
openclawConfigExists: false,
|
|
175
|
-
openclawLineEnding: '\n',
|
|
176
|
-
openclawFileLoading: false,
|
|
177
|
-
openclawSaving: false,
|
|
178
|
-
openclawApplying: false,
|
|
179
|
-
openclawWorkspaceFileName: 'SOUL.md',
|
|
180
|
-
agentsWorkspaceFileName: '',
|
|
181
|
-
openclawStructured: {
|
|
182
|
-
agentPrimary: '',
|
|
183
|
-
agentFallbacks: [],
|
|
184
|
-
workspace: '',
|
|
185
|
-
timeout: '',
|
|
186
|
-
contextTokens: '',
|
|
187
|
-
maxConcurrent: '',
|
|
188
|
-
envItems: [],
|
|
189
|
-
toolsProfile: 'default',
|
|
190
|
-
toolsAllow: [],
|
|
191
|
-
toolsDeny: []
|
|
192
|
-
},
|
|
193
|
-
openclawQuick: {
|
|
194
|
-
providerName: '',
|
|
195
|
-
baseUrl: '',
|
|
196
|
-
apiKey: '',
|
|
197
|
-
apiType: 'openai-responses',
|
|
198
|
-
modelId: '',
|
|
199
|
-
modelName: '',
|
|
200
|
-
contextWindow: '',
|
|
201
|
-
maxTokens: '',
|
|
202
|
-
setPrimary: true,
|
|
203
|
-
overrideProvider: true,
|
|
204
|
-
overrideModels: true,
|
|
205
|
-
showKey: false
|
|
206
|
-
},
|
|
207
|
-
openclawAgentsList: [],
|
|
208
|
-
openclawProviders: [],
|
|
209
|
-
openclawMissingProviders: [],
|
|
210
|
-
healthCheckLoading: false,
|
|
211
|
-
healthCheckResult: null,
|
|
212
|
-
healthCheckRemote: false
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
mounted() {
|
|
216
|
-
this.initSessionStandalone();
|
|
217
|
-
const savedSessionYolo = localStorage.getItem('codexmateSessionResumeYolo');
|
|
218
|
-
if (savedSessionYolo === '0' || savedSessionYolo === 'false') {
|
|
219
|
-
this.sessionResumeWithYolo = false;
|
|
220
|
-
} else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
|
|
221
|
-
this.sessionResumeWithYolo = true;
|
|
222
|
-
}
|
|
223
|
-
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
224
|
-
if (savedConfigs) {
|
|
225
|
-
try {
|
|
226
|
-
this.claudeConfigs = JSON.parse(savedConfigs);
|
|
227
|
-
for (const [name, config] of Object.entries(this.claudeConfigs)) {
|
|
228
|
-
if (config.apiKey && config.apiKey.includes('****')) {
|
|
229
|
-
config.apiKey = '';
|
|
230
|
-
config.hasKey = false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
|
|
234
|
-
} catch (e) {
|
|
235
|
-
console.error('加载 Claude 配置失败:', e);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
void this.refreshClaudeSelectionFromSettings({ silent: true });
|
|
239
|
-
const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
|
|
240
|
-
if (savedOpenclawConfigs) {
|
|
241
|
-
try {
|
|
242
|
-
this.openclawConfigs = JSON.parse(savedOpenclawConfigs);
|
|
243
|
-
const configNames = Object.keys(this.openclawConfigs);
|
|
244
|
-
if (configNames.length > 0) {
|
|
245
|
-
this.currentOpenclawConfig = configNames[0];
|
|
246
|
-
}
|
|
247
|
-
} catch (e) {
|
|
248
|
-
console.error('加载 OpenClaw 配置失败:', e);
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
const configNames = Object.keys(this.openclawConfigs);
|
|
252
|
-
if (configNames.length > 0) {
|
|
253
|
-
this.currentOpenclawConfig = configNames[0];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
this.loadAll();
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
computed: {
|
|
260
|
-
isSessionQueryEnabled() {
|
|
261
|
-
return isSessionQueryEnabled(this.sessionFilterSource);
|
|
262
|
-
},
|
|
263
|
-
sessionQueryPlaceholder() {
|
|
264
|
-
if (this.isSessionQueryEnabled) {
|
|
265
|
-
return '关键词检索(支持 Codex/Claude,例:claude code)';
|
|
266
|
-
}
|
|
267
|
-
return '当前来源暂不支持关键词检索';
|
|
268
|
-
},
|
|
269
|
-
claudeModelHasList() {
|
|
270
|
-
return Array.isArray(this.claudeModels) && this.claudeModels.length > 0;
|
|
271
|
-
},
|
|
272
|
-
claudeModelOptions() {
|
|
273
|
-
const list = Array.isArray(this.claudeModels) ? [...this.claudeModels] : [];
|
|
274
|
-
const current = (this.currentClaudeModel || '').trim();
|
|
275
|
-
if (current && !list.includes(current)) {
|
|
276
|
-
list.unshift(current);
|
|
277
|
-
}
|
|
278
|
-
return list;
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
methods: {
|
|
282
|
-
async loadAll() {
|
|
283
|
-
this.loading = true;
|
|
284
|
-
this.initError = '';
|
|
285
|
-
try {
|
|
286
|
-
const [statusRes, listRes] = await Promise.all([api('status'), api('list')]);
|
|
287
|
-
|
|
288
|
-
if (statusRes.error) {
|
|
289
|
-
this.initError = statusRes.error;
|
|
290
|
-
} else {
|
|
291
|
-
this.currentProvider = statusRes.provider;
|
|
292
|
-
this.currentModel = statusRes.model;
|
|
293
|
-
{
|
|
294
|
-
const tier = typeof statusRes.serviceTier === 'string'
|
|
295
|
-
? statusRes.serviceTier.trim().toLowerCase()
|
|
296
|
-
: '';
|
|
297
|
-
this.serviceTier = tier === 'fast' ? 'fast' : (tier ? 'standard' : 'fast');
|
|
298
|
-
}
|
|
299
|
-
{
|
|
300
|
-
const effort = typeof statusRes.modelReasoningEffort === 'string'
|
|
301
|
-
? statusRes.modelReasoningEffort.trim().toLowerCase()
|
|
302
|
-
: '';
|
|
303
|
-
this.modelReasoningEffort = effort || 'high';
|
|
304
|
-
}
|
|
305
|
-
this.providersList = listRes.providers;
|
|
306
|
-
if (statusRes.configReady === false) {
|
|
307
|
-
this.showMessage('配置已加载', 'info');
|
|
308
|
-
}
|
|
309
|
-
if (statusRes.initNotice) {
|
|
310
|
-
this.showMessage('配置就绪', 'info');
|
|
311
|
-
}
|
|
312
|
-
this.maybeShowStarPrompt();
|
|
313
|
-
}
|
|
314
|
-
} catch (e) {
|
|
315
|
-
this.initError = '连接失败: ' + e.message;
|
|
316
|
-
} finally {
|
|
317
|
-
this.loading = false;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// 模型加载单独异步,不阻塞主 loading
|
|
321
|
-
try {
|
|
322
|
-
await this.loadModelsForProvider(this.currentProvider);
|
|
323
|
-
} catch (e) {
|
|
324
|
-
// loadModelsForProvider 内部已有 toast,这里吞掉防止抛出
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
async loadModelsForProvider(providerName) {
|
|
329
|
-
this.codexModelsLoading = true;
|
|
330
|
-
if (!providerName) {
|
|
331
|
-
this.models = [];
|
|
332
|
-
this.modelsSource = 'unlimited';
|
|
333
|
-
this.modelsHasCurrent = true;
|
|
334
|
-
this.codexModelsLoading = false;
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const res = await api('models', { provider: providerName });
|
|
339
|
-
if (res.unlimited) {
|
|
340
|
-
this.models = [];
|
|
341
|
-
this.modelsSource = 'unlimited';
|
|
342
|
-
this.modelsHasCurrent = true;
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (res.error) {
|
|
346
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
347
|
-
this.models = [];
|
|
348
|
-
this.modelsSource = 'error';
|
|
349
|
-
this.modelsHasCurrent = true;
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const list = Array.isArray(res.models) ? res.models : [];
|
|
353
|
-
this.models = list;
|
|
354
|
-
this.modelsSource = res.source || 'remote';
|
|
355
|
-
this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
|
|
356
|
-
} catch (e) {
|
|
357
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
358
|
-
this.models = [];
|
|
359
|
-
this.modelsSource = 'error';
|
|
360
|
-
this.modelsHasCurrent = true;
|
|
361
|
-
} finally {
|
|
362
|
-
this.codexModelsLoading = false;
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
getCurrentClaudeConfig() {
|
|
367
|
-
if (!this.currentClaudeConfig) return null;
|
|
368
|
-
return this.claudeConfigs[this.currentClaudeConfig] || null;
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
normalizeClaudeValue,
|
|
372
|
-
|
|
373
|
-
normalizeClaudeConfig(config) {
|
|
374
|
-
return normalizeClaudeConfig(config);
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
normalizeClaudeSettingsEnv(env) {
|
|
378
|
-
return normalizeClaudeSettingsEnv(env);
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
matchClaudeConfigFromSettings(env) {
|
|
382
|
-
return matchClaudeConfigFromSettings(this.claudeConfigs, env);
|
|
383
|
-
},
|
|
384
|
-
|
|
385
|
-
findDuplicateClaudeConfigName(config) {
|
|
386
|
-
return findDuplicateClaudeConfigName(this.claudeConfigs, config);
|
|
387
|
-
},
|
|
388
|
-
|
|
389
|
-
async refreshClaudeSelectionFromSettings(options = {}) {
|
|
390
|
-
const configNames = Object.keys(this.claudeConfigs || {});
|
|
391
|
-
if (configNames.length === 0) {
|
|
392
|
-
this.currentClaudeConfig = '';
|
|
393
|
-
this.currentClaudeModel = '';
|
|
394
|
-
this.resetClaudeModelsState();
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
const silent = !!options.silent;
|
|
398
|
-
try {
|
|
399
|
-
const res = await api('get-claude-settings');
|
|
400
|
-
if (res && res.error) {
|
|
401
|
-
if (!silent) {
|
|
402
|
-
this.showMessage('读取配置失败', 'error');
|
|
403
|
-
}
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
|
|
407
|
-
if (matchName) {
|
|
408
|
-
if (this.currentClaudeConfig !== matchName) {
|
|
409
|
-
this.currentClaudeConfig = matchName;
|
|
410
|
-
}
|
|
411
|
-
this.refreshClaudeModelContext();
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
this.currentClaudeConfig = '';
|
|
415
|
-
this.currentClaudeModel = '';
|
|
416
|
-
this.resetClaudeModelsState();
|
|
417
|
-
if (!silent) {
|
|
418
|
-
const tip = res && res.exists
|
|
419
|
-
? '当前 Claude settings.json 与本地配置不匹配,已取消选中'
|
|
420
|
-
: '未检测到 Claude settings.json,已取消选中';
|
|
421
|
-
this.showMessage(tip, 'info');
|
|
422
|
-
}
|
|
423
|
-
} catch (e) {
|
|
424
|
-
if (!silent) {
|
|
425
|
-
this.showMessage('读取配置失败', 'error');
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
},
|
|
429
|
-
|
|
430
|
-
syncClaudeModelFromConfig() {
|
|
431
|
-
const config = this.getCurrentClaudeConfig();
|
|
432
|
-
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
433
|
-
},
|
|
434
|
-
|
|
435
|
-
refreshClaudeModelContext() {
|
|
436
|
-
this.syncClaudeModelFromConfig();
|
|
437
|
-
this.loadClaudeModels();
|
|
438
|
-
},
|
|
439
|
-
|
|
440
|
-
resetClaudeModelsState() {
|
|
441
|
-
this.claudeModels = [];
|
|
442
|
-
this.claudeModelsSource = 'idle';
|
|
443
|
-
this.claudeModelsHasCurrent = true;
|
|
444
|
-
this.claudeModelsLoading = false;
|
|
445
|
-
},
|
|
446
|
-
|
|
447
|
-
updateClaudeModelsCurrent() {
|
|
448
|
-
const currentModel = (this.currentClaudeModel || '').trim();
|
|
449
|
-
this.claudeModelsHasCurrent = !!currentModel && this.claudeModels.includes(currentModel);
|
|
450
|
-
},
|
|
451
|
-
|
|
452
|
-
async loadClaudeModels() {
|
|
453
|
-
const config = this.getCurrentClaudeConfig();
|
|
454
|
-
if (!config) {
|
|
455
|
-
this.resetClaudeModelsState();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
const baseUrl = (config.baseUrl || '').trim();
|
|
459
|
-
const apiKey = (config.apiKey || '').trim();
|
|
460
|
-
|
|
461
|
-
if (!baseUrl) {
|
|
462
|
-
this.resetClaudeModelsState();
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
this.claudeModelsLoading = true;
|
|
467
|
-
try {
|
|
468
|
-
const res = await api('models-by-url', { baseUrl, apiKey });
|
|
469
|
-
if (res.unlimited) {
|
|
470
|
-
this.claudeModels = [];
|
|
471
|
-
this.claudeModelsSource = 'unlimited';
|
|
472
|
-
this.claudeModelsHasCurrent = true;
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (res.error) {
|
|
476
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
477
|
-
this.claudeModels = [];
|
|
478
|
-
this.claudeModelsSource = 'error';
|
|
479
|
-
this.claudeModelsHasCurrent = true;
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
const list = Array.isArray(res.models) ? res.models : [];
|
|
483
|
-
this.claudeModels = list;
|
|
484
|
-
this.claudeModelsSource = res.source || 'remote';
|
|
485
|
-
this.updateClaudeModelsCurrent();
|
|
486
|
-
} catch (e) {
|
|
487
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
488
|
-
this.claudeModels = [];
|
|
489
|
-
this.claudeModelsSource = 'error';
|
|
490
|
-
this.claudeModelsHasCurrent = true;
|
|
491
|
-
} finally {
|
|
492
|
-
this.claudeModelsLoading = false;
|
|
493
|
-
}
|
|
494
|
-
},
|
|
495
|
-
|
|
496
|
-
openClaudeConfigModal() {
|
|
497
|
-
this.showClaudeConfigModal = true;
|
|
498
|
-
},
|
|
499
|
-
|
|
500
|
-
maybeShowStarPrompt() {
|
|
501
|
-
const storageKey = 'codexmateStarPrompted';
|
|
502
|
-
if (localStorage.getItem(storageKey)) {
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
this.showMessage('欢迎到 GitHub 点 Star', 'info');
|
|
506
|
-
localStorage.setItem(storageKey, '1');
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
switchConfigMode(mode) {
|
|
510
|
-
this.mainTab = 'config';
|
|
511
|
-
this.configMode = mode;
|
|
512
|
-
if (mode === 'claude') {
|
|
513
|
-
this.refreshClaudeModelContext();
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
|
|
517
|
-
switchMainTab(tab) {
|
|
518
|
-
this.mainTab = tab;
|
|
519
|
-
if (tab === 'sessions' && this.sessionsList.length === 0) {
|
|
520
|
-
this.loadSessions();
|
|
521
|
-
}
|
|
522
|
-
if (tab === 'config' && this.configMode === 'claude') {
|
|
523
|
-
this.refreshClaudeModelContext();
|
|
524
|
-
}
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
getSessionStandaloneContext() {
|
|
528
|
-
try {
|
|
529
|
-
const url = new URL(window.location.href);
|
|
530
|
-
if (url.pathname !== '/session') {
|
|
531
|
-
return { requested: false, params: null, error: '' };
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const source = (url.searchParams.get('source') || '').trim().toLowerCase();
|
|
535
|
-
const sessionId = (url.searchParams.get('sessionId') || url.searchParams.get('id') || '').trim();
|
|
536
|
-
const filePath = (url.searchParams.get('filePath') || url.searchParams.get('path') || '').trim();
|
|
537
|
-
let error = '';
|
|
538
|
-
if (!source) {
|
|
539
|
-
error = '缺少 source 参数';
|
|
540
|
-
} else if (source !== 'codex' && source !== 'claude') {
|
|
541
|
-
error = 'source 仅支持 codex 或 claude';
|
|
542
|
-
}
|
|
543
|
-
if (!sessionId && !filePath) {
|
|
544
|
-
error = error ? `${error},还缺少 sessionId 或 filePath` : '缺少 sessionId 或 filePath 参数';
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (error) {
|
|
548
|
-
return { requested: true, params: null, error };
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return {
|
|
552
|
-
requested: true,
|
|
553
|
-
params: {
|
|
554
|
-
source,
|
|
555
|
-
sessionId,
|
|
556
|
-
filePath
|
|
557
|
-
},
|
|
558
|
-
error: ''
|
|
559
|
-
};
|
|
560
|
-
} catch (_) {
|
|
561
|
-
return { requested: false, params: null, error: '' };
|
|
562
|
-
}
|
|
563
|
-
},
|
|
564
|
-
|
|
565
|
-
initSessionStandalone() {
|
|
566
|
-
const context = this.getSessionStandaloneContext();
|
|
567
|
-
if (!context.requested) return;
|
|
568
|
-
|
|
569
|
-
this.sessionStandalone = true;
|
|
570
|
-
this.mainTab = 'sessions';
|
|
571
|
-
|
|
572
|
-
if (context.error || !context.params) {
|
|
573
|
-
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
|
|
578
|
-
this.activeSession = {
|
|
579
|
-
source: context.params.source,
|
|
580
|
-
sourceLabel,
|
|
581
|
-
sessionId: context.params.sessionId,
|
|
582
|
-
filePath: context.params.filePath,
|
|
583
|
-
title: context.params.sessionId || context.params.filePath || '会话'
|
|
584
|
-
};
|
|
585
|
-
this.activeSessionMessages = [];
|
|
586
|
-
this.activeSessionDetailError = '';
|
|
587
|
-
this.activeSessionDetailClipped = false;
|
|
588
|
-
this.sessionStandaloneError = '';
|
|
589
|
-
this.sessionStandaloneText = '';
|
|
590
|
-
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
591
|
-
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
592
|
-
this.loadSessionStandalonePlain();
|
|
593
|
-
},
|
|
594
|
-
|
|
595
|
-
buildSessionStandaloneUrl(session) {
|
|
596
|
-
if (!session) return '';
|
|
597
|
-
const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
|
|
598
|
-
if (!source || (source !== 'codex' && source !== 'claude')) return '';
|
|
599
|
-
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
600
|
-
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
601
|
-
if (!sessionId && !filePath) return '';
|
|
602
|
-
const origin = window.location.origin && window.location.origin !== 'null'
|
|
603
|
-
? window.location.origin
|
|
604
|
-
: API_BASE;
|
|
605
|
-
const params = new URLSearchParams();
|
|
606
|
-
params.set('source', source);
|
|
607
|
-
if (sessionId) params.set('sessionId', sessionId);
|
|
608
|
-
if (filePath) params.set('filePath', filePath);
|
|
609
|
-
return `${origin}/session?${params.toString()}`;
|
|
610
|
-
},
|
|
611
|
-
|
|
612
|
-
openSessionStandalone(session) {
|
|
613
|
-
const url = this.buildSessionStandaloneUrl(session);
|
|
614
|
-
if (!url) {
|
|
615
|
-
this.showMessage('无法生成链接', 'error');
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
window.open(url, '_blank', 'noopener');
|
|
619
|
-
},
|
|
620
|
-
|
|
621
|
-
getSessionExportKey(session) {
|
|
622
|
-
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
623
|
-
},
|
|
624
|
-
|
|
625
|
-
isResumeCommandAvailable(session) {
|
|
626
|
-
if (!session) return false;
|
|
627
|
-
const source = String(session.source || '').trim().toLowerCase();
|
|
628
|
-
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
629
|
-
return source === 'codex' && !!sessionId;
|
|
630
|
-
},
|
|
631
|
-
|
|
632
|
-
isCloneAvailable(session) {
|
|
633
|
-
if (!session) return false;
|
|
634
|
-
const source = String(session.source || '').trim().toLowerCase();
|
|
635
|
-
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
636
|
-
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
637
|
-
return source === 'codex' && (!!sessionId || !!filePath);
|
|
638
|
-
},
|
|
639
|
-
|
|
640
|
-
isDeleteAvailable(session) {
|
|
641
|
-
if (!session) return false;
|
|
642
|
-
const source = String(session.source || '').trim().toLowerCase();
|
|
643
|
-
if (source !== 'codex' && source !== 'claude') return false;
|
|
644
|
-
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
645
|
-
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
646
|
-
return !!sessionId || !!filePath;
|
|
647
|
-
},
|
|
648
|
-
|
|
649
|
-
buildResumeCommand(session) {
|
|
650
|
-
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
651
|
-
const arg = this.quoteResumeArg(sessionId);
|
|
652
|
-
if (this.sessionResumeWithYolo) {
|
|
653
|
-
return `codex --yolo resume ${arg}`;
|
|
654
|
-
}
|
|
655
|
-
return `codex resume ${arg}`;
|
|
656
|
-
},
|
|
657
|
-
|
|
658
|
-
quoteShellArg(value) {
|
|
659
|
-
const text = typeof value === 'string' ? value : String(value || '');
|
|
660
|
-
if (!text) return "''";
|
|
661
|
-
if (/^[a-zA-Z0-9._-]+$/.test(text)) return text;
|
|
662
|
-
const escaped = text.replace(/'/g, "'\\''");
|
|
663
|
-
return `'${escaped}'`;
|
|
664
|
-
},
|
|
665
|
-
|
|
666
|
-
quoteResumeArg(value) {
|
|
667
|
-
return this.quoteShellArg(value);
|
|
668
|
-
},
|
|
669
|
-
|
|
670
|
-
fallbackCopyText(text) {
|
|
671
|
-
let textarea = null;
|
|
672
|
-
try {
|
|
673
|
-
textarea = document.createElement('textarea');
|
|
674
|
-
textarea.value = text;
|
|
675
|
-
textarea.setAttribute('readonly', '');
|
|
676
|
-
textarea.style.position = 'fixed';
|
|
677
|
-
textarea.style.top = '-9999px';
|
|
678
|
-
textarea.style.left = '-9999px';
|
|
679
|
-
textarea.style.opacity = '0';
|
|
680
|
-
document.body.appendChild(textarea);
|
|
681
|
-
textarea.select();
|
|
682
|
-
textarea.setSelectionRange(0, textarea.value.length);
|
|
683
|
-
return document.execCommand('copy');
|
|
684
|
-
} catch (e) {
|
|
685
|
-
return false;
|
|
686
|
-
} finally {
|
|
687
|
-
if (textarea && textarea.parentNode) {
|
|
688
|
-
textarea.parentNode.removeChild(textarea);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
},
|
|
692
|
-
|
|
693
|
-
copyAgentsContent() {
|
|
694
|
-
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
695
|
-
if (!text) {
|
|
696
|
-
this.showMessage('没有可复制内容', 'info');
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
const ok = this.fallbackCopyText(text);
|
|
700
|
-
if (ok) {
|
|
701
|
-
this.showMessage('已复制', 'success');
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
this.showMessage('复制失败', 'error');
|
|
705
|
-
},
|
|
706
|
-
|
|
707
|
-
copyInstallCommand(cmd) {
|
|
708
|
-
const text = typeof cmd === 'string' ? cmd.trim() : '';
|
|
709
|
-
if (!text) {
|
|
710
|
-
this.showMessage('没有可复制内容', 'info');
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
const ok = this.fallbackCopyText(text);
|
|
714
|
-
if (ok) {
|
|
715
|
-
this.showMessage('已复制命令', 'success');
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
this.showMessage('复制失败', 'error');
|
|
719
|
-
},
|
|
720
|
-
|
|
721
|
-
async copyResumeCommand(session) {
|
|
722
|
-
if (!this.isResumeCommandAvailable(session)) {
|
|
723
|
-
this.showMessage('不支持此操作', 'error');
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
const command = this.buildResumeCommand(session);
|
|
727
|
-
const ok = this.fallbackCopyText(command);
|
|
728
|
-
if (ok) {
|
|
729
|
-
this.showMessage('已复制', 'success');
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
try {
|
|
733
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
734
|
-
await navigator.clipboard.writeText(command);
|
|
735
|
-
this.showMessage('已复制', 'success');
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
} catch (e) {
|
|
739
|
-
// keep fallback failure message
|
|
740
|
-
}
|
|
741
|
-
this.showMessage('复制失败', 'error');
|
|
742
|
-
},
|
|
743
|
-
|
|
744
|
-
buildProviderShareCommand(payload) {
|
|
745
|
-
if (!payload || typeof payload !== 'object') return '';
|
|
746
|
-
const name = typeof payload.name === 'string' ? payload.name.trim() : '';
|
|
747
|
-
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
748
|
-
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
749
|
-
if (!name || !baseUrl) return '';
|
|
750
|
-
|
|
751
|
-
const nameArg = this.quoteShellArg(name);
|
|
752
|
-
const urlArg = this.quoteShellArg(baseUrl);
|
|
753
|
-
const keyArg = apiKey ? this.quoteShellArg(apiKey) : '';
|
|
754
|
-
const switchCmd = `codexmate switch ${nameArg}`;
|
|
755
|
-
const addCmd = apiKey
|
|
756
|
-
? `codexmate add ${nameArg} ${urlArg} ${keyArg}`
|
|
757
|
-
: `codexmate add ${nameArg} ${urlArg}`;
|
|
758
|
-
return `${addCmd} && ${switchCmd}`;
|
|
759
|
-
},
|
|
760
|
-
|
|
761
|
-
buildClaudeShareCommand(payload) {
|
|
762
|
-
if (!payload || typeof payload !== 'object') return '';
|
|
763
|
-
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
764
|
-
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
765
|
-
const model = typeof payload.model === 'string' && payload.model.trim()
|
|
766
|
-
? payload.model.trim()
|
|
767
|
-
: 'glm-4.7';
|
|
768
|
-
if (!baseUrl || !apiKey) return '';
|
|
769
|
-
const urlArg = this.quoteShellArg(baseUrl);
|
|
770
|
-
const keyArg = this.quoteShellArg(apiKey);
|
|
771
|
-
const modelArg = this.quoteShellArg(model);
|
|
772
|
-
return `codexmate claude ${urlArg} ${keyArg} ${modelArg}`;
|
|
773
|
-
},
|
|
774
|
-
|
|
775
|
-
async copyProviderShareCommand(provider) {
|
|
776
|
-
const name = provider && typeof provider.name === 'string' ? provider.name.trim() : '';
|
|
777
|
-
if (!name) {
|
|
778
|
-
this.showMessage('参数无效', 'error');
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
if (this.providerShareLoading[name]) {
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
this.providerShareLoading[name] = true;
|
|
785
|
-
try {
|
|
786
|
-
const res = await api('export-provider', { name });
|
|
787
|
-
if (res && res.error) {
|
|
788
|
-
this.showMessage(res.error, 'error');
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
const command = this.buildProviderShareCommand(res && res.payload ? res.payload : null);
|
|
792
|
-
if (!command) {
|
|
793
|
-
this.showMessage('生成命令失败', 'error');
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
const ok = this.fallbackCopyText(command);
|
|
797
|
-
if (ok) {
|
|
798
|
-
this.showMessage('已复制', 'success');
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
803
|
-
await navigator.clipboard.writeText(command);
|
|
804
|
-
this.showMessage('已复制', 'success');
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
} catch (e) {
|
|
808
|
-
// keep fallback failure message
|
|
809
|
-
}
|
|
810
|
-
this.showMessage('复制失败', 'error');
|
|
811
|
-
} catch (e) {
|
|
812
|
-
this.showMessage('生成命令失败', 'error');
|
|
813
|
-
} finally {
|
|
814
|
-
this.providerShareLoading[name] = false;
|
|
815
|
-
}
|
|
816
|
-
},
|
|
817
|
-
|
|
818
|
-
async copyClaudeShareCommand(name) {
|
|
819
|
-
const config = this.claudeConfigs[name];
|
|
820
|
-
if (!config) {
|
|
821
|
-
this.showMessage('配置不存在', 'error');
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
if (this.claudeShareLoading[name]) return;
|
|
825
|
-
this.claudeShareLoading[name] = true;
|
|
826
|
-
try {
|
|
827
|
-
const res = await api('export-claude-share', { config });
|
|
828
|
-
if (res && res.error) {
|
|
829
|
-
this.showMessage(res.error, 'error');
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
const command = this.buildClaudeShareCommand(res && res.payload ? res.payload : null);
|
|
833
|
-
if (!command) {
|
|
834
|
-
this.showMessage('生成命令失败', 'error');
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
const ok = this.fallbackCopyText(command);
|
|
838
|
-
if (ok) {
|
|
839
|
-
this.showMessage('已复制', 'success');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
try {
|
|
843
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
844
|
-
await navigator.clipboard.writeText(command);
|
|
845
|
-
this.showMessage('已复制', 'success');
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
} catch (e) {
|
|
849
|
-
// fall through
|
|
850
|
-
}
|
|
851
|
-
this.showMessage('复制失败', 'error');
|
|
852
|
-
} catch (e) {
|
|
853
|
-
this.showMessage('生成命令失败', 'error');
|
|
854
|
-
} finally {
|
|
855
|
-
this.claudeShareLoading[name] = false;
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
|
|
859
|
-
async cloneSession(session) {
|
|
860
|
-
if (!this.isCloneAvailable(session)) {
|
|
861
|
-
this.showMessage('不支持此操作', 'error');
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
const key = this.getSessionExportKey(session);
|
|
865
|
-
if (this.sessionCloning[key]) {
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
this.sessionCloning[key] = true;
|
|
869
|
-
try {
|
|
870
|
-
const res = await api('clone-session', {
|
|
871
|
-
source: session.source,
|
|
872
|
-
sessionId: session.sessionId,
|
|
873
|
-
filePath: session.filePath
|
|
874
|
-
});
|
|
875
|
-
if (res.error) {
|
|
876
|
-
this.showMessage(res.error, 'error');
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
this.showMessage('操作成功', 'success');
|
|
881
|
-
await this.loadSessions();
|
|
882
|
-
if (res.sessionId) {
|
|
883
|
-
const matched = this.sessionsList.find(item => item.source === 'codex' && item.sessionId === res.sessionId);
|
|
884
|
-
if (matched) {
|
|
885
|
-
await this.selectSession(matched);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
} catch (e) {
|
|
889
|
-
this.showMessage('克隆失败', 'error');
|
|
890
|
-
} finally {
|
|
891
|
-
this.sessionCloning[key] = false;
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
|
|
895
|
-
async deleteSession(session) {
|
|
896
|
-
if (!this.isDeleteAvailable(session)) {
|
|
897
|
-
this.showMessage('不支持此操作', 'error');
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const key = this.getSessionExportKey(session);
|
|
901
|
-
if (this.sessionDeleting[key]) {
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
this.sessionDeleting[key] = true;
|
|
905
|
-
try {
|
|
906
|
-
const res = await api('delete-session', {
|
|
907
|
-
source: session.source,
|
|
908
|
-
sessionId: session.sessionId,
|
|
909
|
-
filePath: session.filePath
|
|
910
|
-
});
|
|
911
|
-
if (res.error) {
|
|
912
|
-
this.showMessage(res.error, 'error');
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
this.showMessage('操作成功', 'success');
|
|
916
|
-
await this.loadSessions();
|
|
917
|
-
} catch (e) {
|
|
918
|
-
this.showMessage('删除失败', 'error');
|
|
919
|
-
} finally {
|
|
920
|
-
this.sessionDeleting[key] = false;
|
|
921
|
-
}
|
|
922
|
-
},
|
|
923
|
-
|
|
924
|
-
normalizeSessionPathValue(value) {
|
|
925
|
-
if (typeof value !== 'string') return '';
|
|
926
|
-
return value.trim();
|
|
927
|
-
},
|
|
928
|
-
|
|
929
|
-
mergeSessionPathOptions(baseList = [], incomingList = []) {
|
|
930
|
-
const merged = [];
|
|
931
|
-
const seen = new Set();
|
|
932
|
-
const append = (items) => {
|
|
933
|
-
if (!Array.isArray(items)) return;
|
|
934
|
-
for (const item of items) {
|
|
935
|
-
const value = this.normalizeSessionPathValue(item);
|
|
936
|
-
if (!value) continue;
|
|
937
|
-
const key = value.toLowerCase();
|
|
938
|
-
if (seen.has(key)) continue;
|
|
939
|
-
seen.add(key);
|
|
940
|
-
merged.push(value);
|
|
941
|
-
}
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
append(baseList);
|
|
945
|
-
append(incomingList);
|
|
946
|
-
return merged;
|
|
947
|
-
},
|
|
948
|
-
|
|
949
|
-
extractPathOptionsFromSessions(sessions) {
|
|
950
|
-
const paths = [];
|
|
951
|
-
if (!Array.isArray(sessions)) {
|
|
952
|
-
return paths;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const seen = new Set();
|
|
956
|
-
for (const session of sessions) {
|
|
957
|
-
const value = this.normalizeSessionPathValue(session && session.cwd ? session.cwd : '');
|
|
958
|
-
if (!value) continue;
|
|
959
|
-
const key = value.toLowerCase();
|
|
960
|
-
if (seen.has(key)) continue;
|
|
961
|
-
seen.add(key);
|
|
962
|
-
paths.push(value);
|
|
963
|
-
}
|
|
964
|
-
return paths;
|
|
965
|
-
},
|
|
966
|
-
|
|
967
|
-
syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
|
|
968
|
-
const targetSource = source === 'claude' ? 'claude' : (source === 'all' ? 'all' : 'codex');
|
|
969
|
-
const current = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
970
|
-
? this.sessionPathOptionsMap[targetSource]
|
|
971
|
-
: [];
|
|
972
|
-
const merged = mergeWithExisting
|
|
973
|
-
? this.mergeSessionPathOptions(current, nextOptions)
|
|
974
|
-
: this.mergeSessionPathOptions([], nextOptions);
|
|
975
|
-
this.sessionPathOptionsMap = {
|
|
976
|
-
...this.sessionPathOptionsMap,
|
|
977
|
-
[targetSource]: merged
|
|
978
|
-
};
|
|
979
|
-
this.refreshSessionPathOptions(targetSource);
|
|
980
|
-
},
|
|
981
|
-
|
|
982
|
-
refreshSessionPathOptions(source) {
|
|
983
|
-
const targetSource = source === 'claude' ? 'claude' : (source === 'all' ? 'all' : 'codex');
|
|
984
|
-
const base = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
985
|
-
? [...this.sessionPathOptionsMap[targetSource]]
|
|
986
|
-
: [];
|
|
987
|
-
const selected = this.normalizeSessionPathValue(this.sessionPathFilter);
|
|
988
|
-
if (selected && !base.some(item => item.toLowerCase() === selected.toLowerCase())) {
|
|
989
|
-
base.unshift(selected);
|
|
990
|
-
}
|
|
991
|
-
if (targetSource === this.sessionFilterSource) {
|
|
992
|
-
this.sessionPathOptions = base;
|
|
993
|
-
}
|
|
994
|
-
},
|
|
995
|
-
|
|
996
|
-
async loadSessionPathOptions(options = {}) {
|
|
997
|
-
const source = options.source === 'claude' ? 'claude' : (options.source === 'all' ? 'all' : 'codex');
|
|
998
|
-
const forceRefresh = !!options.forceRefresh;
|
|
999
|
-
const loaded = !!this.sessionPathOptionsLoadedMap[source];
|
|
1000
|
-
if (!forceRefresh && loaded) {
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
const requestSeq = ++this.sessionPathRequestSeq;
|
|
1005
|
-
this.sessionPathOptionsLoading = true;
|
|
1006
|
-
try {
|
|
1007
|
-
const res = await api('list-session-paths', {
|
|
1008
|
-
source,
|
|
1009
|
-
limit: 500,
|
|
1010
|
-
forceRefresh
|
|
1011
|
-
});
|
|
1012
|
-
if (requestSeq !== this.sessionPathRequestSeq) {
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
if (res && !res.error && Array.isArray(res.paths)) {
|
|
1016
|
-
this.syncSessionPathOptionsForSource(source, res.paths, true);
|
|
1017
|
-
this.sessionPathOptionsLoadedMap = {
|
|
1018
|
-
...this.sessionPathOptionsLoadedMap,
|
|
1019
|
-
[source]: true
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
} catch (_) {
|
|
1023
|
-
// 路径补全失败不影响会话主流程
|
|
1024
|
-
} finally {
|
|
1025
|
-
if (requestSeq === this.sessionPathRequestSeq) {
|
|
1026
|
-
this.sessionPathOptionsLoading = false;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
},
|
|
1030
|
-
|
|
1031
|
-
onSessionResumeYoloChange() {
|
|
1032
|
-
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
1033
|
-
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
1034
|
-
},
|
|
1035
|
-
|
|
1036
|
-
async onSessionSourceChange() {
|
|
1037
|
-
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
1038
|
-
await this.loadSessions();
|
|
1039
|
-
},
|
|
1040
|
-
|
|
1041
|
-
async onSessionPathFilterChange() {
|
|
1042
|
-
await this.loadSessions();
|
|
1043
|
-
},
|
|
1044
|
-
|
|
1045
|
-
async onSessionFilterChange() {
|
|
1046
|
-
await this.loadSessions();
|
|
1047
|
-
},
|
|
1048
|
-
|
|
1049
|
-
async clearSessionFilters() {
|
|
1050
|
-
this.sessionFilterSource = 'all';
|
|
1051
|
-
this.sessionPathFilter = '';
|
|
1052
|
-
this.sessionQuery = '';
|
|
1053
|
-
this.sessionRoleFilter = 'all';
|
|
1054
|
-
this.sessionTimePreset = 'all';
|
|
1055
|
-
await this.onSessionSourceChange();
|
|
1056
|
-
},
|
|
1057
|
-
|
|
1058
|
-
getRecordKey(message) {
|
|
1059
|
-
if (!message || !Number.isInteger(message.recordLineIndex) || message.recordLineIndex < 0) {
|
|
1060
|
-
return '';
|
|
1061
|
-
}
|
|
1062
|
-
return String(message.recordLineIndex);
|
|
1063
|
-
},
|
|
1064
|
-
|
|
1065
|
-
getRecordRenderKey(message, idx) {
|
|
1066
|
-
const recordKey = this.getRecordKey(message);
|
|
1067
|
-
if (recordKey) {
|
|
1068
|
-
return `record-${recordKey}`;
|
|
1069
|
-
}
|
|
1070
|
-
return `record-fallback-${idx}-${message && message.timestamp ? message.timestamp : ''}`;
|
|
1071
|
-
},
|
|
1072
|
-
|
|
1073
|
-
syncActiveSessionMessageCount(messageCount) {
|
|
1074
|
-
if (!Number.isFinite(messageCount) || messageCount < 0) return;
|
|
1075
|
-
if (this.activeSession) {
|
|
1076
|
-
this.activeSession.messageCount = messageCount;
|
|
1077
|
-
}
|
|
1078
|
-
const activeKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1079
|
-
if (!activeKey) return;
|
|
1080
|
-
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === activeKey);
|
|
1081
|
-
if (matched) {
|
|
1082
|
-
matched.messageCount = messageCount;
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
|
|
1086
|
-
async loadSessions() {
|
|
1087
|
-
if (this.sessionsLoading) return;
|
|
1088
|
-
this.sessionsLoading = true;
|
|
1089
|
-
this.activeSessionDetailError = '';
|
|
1090
|
-
const params = buildSessionListParams({
|
|
1091
|
-
source: this.sessionFilterSource,
|
|
1092
|
-
pathFilter: this.sessionPathFilter,
|
|
1093
|
-
query: this.sessionQuery,
|
|
1094
|
-
roleFilter: this.sessionRoleFilter,
|
|
1095
|
-
timeRangePreset: this.sessionTimePreset
|
|
1096
|
-
});
|
|
1097
|
-
try {
|
|
1098
|
-
const res = await api('list-sessions', params);
|
|
1099
|
-
if (res.error) {
|
|
1100
|
-
this.showMessage(res.error, 'error');
|
|
1101
|
-
this.sessionsList = [];
|
|
1102
|
-
this.activeSession = null;
|
|
1103
|
-
this.activeSessionMessages = [];
|
|
1104
|
-
this.activeSessionDetailClipped = false;
|
|
1105
|
-
} else {
|
|
1106
|
-
this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
|
|
1107
|
-
this.syncSessionPathOptionsForSource(
|
|
1108
|
-
this.sessionFilterSource,
|
|
1109
|
-
this.extractPathOptionsFromSessions(this.sessionsList),
|
|
1110
|
-
true
|
|
1111
|
-
);
|
|
1112
|
-
if (this.sessionsList.length === 0) {
|
|
1113
|
-
this.activeSession = null;
|
|
1114
|
-
this.activeSessionMessages = [];
|
|
1115
|
-
this.activeSessionDetailClipped = false;
|
|
1116
|
-
} else {
|
|
1117
|
-
const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
|
|
1118
|
-
const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
|
|
1119
|
-
this.activeSession = matched || this.sessionsList[0];
|
|
1120
|
-
await this.loadActiveSessionDetail();
|
|
1121
|
-
}
|
|
1122
|
-
void this.loadSessionPathOptions({ source: this.sessionFilterSource });
|
|
1123
|
-
}
|
|
1124
|
-
} catch (e) {
|
|
1125
|
-
this.sessionsList = [];
|
|
1126
|
-
this.activeSession = null;
|
|
1127
|
-
this.activeSessionMessages = [];
|
|
1128
|
-
this.activeSessionDetailClipped = false;
|
|
1129
|
-
this.showMessage('加载会话失败', 'error');
|
|
1130
|
-
} finally {
|
|
1131
|
-
this.sessionsLoading = false;
|
|
1132
|
-
}
|
|
1133
|
-
},
|
|
1134
|
-
|
|
1135
|
-
async selectSession(session) {
|
|
1136
|
-
if (!session) return;
|
|
1137
|
-
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
1138
|
-
this.activeSession = session;
|
|
1139
|
-
this.activeSessionMessages = [];
|
|
1140
|
-
this.activeSessionDetailError = '';
|
|
1141
|
-
this.activeSessionDetailClipped = false;
|
|
1142
|
-
await this.loadActiveSessionDetail();
|
|
1143
|
-
},
|
|
1144
|
-
|
|
1145
|
-
async loadSessionStandalonePlain() {
|
|
1146
|
-
if (!this.activeSession) {
|
|
1147
|
-
this.sessionStandaloneText = '';
|
|
1148
|
-
this.sessionStandaloneTitle = '会话';
|
|
1149
|
-
this.sessionStandaloneSourceLabel = '';
|
|
1150
|
-
this.sessionStandaloneError = '';
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
1155
|
-
this.sessionStandaloneLoading = true;
|
|
1156
|
-
this.sessionStandaloneError = '';
|
|
1157
|
-
try {
|
|
1158
|
-
const res = await api('session-plain', {
|
|
1159
|
-
source: this.activeSession.source,
|
|
1160
|
-
sessionId: this.activeSession.sessionId,
|
|
1161
|
-
filePath: this.activeSession.filePath
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
if (res.error) {
|
|
1169
|
-
this.sessionStandaloneText = '';
|
|
1170
|
-
this.sessionStandaloneError = res.error;
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
1175
|
-
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
1176
|
-
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
1177
|
-
} catch (e) {
|
|
1178
|
-
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
this.sessionStandaloneText = '';
|
|
1182
|
-
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
1183
|
-
} finally {
|
|
1184
|
-
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
1185
|
-
this.sessionStandaloneLoading = false;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
},
|
|
1189
|
-
|
|
1190
|
-
async loadActiveSessionDetail() {
|
|
1191
|
-
if (!this.activeSession) {
|
|
1192
|
-
this.activeSessionMessages = [];
|
|
1193
|
-
this.activeSessionDetailError = '';
|
|
1194
|
-
this.activeSessionDetailClipped = false;
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
const requestSeq = ++this.sessionDetailRequestSeq;
|
|
1199
|
-
this.sessionDetailLoading = true;
|
|
1200
|
-
this.activeSessionDetailError = '';
|
|
1201
|
-
try {
|
|
1202
|
-
const res = await api('session-detail', {
|
|
1203
|
-
source: this.activeSession.source,
|
|
1204
|
-
sessionId: this.activeSession.sessionId,
|
|
1205
|
-
filePath: this.activeSession.filePath,
|
|
1206
|
-
messageLimit: 300
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1210
|
-
return;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
if (res.error) {
|
|
1214
|
-
this.activeSessionMessages = [];
|
|
1215
|
-
this.activeSessionDetailClipped = false;
|
|
1216
|
-
this.activeSessionDetailError = res.error;
|
|
1217
|
-
return;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
this.activeSessionMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
1221
|
-
this.activeSessionDetailClipped = !!res.clipped;
|
|
1222
|
-
if (this.activeSession) {
|
|
1223
|
-
if (res.sourceLabel) {
|
|
1224
|
-
this.activeSession.sourceLabel = res.sourceLabel;
|
|
1225
|
-
}
|
|
1226
|
-
if (res.sessionId) {
|
|
1227
|
-
this.activeSession.sessionId = res.sessionId;
|
|
1228
|
-
if (!this.activeSession.title) {
|
|
1229
|
-
this.activeSession.title = res.sessionId;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
if (res.filePath) {
|
|
1233
|
-
this.activeSession.filePath = res.filePath;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
if (res.updatedAt) {
|
|
1237
|
-
this.activeSession.updatedAt = res.updatedAt;
|
|
1238
|
-
}
|
|
1239
|
-
if (res.cwd) {
|
|
1240
|
-
this.activeSession.cwd = res.cwd;
|
|
1241
|
-
}
|
|
1242
|
-
if (Number.isFinite(res.totalMessages)) {
|
|
1243
|
-
this.syncActiveSessionMessageCount(res.totalMessages);
|
|
1244
|
-
}
|
|
1245
|
-
} catch (e) {
|
|
1246
|
-
if (requestSeq !== this.sessionDetailRequestSeq) {
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
this.activeSessionMessages = [];
|
|
1250
|
-
this.activeSessionDetailClipped = false;
|
|
1251
|
-
this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
|
|
1252
|
-
} finally {
|
|
1253
|
-
if (requestSeq === this.sessionDetailRequestSeq) {
|
|
1254
|
-
this.sessionDetailLoading = false;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
|
|
1259
|
-
downloadTextFile(fileName, content) {
|
|
1260
|
-
// 使用 UTF-8 BOM 确保文本编辑器正确识别编码
|
|
1261
|
-
const BOM = '\uFEFF';
|
|
1262
|
-
const blob = new Blob([BOM + content], { type: 'text/markdown;charset=utf-8' });
|
|
1263
|
-
const url = URL.createObjectURL(blob);
|
|
1264
|
-
const link = document.createElement('a');
|
|
1265
|
-
link.href = url;
|
|
1266
|
-
link.download = fileName;
|
|
1267
|
-
link.click();
|
|
1268
|
-
URL.revokeObjectURL(url);
|
|
1269
|
-
},
|
|
1270
|
-
|
|
1271
|
-
async exportSession(session) {
|
|
1272
|
-
const key = this.getSessionExportKey(session);
|
|
1273
|
-
if (this.sessionExporting[key]) return;
|
|
1274
|
-
|
|
1275
|
-
this.sessionExporting[key] = true;
|
|
1276
|
-
try {
|
|
1277
|
-
const res = await api('export-session', {
|
|
1278
|
-
source: session.source,
|
|
1279
|
-
sessionId: session.sessionId,
|
|
1280
|
-
filePath: session.filePath
|
|
1281
|
-
});
|
|
1282
|
-
if (res.error) {
|
|
1283
|
-
this.showMessage(res.error, 'error');
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
1288
|
-
this.downloadTextFile(fileName, res.content || '');
|
|
1289
|
-
if (res.truncated) {
|
|
1290
|
-
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
1291
|
-
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
1292
|
-
} else {
|
|
1293
|
-
this.showMessage('操作成功', 'success');
|
|
1294
|
-
}
|
|
1295
|
-
} catch (e) {
|
|
1296
|
-
this.showMessage('导出失败', 'error');
|
|
1297
|
-
} finally {
|
|
1298
|
-
this.sessionExporting[key] = false;
|
|
1299
|
-
}
|
|
1300
|
-
},
|
|
1301
|
-
|
|
1302
|
-
async switchProvider(name) {
|
|
1303
|
-
this.currentProvider = name;
|
|
1304
|
-
await this.loadModelsForProvider(name);
|
|
1305
|
-
if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
|
|
1306
|
-
this.currentModel = this.models[0];
|
|
1307
|
-
}
|
|
1308
|
-
await this.applyCodexConfigDirect({ silent: true });
|
|
1309
|
-
},
|
|
1310
|
-
|
|
1311
|
-
async onModelChange() {
|
|
1312
|
-
await this.applyCodexConfigDirect();
|
|
1313
|
-
},
|
|
1314
|
-
|
|
1315
|
-
async onServiceTierChange() {
|
|
1316
|
-
await this.applyCodexConfigDirect({ silent: true });
|
|
1317
|
-
},
|
|
1318
|
-
|
|
1319
|
-
async onReasoningEffortChange() {
|
|
1320
|
-
await this.applyCodexConfigDirect({ silent: true });
|
|
1321
|
-
},
|
|
1322
|
-
|
|
1323
|
-
async runHealthCheck() {
|
|
1324
|
-
this.healthCheckLoading = true;
|
|
1325
|
-
this.healthCheckResult = null;
|
|
1326
|
-
try {
|
|
1327
|
-
const res = await api('config-health-check', {
|
|
1328
|
-
remote: false
|
|
1329
|
-
});
|
|
1330
|
-
if (res && typeof res === 'object') {
|
|
1331
|
-
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
1332
|
-
let remote = res.remote || null;
|
|
1333
|
-
{
|
|
1334
|
-
const providers = (this.providersList || [])
|
|
1335
|
-
.filter(provider => provider && provider.name);
|
|
1336
|
-
const tasks = providers.map(provider =>
|
|
1337
|
-
this.runSpeedTest(provider.name, { silent: true })
|
|
1338
|
-
.then(result => ({ name: provider.name, result }))
|
|
1339
|
-
.catch(err => ({
|
|
1340
|
-
name: provider.name,
|
|
1341
|
-
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
1342
|
-
}))
|
|
1343
|
-
);
|
|
1344
|
-
const pairs = await Promise.all(tasks);
|
|
1345
|
-
const results = {};
|
|
1346
|
-
for (const pair of pairs) {
|
|
1347
|
-
results[pair.name] = pair.result || null;
|
|
1348
|
-
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
1349
|
-
if (issue) issues.push(issue);
|
|
1350
|
-
}
|
|
1351
|
-
remote = {
|
|
1352
|
-
type: 'speed-test',
|
|
1353
|
-
results
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
const ok = issues.length === 0;
|
|
1358
|
-
this.healthCheckResult = {
|
|
1359
|
-
...res,
|
|
1360
|
-
ok,
|
|
1361
|
-
issues,
|
|
1362
|
-
remote
|
|
1363
|
-
};
|
|
1364
|
-
if (ok) {
|
|
1365
|
-
this.showMessage('检查通过', 'success');
|
|
1366
|
-
}
|
|
1367
|
-
} else {
|
|
1368
|
-
this.healthCheckResult = null;
|
|
1369
|
-
this.showMessage('检查失败', 'error');
|
|
1370
|
-
}
|
|
1371
|
-
} catch (e) {
|
|
1372
|
-
this.healthCheckResult = null;
|
|
1373
|
-
this.showMessage('检查失败', 'error');
|
|
1374
|
-
} finally {
|
|
1375
|
-
if (this.configMode === 'claude') {
|
|
1376
|
-
try {
|
|
1377
|
-
const entries = Object.entries(this.claudeConfigs || {});
|
|
1378
|
-
await Promise.all(entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)));
|
|
1379
|
-
} catch (e) {}
|
|
1380
|
-
}
|
|
1381
|
-
this.healthCheckLoading = false;
|
|
1382
|
-
}
|
|
1383
|
-
},
|
|
1384
|
-
|
|
1385
|
-
escapeTomlString(value) {
|
|
1386
|
-
return String(value || '')
|
|
1387
|
-
.replace(/\\/g, '\\\\')
|
|
1388
|
-
.replace(/"/g, '\\"');
|
|
1389
|
-
},
|
|
1390
|
-
|
|
1391
|
-
async openConfigTemplateEditor(options = {}) {
|
|
1392
|
-
try {
|
|
1393
|
-
const res = await api('get-config-template', {
|
|
1394
|
-
provider: this.currentProvider,
|
|
1395
|
-
model: this.currentModel,
|
|
1396
|
-
serviceTier: this.serviceTier
|
|
1397
|
-
});
|
|
1398
|
-
if (res.error) {
|
|
1399
|
-
this.showMessage(res.error, 'error');
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
let template = res.template || '';
|
|
1403
|
-
const appendHint = typeof options.appendHint === 'string' ? options.appendHint.trim() : '';
|
|
1404
|
-
const appendBlock = typeof options.appendBlock === 'string' ? options.appendBlock.trim() : '';
|
|
1405
|
-
if (appendHint) {
|
|
1406
|
-
template = `${template.trimEnd()}\n\n# -------------------------------\n# ${appendHint}\n# -------------------------------\n`;
|
|
1407
|
-
}
|
|
1408
|
-
if (appendBlock) {
|
|
1409
|
-
template = `${template.trimEnd()}\n\n${appendBlock}\n`;
|
|
1410
|
-
}
|
|
1411
|
-
this.configTemplateContent = template;
|
|
1412
|
-
this.showConfigTemplateModal = true;
|
|
1413
|
-
} catch (e) {
|
|
1414
|
-
this.showMessage('加载模板失败', 'error');
|
|
1415
|
-
}
|
|
1416
|
-
},
|
|
1417
|
-
|
|
1418
|
-
async applyCodexConfigDirect(options = {}) {
|
|
1419
|
-
if (this.codexApplying) return;
|
|
1420
|
-
|
|
1421
|
-
const provider = (this.currentProvider || '').trim();
|
|
1422
|
-
const model = (this.currentModel || '').trim();
|
|
1423
|
-
if (!provider || !model) {
|
|
1424
|
-
this.showMessage('请选择提供商和模型', 'error');
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
this.codexApplying = true;
|
|
1429
|
-
try {
|
|
1430
|
-
const tplRes = await api('get-config-template', {
|
|
1431
|
-
provider,
|
|
1432
|
-
model,
|
|
1433
|
-
serviceTier: this.serviceTier,
|
|
1434
|
-
reasoningEffort: this.modelReasoningEffort
|
|
1435
|
-
});
|
|
1436
|
-
if (tplRes.error) {
|
|
1437
|
-
this.showMessage('获取模板失败', 'error');
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const applyRes = await api('apply-config-template', {
|
|
1442
|
-
template: tplRes.template
|
|
1443
|
-
});
|
|
1444
|
-
if (applyRes.error) {
|
|
1445
|
-
this.showMessage('应用模板失败', 'error');
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
if (options.silent !== true) {
|
|
1450
|
-
this.showMessage('配置已应用', 'success');
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
await this.loadAll();
|
|
1454
|
-
} catch (e) {
|
|
1455
|
-
this.showMessage('应用失败', 'error');
|
|
1456
|
-
} finally {
|
|
1457
|
-
this.codexApplying = false;
|
|
1458
|
-
}
|
|
1459
|
-
},
|
|
1460
|
-
|
|
1461
|
-
closeConfigTemplateModal() {
|
|
1462
|
-
this.showConfigTemplateModal = false;
|
|
1463
|
-
this.configTemplateContent = '';
|
|
1464
|
-
},
|
|
1465
|
-
|
|
1466
|
-
async applyConfigTemplate() {
|
|
1467
|
-
if (!this.configTemplateContent || !this.configTemplateContent.trim()) {
|
|
1468
|
-
this.showMessage('模板不能为空', 'error');
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
this.configTemplateApplying = true;
|
|
1473
|
-
try {
|
|
1474
|
-
const res = await api('apply-config-template', {
|
|
1475
|
-
template: this.configTemplateContent
|
|
1476
|
-
});
|
|
1477
|
-
if (res.error) {
|
|
1478
|
-
this.showMessage(res.error, 'error');
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
this.showMessage('模板已应用', 'success');
|
|
1482
|
-
this.closeConfigTemplateModal();
|
|
1483
|
-
await this.loadAll();
|
|
1484
|
-
} catch (e) {
|
|
1485
|
-
this.showMessage('应用模板失败', 'error');
|
|
1486
|
-
} finally {
|
|
1487
|
-
this.configTemplateApplying = false;
|
|
1488
|
-
}
|
|
1489
|
-
},
|
|
1490
|
-
|
|
1491
|
-
async openAgentsEditor() {
|
|
1492
|
-
this.setAgentsModalContext('codex');
|
|
1493
|
-
this.agentsLoading = true;
|
|
1494
|
-
try {
|
|
1495
|
-
const res = await api('get-agents-file');
|
|
1496
|
-
if (res.error) {
|
|
1497
|
-
this.showMessage(res.error, 'error');
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
this.agentsContent = res.content || '';
|
|
1501
|
-
this.agentsPath = res.path || '';
|
|
1502
|
-
this.agentsExists = !!res.exists;
|
|
1503
|
-
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1504
|
-
this.showAgentsModal = true;
|
|
1505
|
-
} catch (e) {
|
|
1506
|
-
this.showMessage('加载文件失败', 'error');
|
|
1507
|
-
} finally {
|
|
1508
|
-
this.agentsLoading = false;
|
|
1509
|
-
}
|
|
1510
|
-
},
|
|
1511
|
-
|
|
1512
|
-
async openOpenclawAgentsEditor() {
|
|
1513
|
-
this.setAgentsModalContext('openclaw');
|
|
1514
|
-
this.agentsLoading = true;
|
|
1515
|
-
try {
|
|
1516
|
-
const res = await api('get-openclaw-agents-file');
|
|
1517
|
-
if (res.error) {
|
|
1518
|
-
this.showMessage(res.error, 'error');
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
if (res.configError) {
|
|
1522
|
-
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
1523
|
-
}
|
|
1524
|
-
this.agentsContent = res.content || '';
|
|
1525
|
-
this.agentsPath = res.path || '';
|
|
1526
|
-
this.agentsExists = !!res.exists;
|
|
1527
|
-
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1528
|
-
this.showAgentsModal = true;
|
|
1529
|
-
} catch (e) {
|
|
1530
|
-
this.showMessage('加载文件失败', 'error');
|
|
1531
|
-
} finally {
|
|
1532
|
-
this.agentsLoading = false;
|
|
1533
|
-
}
|
|
1534
|
-
},
|
|
1535
|
-
|
|
1536
|
-
async openOpenclawWorkspaceEditor() {
|
|
1537
|
-
const fileName = (this.openclawWorkspaceFileName || '').trim();
|
|
1538
|
-
if (!fileName) {
|
|
1539
|
-
this.showMessage('请输入文件名', 'error');
|
|
1540
|
-
return;
|
|
1541
|
-
}
|
|
1542
|
-
this.setAgentsModalContext('openclaw-workspace', { fileName });
|
|
1543
|
-
this.agentsLoading = true;
|
|
1544
|
-
try {
|
|
1545
|
-
const res = await api('get-openclaw-workspace-file', { fileName });
|
|
1546
|
-
if (res.error) {
|
|
1547
|
-
this.showMessage(res.error, 'error');
|
|
1548
|
-
return;
|
|
1549
|
-
}
|
|
1550
|
-
if (res.configError) {
|
|
1551
|
-
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
1552
|
-
}
|
|
1553
|
-
this.agentsContent = res.content || '';
|
|
1554
|
-
this.agentsPath = res.path || '';
|
|
1555
|
-
this.agentsExists = !!res.exists;
|
|
1556
|
-
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1557
|
-
this.showAgentsModal = true;
|
|
1558
|
-
} catch (e) {
|
|
1559
|
-
this.showMessage('加载文件失败', 'error');
|
|
1560
|
-
} finally {
|
|
1561
|
-
this.agentsLoading = false;
|
|
1562
|
-
}
|
|
1563
|
-
},
|
|
1564
|
-
|
|
1565
|
-
setAgentsModalContext(context, options = {}) {
|
|
1566
|
-
if (context === 'openclaw-workspace') {
|
|
1567
|
-
const fileName = (options.fileName || this.openclawWorkspaceFileName || 'AGENTS.md').trim();
|
|
1568
|
-
this.agentsContext = 'openclaw-workspace';
|
|
1569
|
-
this.agentsWorkspaceFileName = fileName;
|
|
1570
|
-
this.agentsModalTitle = `OpenClaw 工作区文件: ${fileName}`;
|
|
1571
|
-
this.agentsModalHint = `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`;
|
|
1572
|
-
return;
|
|
1573
|
-
}
|
|
1574
|
-
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
1575
|
-
if (this.agentsContext === 'openclaw') {
|
|
1576
|
-
this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
|
|
1577
|
-
this.agentsModalHint = '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。';
|
|
1578
|
-
} else {
|
|
1579
|
-
this.agentsModalTitle = 'AGENTS.md 编辑器';
|
|
1580
|
-
this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
|
|
1581
|
-
}
|
|
1582
|
-
this.agentsWorkspaceFileName = '';
|
|
1583
|
-
},
|
|
1584
|
-
|
|
1585
|
-
closeAgentsModal() {
|
|
1586
|
-
this.showAgentsModal = false;
|
|
1587
|
-
this.agentsContent = '';
|
|
1588
|
-
this.agentsPath = '';
|
|
1589
|
-
this.agentsExists = false;
|
|
1590
|
-
this.agentsLineEnding = '\n';
|
|
1591
|
-
this.agentsSaving = false;
|
|
1592
|
-
this.agentsWorkspaceFileName = '';
|
|
1593
|
-
this.setAgentsModalContext('codex');
|
|
1594
|
-
},
|
|
1595
|
-
|
|
1596
|
-
async applyAgentsContent() {
|
|
1597
|
-
this.agentsSaving = true;
|
|
1598
|
-
try {
|
|
1599
|
-
let action = 'apply-agents-file';
|
|
1600
|
-
const params = {
|
|
1601
|
-
content: this.agentsContent,
|
|
1602
|
-
lineEnding: this.agentsLineEnding
|
|
1603
|
-
};
|
|
1604
|
-
if (this.agentsContext === 'openclaw') {
|
|
1605
|
-
action = 'apply-openclaw-agents-file';
|
|
1606
|
-
} else if (this.agentsContext === 'openclaw-workspace') {
|
|
1607
|
-
action = 'apply-openclaw-workspace-file';
|
|
1608
|
-
params.fileName = this.agentsWorkspaceFileName;
|
|
1609
|
-
}
|
|
1610
|
-
const res = await api(action, params);
|
|
1611
|
-
if (res.error) {
|
|
1612
|
-
this.showMessage(res.error, 'error');
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
const successLabel = this.agentsContext === 'openclaw-workspace'
|
|
1616
|
-
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
1617
|
-
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
1618
|
-
this.showMessage(successLabel, 'success');
|
|
1619
|
-
this.closeAgentsModal();
|
|
1620
|
-
} catch (e) {
|
|
1621
|
-
this.showMessage('保存失败', 'error');
|
|
1622
|
-
} finally {
|
|
1623
|
-
this.agentsSaving = false;
|
|
1624
|
-
}
|
|
1625
|
-
},
|
|
1626
|
-
|
|
1627
|
-
async addProvider() {
|
|
1628
|
-
if (!this.newProvider.name || !this.newProvider.url) {
|
|
1629
|
-
return this.showMessage('名称和URL必填', 'error');
|
|
1630
|
-
}
|
|
1631
|
-
const name = this.newProvider.name.trim();
|
|
1632
|
-
if (!name) {
|
|
1633
|
-
return this.showMessage('名称不能为空', 'error');
|
|
1634
|
-
}
|
|
1635
|
-
if (this.providersList.some(item => item.name === name)) {
|
|
1636
|
-
return this.showMessage('名称已存在', 'error');
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
try {
|
|
1640
|
-
const res = await api('add-provider', {
|
|
1641
|
-
name,
|
|
1642
|
-
url: this.newProvider.url.trim(),
|
|
1643
|
-
key: this.newProvider.key || ''
|
|
1644
|
-
});
|
|
1645
|
-
if (res.error) {
|
|
1646
|
-
this.showMessage(res.error, 'error');
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
this.showMessage('操作成功', 'success');
|
|
1651
|
-
this.closeAddModal();
|
|
1652
|
-
await this.loadAll();
|
|
1653
|
-
} catch (e) {
|
|
1654
|
-
this.showMessage('添加失败', 'error');
|
|
1655
|
-
}
|
|
1656
|
-
},
|
|
1657
|
-
|
|
1658
|
-
async deleteProvider(name) {
|
|
1659
|
-
const res = await api('delete-provider', { name });
|
|
1660
|
-
if (res.error) {
|
|
1661
|
-
this.showMessage(res.error, 'error');
|
|
1662
|
-
return;
|
|
1663
|
-
}
|
|
1664
|
-
if (res.switched && res.provider) {
|
|
1665
|
-
this.showMessage(`已删除提供商,自动切换到 ${res.provider}${res.model ? ` / ${res.model}` : ''}`, 'success');
|
|
1666
|
-
} else {
|
|
1667
|
-
this.showMessage('操作成功', 'success');
|
|
1668
|
-
}
|
|
1669
|
-
await this.loadAll();
|
|
1670
|
-
},
|
|
1671
|
-
|
|
1672
|
-
openEditModal(provider) {
|
|
1673
|
-
this.editingProvider = {
|
|
1674
|
-
name: provider.name,
|
|
1675
|
-
url: provider.url || '',
|
|
1676
|
-
key: ''
|
|
1677
|
-
};
|
|
1678
|
-
this.showEditModal = true;
|
|
1679
|
-
},
|
|
1680
|
-
|
|
1681
|
-
async updateProvider() {
|
|
1682
|
-
if (!this.editingProvider.url) {
|
|
1683
|
-
return this.showMessage('URL 必填', 'error');
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
const name = this.editingProvider.name;
|
|
1687
|
-
const url = this.editingProvider.url.trim();
|
|
1688
|
-
const key = this.editingProvider.key || '';
|
|
1689
|
-
this.closeEditModal();
|
|
1690
|
-
try {
|
|
1691
|
-
const res = await api('update-provider', { name, url, key });
|
|
1692
|
-
if (res.error) {
|
|
1693
|
-
this.showMessage(res.error, 'error');
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
this.showMessage('操作成功', 'success');
|
|
1697
|
-
await this.loadAll();
|
|
1698
|
-
} catch (e) {
|
|
1699
|
-
this.showMessage('更新失败', 'error');
|
|
1700
|
-
}
|
|
1701
|
-
},
|
|
1702
|
-
|
|
1703
|
-
closeEditModal() {
|
|
1704
|
-
this.showEditModal = false;
|
|
1705
|
-
this.editingProvider = { name: '', url: '', key: '' };
|
|
1706
|
-
},
|
|
1707
|
-
|
|
1708
|
-
async resetConfig() {
|
|
1709
|
-
if (this.resetConfigLoading) return;
|
|
1710
|
-
this.resetConfigLoading = true;
|
|
1711
|
-
try {
|
|
1712
|
-
const res = await api('reset-config');
|
|
1713
|
-
if (res.error) {
|
|
1714
|
-
this.showMessage(res.error, 'error');
|
|
1715
|
-
return;
|
|
1716
|
-
}
|
|
1717
|
-
const backup = res.backupFile ? `(已备份: ${res.backupFile})` : '';
|
|
1718
|
-
this.showMessage(`配置已重装${backup}`, 'success');
|
|
1719
|
-
await this.loadAll();
|
|
1720
|
-
} catch (e) {
|
|
1721
|
-
this.showMessage('重装失败', 'error');
|
|
1722
|
-
} finally {
|
|
1723
|
-
this.resetConfigLoading = false;
|
|
1724
|
-
}
|
|
1725
|
-
},
|
|
1726
|
-
|
|
1727
|
-
async addModel() {
|
|
1728
|
-
if (!this.newModelName || !this.newModelName.trim()) {
|
|
1729
|
-
return this.showMessage('请输入模型', 'error');
|
|
1730
|
-
}
|
|
1731
|
-
const res = await api('add-model', { model: this.newModelName.trim() });
|
|
1732
|
-
if (res.error) {
|
|
1733
|
-
this.showMessage(res.error, 'error');
|
|
1734
|
-
} else {
|
|
1735
|
-
this.showMessage('操作成功', 'success');
|
|
1736
|
-
this.closeModelModal();
|
|
1737
|
-
await this.loadAll();
|
|
1738
|
-
}
|
|
1739
|
-
},
|
|
1740
|
-
|
|
1741
|
-
async removeModel(model) {
|
|
1742
|
-
const res = await api('delete-model', { model });
|
|
1743
|
-
if (res.error) {
|
|
1744
|
-
this.showMessage(res.error, 'error');
|
|
1745
|
-
} else {
|
|
1746
|
-
this.showMessage('操作成功', 'success');
|
|
1747
|
-
await this.loadAll();
|
|
1748
|
-
}
|
|
1749
|
-
},
|
|
1750
|
-
|
|
1751
|
-
closeAddModal() {
|
|
1752
|
-
this.showAddModal = false;
|
|
1753
|
-
this.newProvider = { name: '', url: '', key: '' };
|
|
1754
|
-
},
|
|
1755
|
-
|
|
1756
|
-
closeModelModal() {
|
|
1757
|
-
this.showModelModal = false;
|
|
1758
|
-
this.newModelName = '';
|
|
1759
|
-
},
|
|
1760
|
-
|
|
1761
|
-
formatKey(key) {
|
|
1762
|
-
if (!key) return '(未设置)';
|
|
1763
|
-
if (key.length > 10) {
|
|
1764
|
-
return key.substring(0, 3) + '****' + key.substring(key.length - 3);
|
|
1765
|
-
}
|
|
1766
|
-
return '****';
|
|
1767
|
-
},
|
|
1768
|
-
|
|
1769
|
-
displayApiKey(configName) {
|
|
1770
|
-
const key = this.claudeConfigs[configName]?.apiKey;
|
|
1771
|
-
return this.formatKey(key);
|
|
1772
|
-
},
|
|
1773
|
-
|
|
1774
|
-
switchClaudeConfig(name) {
|
|
1775
|
-
this.currentClaudeConfig = name;
|
|
1776
|
-
this.refreshClaudeModelContext();
|
|
1777
|
-
},
|
|
1778
|
-
|
|
1779
|
-
onClaudeModelChange() {
|
|
1780
|
-
const name = this.currentClaudeConfig;
|
|
1781
|
-
if (!name) {
|
|
1782
|
-
this.showMessage('请先选择配置', 'error');
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
const model = (this.currentClaudeModel || '').trim();
|
|
1786
|
-
if (!model) {
|
|
1787
|
-
this.showMessage('请输入模型', 'error');
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
const existing = this.claudeConfigs[name] || {};
|
|
1791
|
-
this.currentClaudeModel = model;
|
|
1792
|
-
this.claudeConfigs[name] = {
|
|
1793
|
-
apiKey: existing.apiKey || '',
|
|
1794
|
-
baseUrl: existing.baseUrl || '',
|
|
1795
|
-
model: model,
|
|
1796
|
-
hasKey: !!existing.apiKey
|
|
1797
|
-
};
|
|
1798
|
-
this.saveClaudeConfigs();
|
|
1799
|
-
this.updateClaudeModelsCurrent();
|
|
1800
|
-
if (!this.claudeConfigs[name].apiKey) {
|
|
1801
|
-
this.showMessage('请先配置 API Key', 'error');
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
this.applyClaudeConfig(name);
|
|
1805
|
-
},
|
|
1806
|
-
|
|
1807
|
-
saveClaudeConfigs() {
|
|
1808
|
-
localStorage.setItem('claudeConfigs', JSON.stringify(this.claudeConfigs));
|
|
1809
|
-
},
|
|
1810
|
-
|
|
1811
|
-
openEditConfigModal(name) {
|
|
1812
|
-
const config = this.claudeConfigs[name];
|
|
1813
|
-
this.editingConfig = {
|
|
1814
|
-
name: name,
|
|
1815
|
-
apiKey: config.apiKey || '',
|
|
1816
|
-
baseUrl: config.baseUrl || '',
|
|
1817
|
-
model: config.model || ''
|
|
1818
|
-
};
|
|
1819
|
-
this.showEditConfigModal = true;
|
|
1820
|
-
},
|
|
1821
|
-
|
|
1822
|
-
updateConfig() {
|
|
1823
|
-
const name = this.editingConfig.name;
|
|
1824
|
-
this.claudeConfigs[name] = {
|
|
1825
|
-
apiKey: this.editingConfig.apiKey,
|
|
1826
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
1827
|
-
model: this.editingConfig.model,
|
|
1828
|
-
hasKey: !!this.editingConfig.apiKey
|
|
1829
|
-
};
|
|
1830
|
-
this.saveClaudeConfigs();
|
|
1831
|
-
this.showMessage('操作成功', 'success');
|
|
1832
|
-
this.closeEditConfigModal();
|
|
1833
|
-
if (name === this.currentClaudeConfig) {
|
|
1834
|
-
this.refreshClaudeModelContext();
|
|
1835
|
-
}
|
|
1836
|
-
},
|
|
1837
|
-
|
|
1838
|
-
closeEditConfigModal() {
|
|
1839
|
-
this.showEditConfigModal = false;
|
|
1840
|
-
this.editingConfig = { name: '', apiKey: '', baseUrl: '', model: '' };
|
|
1841
|
-
},
|
|
1842
|
-
|
|
1843
|
-
async saveAndApplyConfig() {
|
|
1844
|
-
const name = this.editingConfig.name;
|
|
1845
|
-
this.claudeConfigs[name] = {
|
|
1846
|
-
apiKey: this.editingConfig.apiKey,
|
|
1847
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
1848
|
-
model: this.editingConfig.model,
|
|
1849
|
-
hasKey: !!this.editingConfig.apiKey
|
|
1850
|
-
};
|
|
1851
|
-
this.saveClaudeConfigs();
|
|
1852
|
-
|
|
1853
|
-
const config = this.claudeConfigs[name];
|
|
1854
|
-
if (!config.apiKey) {
|
|
1855
|
-
this.showMessage('已保存,未应用', 'info');
|
|
1856
|
-
this.closeEditConfigModal();
|
|
1857
|
-
if (name === this.currentClaudeConfig) {
|
|
1858
|
-
this.refreshClaudeModelContext();
|
|
1859
|
-
}
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
const res = await api('apply-claude-config', { config });
|
|
1864
|
-
if (res.error || res.success === false) {
|
|
1865
|
-
this.showMessage(res.error || '应用配置失败', 'error');
|
|
1866
|
-
} else {
|
|
1867
|
-
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
1868
|
-
this.showMessage(`已保存并应用到 Claude 配置${targetTip}`, 'success');
|
|
1869
|
-
this.closeEditConfigModal();
|
|
1870
|
-
if (name === this.currentClaudeConfig) {
|
|
1871
|
-
this.refreshClaudeModelContext();
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
},
|
|
1875
|
-
|
|
1876
|
-
addClaudeConfig() {
|
|
1877
|
-
if (!this.newClaudeConfig.name || !this.newClaudeConfig.name.trim()) {
|
|
1878
|
-
return this.showMessage('请输入名称', 'error');
|
|
1879
|
-
}
|
|
1880
|
-
const name = this.newClaudeConfig.name.trim();
|
|
1881
|
-
if (this.claudeConfigs[name]) {
|
|
1882
|
-
return this.showMessage('名称已存在', 'error');
|
|
1883
|
-
}
|
|
1884
|
-
const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
|
|
1885
|
-
if (duplicateName) {
|
|
1886
|
-
return this.showMessage('配置已存在', 'info');
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
this.claudeConfigs[name] = {
|
|
1890
|
-
apiKey: this.newClaudeConfig.apiKey,
|
|
1891
|
-
baseUrl: this.newClaudeConfig.baseUrl,
|
|
1892
|
-
model: this.newClaudeConfig.model,
|
|
1893
|
-
hasKey: !!this.newClaudeConfig.apiKey
|
|
1894
|
-
};
|
|
1895
|
-
|
|
1896
|
-
this.currentClaudeConfig = name;
|
|
1897
|
-
this.saveClaudeConfigs();
|
|
1898
|
-
this.showMessage('操作成功', 'success');
|
|
1899
|
-
this.closeClaudeConfigModal();
|
|
1900
|
-
this.refreshClaudeModelContext();
|
|
1901
|
-
},
|
|
1902
|
-
|
|
1903
|
-
deleteClaudeConfig(name) {
|
|
1904
|
-
if (Object.keys(this.claudeConfigs).length <= 1) {
|
|
1905
|
-
return this.showMessage('至少保留一项', 'error');
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
if (!confirm(`确定删除配置 "${name}"?`)) return;
|
|
1909
|
-
|
|
1910
|
-
delete this.claudeConfigs[name];
|
|
1911
|
-
if (this.currentClaudeConfig === name) {
|
|
1912
|
-
this.currentClaudeConfig = Object.keys(this.claudeConfigs)[0];
|
|
1913
|
-
}
|
|
1914
|
-
this.saveClaudeConfigs();
|
|
1915
|
-
this.showMessage('操作成功', 'success');
|
|
1916
|
-
this.refreshClaudeModelContext();
|
|
1917
|
-
},
|
|
1918
|
-
|
|
1919
|
-
async applyClaudeConfig(name) {
|
|
1920
|
-
this.currentClaudeConfig = name;
|
|
1921
|
-
this.refreshClaudeModelContext();
|
|
1922
|
-
const config = this.claudeConfigs[name];
|
|
1923
|
-
|
|
1924
|
-
if (!config.apiKey) {
|
|
1925
|
-
return this.showMessage('请先配置 API Key', 'error');
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
const res = await api('apply-claude-config', { config });
|
|
1929
|
-
if (res.error || res.success === false) {
|
|
1930
|
-
this.showMessage(res.error || '应用配置失败', 'error');
|
|
1931
|
-
} else {
|
|
1932
|
-
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
1933
|
-
this.showMessage(`已应用配置到 Claude 设置: ${name}${targetTip}`, 'success');
|
|
1934
|
-
}
|
|
1935
|
-
},
|
|
1936
|
-
|
|
1937
|
-
closeClaudeConfigModal() {
|
|
1938
|
-
this.showClaudeConfigModal = false;
|
|
1939
|
-
this.newClaudeConfig = {
|
|
1940
|
-
name: '',
|
|
1941
|
-
apiKey: '',
|
|
1942
|
-
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
1943
|
-
model: 'glm-4.7'
|
|
1944
|
-
};
|
|
1945
|
-
},
|
|
1946
|
-
|
|
1947
|
-
getOpenclawParser() {
|
|
1948
|
-
if (window.JSON5 && typeof window.JSON5.parse === 'function' && typeof window.JSON5.stringify === 'function') {
|
|
1949
|
-
return {
|
|
1950
|
-
parse: window.JSON5.parse,
|
|
1951
|
-
stringify: window.JSON5.stringify
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
return {
|
|
1955
|
-
parse: JSON.parse,
|
|
1956
|
-
stringify: JSON.stringify
|
|
1957
|
-
};
|
|
1958
|
-
},
|
|
1959
|
-
|
|
1960
|
-
parseOpenclawContent(content, options = {}) {
|
|
1961
|
-
const allowEmpty = !!options.allowEmpty;
|
|
1962
|
-
const raw = typeof content === 'string' ? content.trim() : '';
|
|
1963
|
-
if (!raw) {
|
|
1964
|
-
if (allowEmpty) {
|
|
1965
|
-
return { ok: true, data: {} };
|
|
1966
|
-
}
|
|
1967
|
-
return { ok: false, error: '配置内容为空' };
|
|
1968
|
-
}
|
|
1969
|
-
try {
|
|
1970
|
-
const parser = this.getOpenclawParser();
|
|
1971
|
-
const data = parser.parse(raw);
|
|
1972
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
1973
|
-
return { ok: false, error: '配置格式错误(根节点必须是对象)' };
|
|
1974
|
-
}
|
|
1975
|
-
return { ok: true, data };
|
|
1976
|
-
} catch (e) {
|
|
1977
|
-
return { ok: false, error: e.message || '解析失败' };
|
|
1978
|
-
}
|
|
1979
|
-
},
|
|
1980
|
-
|
|
1981
|
-
stringifyOpenclawConfig(data) {
|
|
1982
|
-
const parser = this.getOpenclawParser();
|
|
1983
|
-
try {
|
|
1984
|
-
return parser.stringify(data, null, 2);
|
|
1985
|
-
} catch (e) {
|
|
1986
|
-
return JSON.stringify(data, null, 2);
|
|
1987
|
-
}
|
|
1988
|
-
},
|
|
1989
|
-
|
|
1990
|
-
resetOpenclawStructured() {
|
|
1991
|
-
this.openclawStructured = {
|
|
1992
|
-
agentPrimary: '',
|
|
1993
|
-
agentFallbacks: [''],
|
|
1994
|
-
workspace: '',
|
|
1995
|
-
timeout: '',
|
|
1996
|
-
contextTokens: '',
|
|
1997
|
-
maxConcurrent: '',
|
|
1998
|
-
envItems: [{ key: '', value: '', show: false }],
|
|
1999
|
-
toolsProfile: 'default',
|
|
2000
|
-
toolsAllow: [''],
|
|
2001
|
-
toolsDeny: ['']
|
|
2002
|
-
};
|
|
2003
|
-
this.openclawAgentsList = [];
|
|
2004
|
-
this.openclawProviders = [];
|
|
2005
|
-
this.openclawMissingProviders = [];
|
|
2006
|
-
},
|
|
2007
|
-
|
|
2008
|
-
getOpenclawQuickDefaults() {
|
|
2009
|
-
return {
|
|
2010
|
-
providerName: '',
|
|
2011
|
-
baseUrl: '',
|
|
2012
|
-
apiKey: '',
|
|
2013
|
-
apiType: 'openai-responses',
|
|
2014
|
-
modelId: '',
|
|
2015
|
-
modelName: '',
|
|
2016
|
-
contextWindow: '',
|
|
2017
|
-
maxTokens: '',
|
|
2018
|
-
setPrimary: true,
|
|
2019
|
-
overrideProvider: true,
|
|
2020
|
-
overrideModels: true,
|
|
2021
|
-
showKey: false
|
|
2022
|
-
};
|
|
2023
|
-
},
|
|
2024
|
-
|
|
2025
|
-
resetOpenclawQuick() {
|
|
2026
|
-
this.openclawQuick = this.getOpenclawQuickDefaults();
|
|
2027
|
-
},
|
|
2028
|
-
|
|
2029
|
-
toggleOpenclawQuickKey() {
|
|
2030
|
-
this.openclawQuick.showKey = !this.openclawQuick.showKey;
|
|
2031
|
-
},
|
|
2032
|
-
|
|
2033
|
-
fillOpenclawQuickFromConfig(config) {
|
|
2034
|
-
const defaults = this.getOpenclawQuickDefaults();
|
|
2035
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
2036
|
-
this.openclawQuick = defaults;
|
|
2037
|
-
return;
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
const agentDefaults = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
2041
|
-
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
2042
|
-
? config.agents.defaults
|
|
2043
|
-
: {};
|
|
2044
|
-
const modelConfig = agentDefaults.model;
|
|
2045
|
-
const legacyAgent = config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
2046
|
-
? config.agent
|
|
2047
|
-
: {};
|
|
2048
|
-
|
|
2049
|
-
let primaryRef = '';
|
|
2050
|
-
if (modelConfig && typeof modelConfig === 'object' && !Array.isArray(modelConfig) && typeof modelConfig.primary === 'string') {
|
|
2051
|
-
primaryRef = modelConfig.primary;
|
|
2052
|
-
} else if (typeof modelConfig === 'string') {
|
|
2053
|
-
primaryRef = modelConfig;
|
|
2054
|
-
}
|
|
2055
|
-
if (!primaryRef) {
|
|
2056
|
-
if (typeof legacyAgent.model === 'string') {
|
|
2057
|
-
primaryRef = legacyAgent.model;
|
|
2058
|
-
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
2059
|
-
primaryRef = legacyAgent.model.primary;
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
let providerName = '';
|
|
2064
|
-
let modelId = '';
|
|
2065
|
-
if (primaryRef) {
|
|
2066
|
-
const parts = primaryRef.split('/');
|
|
2067
|
-
if (parts.length >= 2) {
|
|
2068
|
-
providerName = parts.shift().trim();
|
|
2069
|
-
modelId = parts.join('/').trim();
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
const providers = config.models && typeof config.models === 'object' && !Array.isArray(config.models)
|
|
2074
|
-
&& config.models.providers && typeof config.models.providers === 'object' && !Array.isArray(config.models.providers)
|
|
2075
|
-
? config.models.providers
|
|
2076
|
-
: null;
|
|
2077
|
-
let providerConfig = providerName && providers ? providers[providerName] : null;
|
|
2078
|
-
if (!providerName && providers) {
|
|
2079
|
-
const providerKeys = Object.keys(providers);
|
|
2080
|
-
if (providerKeys.length === 1) {
|
|
2081
|
-
providerName = providerKeys[0];
|
|
2082
|
-
providerConfig = providers[providerName];
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
let modelEntry = null;
|
|
2087
|
-
if (providerConfig && typeof providerConfig === 'object' && Array.isArray(providerConfig.models)) {
|
|
2088
|
-
if (modelId) {
|
|
2089
|
-
modelEntry = providerConfig.models.find(item => item && item.id === modelId);
|
|
2090
|
-
}
|
|
2091
|
-
if (!modelEntry && providerConfig.models.length === 1) {
|
|
2092
|
-
modelEntry = providerConfig.models[0];
|
|
2093
|
-
if (!modelId && modelEntry && typeof modelEntry.id === 'string') {
|
|
2094
|
-
modelId = modelEntry.id;
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
const baseUrl = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.baseUrl === 'string'
|
|
2100
|
-
? providerConfig.baseUrl
|
|
2101
|
-
: '';
|
|
2102
|
-
const apiKey = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.apiKey === 'string'
|
|
2103
|
-
? providerConfig.apiKey
|
|
2104
|
-
: '';
|
|
2105
|
-
const apiType = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.api === 'string'
|
|
2106
|
-
? providerConfig.api
|
|
2107
|
-
: defaults.apiType;
|
|
2108
|
-
|
|
2109
|
-
this.openclawQuick = {
|
|
2110
|
-
...defaults,
|
|
2111
|
-
providerName,
|
|
2112
|
-
baseUrl,
|
|
2113
|
-
apiKey,
|
|
2114
|
-
apiType,
|
|
2115
|
-
modelId: modelId || '',
|
|
2116
|
-
modelName: modelEntry && typeof modelEntry.name === 'string' ? modelEntry.name : '',
|
|
2117
|
-
contextWindow: modelEntry && typeof modelEntry.contextWindow === 'number'
|
|
2118
|
-
? String(modelEntry.contextWindow)
|
|
2119
|
-
: '',
|
|
2120
|
-
maxTokens: modelEntry && typeof modelEntry.maxTokens === 'number'
|
|
2121
|
-
? String(modelEntry.maxTokens)
|
|
2122
|
-
: ''
|
|
2123
|
-
};
|
|
2124
|
-
},
|
|
2125
|
-
|
|
2126
|
-
syncOpenclawQuickFromText(options = {}) {
|
|
2127
|
-
const silent = !!options.silent;
|
|
2128
|
-
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
2129
|
-
if (!parsed.ok) {
|
|
2130
|
-
this.resetOpenclawQuick();
|
|
2131
|
-
if (!silent) {
|
|
2132
|
-
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
2133
|
-
}
|
|
2134
|
-
return false;
|
|
2135
|
-
}
|
|
2136
|
-
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
2137
|
-
if (!silent) {
|
|
2138
|
-
this.showMessage('已读取配置', 'success');
|
|
2139
|
-
}
|
|
2140
|
-
return true;
|
|
2141
|
-
},
|
|
2142
|
-
|
|
2143
|
-
mergeOpenclawModelEntry(existing, incoming, overwrite = false) {
|
|
2144
|
-
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
2145
|
-
return { ...incoming };
|
|
2146
|
-
}
|
|
2147
|
-
if (overwrite) {
|
|
2148
|
-
return { ...incoming };
|
|
2149
|
-
}
|
|
2150
|
-
const merged = { ...existing };
|
|
2151
|
-
for (const [key, value] of Object.entries(incoming || {})) {
|
|
2152
|
-
if (merged[key] === undefined || merged[key] === null || merged[key] === '') {
|
|
2153
|
-
merged[key] = value;
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
return merged;
|
|
2157
|
-
},
|
|
2158
|
-
|
|
2159
|
-
fillOpenclawStructured(config) {
|
|
2160
|
-
const defaults = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
2161
|
-
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
2162
|
-
? config.agents.defaults
|
|
2163
|
-
: {};
|
|
2164
|
-
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
2165
|
-
? defaults.model
|
|
2166
|
-
: {};
|
|
2167
|
-
const legacyAgent = config && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
2168
|
-
? config.agent
|
|
2169
|
-
: {};
|
|
2170
|
-
const fallbackList = Array.isArray(model.fallbacks)
|
|
2171
|
-
? model.fallbacks.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
2172
|
-
: [];
|
|
2173
|
-
const env = config && config.env && typeof config.env === 'object' && !Array.isArray(config.env)
|
|
2174
|
-
? config.env
|
|
2175
|
-
: {};
|
|
2176
|
-
const envItems = Object.entries(env).map(([key, value]) => ({
|
|
2177
|
-
key,
|
|
2178
|
-
value: value == null ? '' : String(value),
|
|
2179
|
-
show: false
|
|
2180
|
-
}));
|
|
2181
|
-
const tools = config && config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
2182
|
-
? config.tools
|
|
2183
|
-
: {};
|
|
2184
|
-
|
|
2185
|
-
let primary = typeof model.primary === 'string' ? model.primary : '';
|
|
2186
|
-
if (!primary) {
|
|
2187
|
-
if (typeof legacyAgent.model === 'string') {
|
|
2188
|
-
primary = legacyAgent.model;
|
|
2189
|
-
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
2190
|
-
primary = legacyAgent.model.primary;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
this.openclawStructured = {
|
|
2195
|
-
agentPrimary: primary,
|
|
2196
|
-
agentFallbacks: fallbackList.length ? fallbackList : [''],
|
|
2197
|
-
workspace: typeof defaults.workspace === 'string' ? defaults.workspace : '',
|
|
2198
|
-
timeout: typeof defaults.timeout === 'number' && Number.isFinite(defaults.timeout)
|
|
2199
|
-
? String(defaults.timeout)
|
|
2200
|
-
: '',
|
|
2201
|
-
contextTokens: typeof defaults.contextTokens === 'number' && Number.isFinite(defaults.contextTokens)
|
|
2202
|
-
? String(defaults.contextTokens)
|
|
2203
|
-
: '',
|
|
2204
|
-
maxConcurrent: typeof defaults.maxConcurrent === 'number' && Number.isFinite(defaults.maxConcurrent)
|
|
2205
|
-
? String(defaults.maxConcurrent)
|
|
2206
|
-
: '',
|
|
2207
|
-
envItems: envItems.length ? envItems : [{ key: '', value: '', show: false }],
|
|
2208
|
-
toolsProfile: typeof tools.profile === 'string' && tools.profile.trim() ? tools.profile : 'default',
|
|
2209
|
-
toolsAllow: Array.isArray(tools.allow) && tools.allow.length
|
|
2210
|
-
? tools.allow.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
2211
|
-
: [''],
|
|
2212
|
-
toolsDeny: Array.isArray(tools.deny) && tools.deny.length
|
|
2213
|
-
? tools.deny.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
2214
|
-
: ['']
|
|
2215
|
-
};
|
|
2216
|
-
},
|
|
2217
|
-
|
|
2218
|
-
syncOpenclawStructuredFromText(options = {}) {
|
|
2219
|
-
const silent = !!options.silent;
|
|
2220
|
-
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
2221
|
-
if (!parsed.ok) {
|
|
2222
|
-
this.resetOpenclawStructured();
|
|
2223
|
-
this.resetOpenclawQuick();
|
|
2224
|
-
if (!silent) {
|
|
2225
|
-
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
2226
|
-
}
|
|
2227
|
-
return false;
|
|
2228
|
-
}
|
|
2229
|
-
this.fillOpenclawStructured(parsed.data);
|
|
2230
|
-
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
2231
|
-
this.refreshOpenclawProviders(parsed.data);
|
|
2232
|
-
this.refreshOpenclawAgentsList(parsed.data);
|
|
2233
|
-
if (!silent) {
|
|
2234
|
-
this.showMessage('已刷新配置', 'success');
|
|
2235
|
-
}
|
|
2236
|
-
return true;
|
|
2237
|
-
},
|
|
2238
|
-
|
|
2239
|
-
getOpenclawActiveProviders(config) {
|
|
2240
|
-
const active = new Set();
|
|
2241
|
-
const addProvider = (ref) => {
|
|
2242
|
-
if (typeof ref !== 'string') return;
|
|
2243
|
-
const text = ref.trim();
|
|
2244
|
-
if (!text) return;
|
|
2245
|
-
const parts = text.split('/');
|
|
2246
|
-
if (parts.length < 2) return;
|
|
2247
|
-
const provider = parts[0].trim();
|
|
2248
|
-
if (provider) active.add(provider);
|
|
2249
|
-
};
|
|
2250
|
-
const defaults = config && config.agents && config.agents.defaults
|
|
2251
|
-
? config.agents.defaults
|
|
2252
|
-
: {};
|
|
2253
|
-
const model = defaults && defaults.model;
|
|
2254
|
-
if (model && typeof model === 'object' && !Array.isArray(model)) {
|
|
2255
|
-
addProvider(model.primary);
|
|
2256
|
-
if (Array.isArray(model.fallbacks)) {
|
|
2257
|
-
for (const item of model.fallbacks) {
|
|
2258
|
-
addProvider(item);
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
} else if (typeof model === 'string') {
|
|
2262
|
-
addProvider(model);
|
|
2263
|
-
}
|
|
2264
|
-
const modelsDefaults = config && config.models && config.models.defaults
|
|
2265
|
-
? config.models.defaults
|
|
2266
|
-
: {};
|
|
2267
|
-
if (modelsDefaults && typeof modelsDefaults.provider === 'string' && modelsDefaults.provider.trim()) {
|
|
2268
|
-
active.add(modelsDefaults.provider.trim());
|
|
2269
|
-
}
|
|
2270
|
-
if (modelsDefaults && typeof modelsDefaults.model === 'string') {
|
|
2271
|
-
addProvider(modelsDefaults.model);
|
|
2272
|
-
}
|
|
2273
|
-
return active;
|
|
2274
|
-
},
|
|
2275
|
-
|
|
2276
|
-
maskProviderValue(value) {
|
|
2277
|
-
const text = value == null ? '' : String(value);
|
|
2278
|
-
if (!text) return '****';
|
|
2279
|
-
if (text.length <= 6) return '****';
|
|
2280
|
-
return `${text.slice(0, 3)}****${text.slice(-3)}`;
|
|
2281
|
-
},
|
|
2282
|
-
|
|
2283
|
-
formatProviderValue(key, value) {
|
|
2284
|
-
if (typeof value === 'undefined' || value === null) {
|
|
2285
|
-
return '';
|
|
2286
|
-
}
|
|
2287
|
-
let text = '';
|
|
2288
|
-
if (typeof value === 'string') {
|
|
2289
|
-
text = value;
|
|
2290
|
-
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
2291
|
-
text = String(value);
|
|
2292
|
-
} else {
|
|
2293
|
-
try {
|
|
2294
|
-
text = JSON.stringify(value);
|
|
2295
|
-
} catch (_) {
|
|
2296
|
-
text = String(value);
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
if (!text) return '';
|
|
2300
|
-
if (/key|token|secret|password/i.test(key)) {
|
|
2301
|
-
return this.maskProviderValue(text);
|
|
2302
|
-
}
|
|
2303
|
-
if (text.length > 160) {
|
|
2304
|
-
return `${text.slice(0, 157)}...`;
|
|
2305
|
-
}
|
|
2306
|
-
return text;
|
|
2307
|
-
},
|
|
2308
|
-
|
|
2309
|
-
collectOpenclawProviders(source, providerMap, activeProviders, entries) {
|
|
2310
|
-
if (!providerMap || typeof providerMap !== 'object' || Array.isArray(providerMap)) {
|
|
2311
|
-
return;
|
|
2312
|
-
}
|
|
2313
|
-
const keys = Object.keys(providerMap).sort();
|
|
2314
|
-
for (const key of keys) {
|
|
2315
|
-
const value = providerMap[key];
|
|
2316
|
-
const fields = [];
|
|
2317
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
2318
|
-
const fieldKeys = Object.keys(value).sort();
|
|
2319
|
-
for (const fieldKey of fieldKeys) {
|
|
2320
|
-
const fieldValue = this.formatProviderValue(fieldKey, value[fieldKey]);
|
|
2321
|
-
if (fieldValue === '') continue;
|
|
2322
|
-
fields.push({ key: fieldKey, value: fieldValue });
|
|
2323
|
-
}
|
|
2324
|
-
} else {
|
|
2325
|
-
const fieldValue = this.formatProviderValue('value', value);
|
|
2326
|
-
if (fieldValue !== '') {
|
|
2327
|
-
fields.push({ key: 'value', value: fieldValue });
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
entries.push({
|
|
2331
|
-
key,
|
|
2332
|
-
source,
|
|
2333
|
-
fields,
|
|
2334
|
-
isActive: activeProviders.has(key)
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
},
|
|
2338
|
-
|
|
2339
|
-
refreshOpenclawProviders(config) {
|
|
2340
|
-
const activeProviders = this.getOpenclawActiveProviders(config || {});
|
|
2341
|
-
const entries = [];
|
|
2342
|
-
const modelsProviders = config && config.models ? config.models.providers : null;
|
|
2343
|
-
const rootProviders = config && config.providers ? config.providers : null;
|
|
2344
|
-
this.collectOpenclawProviders('models.providers', modelsProviders, activeProviders, entries);
|
|
2345
|
-
this.collectOpenclawProviders('providers', rootProviders, activeProviders, entries);
|
|
2346
|
-
const existing = new Set(entries.map(item => item.key));
|
|
2347
|
-
const missing = [];
|
|
2348
|
-
for (const provider of activeProviders) {
|
|
2349
|
-
if (!existing.has(provider)) {
|
|
2350
|
-
missing.push(provider);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
this.openclawProviders = entries;
|
|
2354
|
-
this.openclawMissingProviders = missing;
|
|
2355
|
-
},
|
|
2356
|
-
|
|
2357
|
-
refreshOpenclawAgentsList(config) {
|
|
2358
|
-
const list = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
2359
|
-
? config.agents.list
|
|
2360
|
-
: null;
|
|
2361
|
-
if (!Array.isArray(list)) {
|
|
2362
|
-
this.openclawAgentsList = [];
|
|
2363
|
-
return;
|
|
2364
|
-
}
|
|
2365
|
-
const entries = [];
|
|
2366
|
-
list.forEach((item, index) => {
|
|
2367
|
-
if (!item || typeof item !== 'object') return;
|
|
2368
|
-
const id = typeof item.id === 'string' && item.id.trim() ? item.id.trim() : `agent-${index + 1}`;
|
|
2369
|
-
const identity = item.identity && typeof item.identity === 'object' && !Array.isArray(item.identity)
|
|
2370
|
-
? item.identity
|
|
2371
|
-
: {};
|
|
2372
|
-
const name = typeof identity.name === 'string' && identity.name.trim()
|
|
2373
|
-
? identity.name.trim()
|
|
2374
|
-
: id;
|
|
2375
|
-
entries.push({
|
|
2376
|
-
key: `${id}-${index}`,
|
|
2377
|
-
id,
|
|
2378
|
-
name,
|
|
2379
|
-
theme: typeof identity.theme === 'string' ? identity.theme : '',
|
|
2380
|
-
emoji: typeof identity.emoji === 'string' ? identity.emoji : '',
|
|
2381
|
-
avatar: typeof identity.avatar === 'string' ? identity.avatar : ''
|
|
2382
|
-
});
|
|
2383
|
-
});
|
|
2384
|
-
this.openclawAgentsList = entries;
|
|
2385
|
-
},
|
|
2386
|
-
|
|
2387
|
-
normalizeStringList(list) {
|
|
2388
|
-
if (!Array.isArray(list)) return [];
|
|
2389
|
-
const result = [];
|
|
2390
|
-
const seen = new Set();
|
|
2391
|
-
for (const item of list) {
|
|
2392
|
-
const value = typeof item === 'string' ? item.trim() : String(item || '').trim();
|
|
2393
|
-
if (!value) continue;
|
|
2394
|
-
const key = value;
|
|
2395
|
-
if (seen.has(key)) continue;
|
|
2396
|
-
seen.add(key);
|
|
2397
|
-
result.push(value);
|
|
2398
|
-
}
|
|
2399
|
-
return result;
|
|
2400
|
-
},
|
|
2401
|
-
|
|
2402
|
-
normalizeEnvItems(items) {
|
|
2403
|
-
if (!Array.isArray(items)) {
|
|
2404
|
-
return { ok: true, items: {} };
|
|
2405
|
-
}
|
|
2406
|
-
const output = {};
|
|
2407
|
-
const seen = new Set();
|
|
2408
|
-
for (const item of items) {
|
|
2409
|
-
const key = item && typeof item.key === 'string' ? item.key.trim() : '';
|
|
2410
|
-
if (!key) continue;
|
|
2411
|
-
if (seen.has(key)) {
|
|
2412
|
-
return { ok: false, error: `环境变量重复: ${key}` };
|
|
2413
|
-
}
|
|
2414
|
-
seen.add(key);
|
|
2415
|
-
const value = item && typeof item.value !== 'undefined' ? String(item.value) : '';
|
|
2416
|
-
output[key] = value;
|
|
2417
|
-
}
|
|
2418
|
-
return { ok: true, items: output };
|
|
2419
|
-
},
|
|
2420
|
-
|
|
2421
|
-
parseOptionalNumber(value, label) {
|
|
2422
|
-
const text = typeof value === 'string' ? value.trim() : String(value || '').trim();
|
|
2423
|
-
if (!text) {
|
|
2424
|
-
return { ok: true, value: null };
|
|
2425
|
-
}
|
|
2426
|
-
const num = Number(text);
|
|
2427
|
-
if (!Number.isFinite(num) || num < 0) {
|
|
2428
|
-
return { ok: false, error: `${label} 请输入有效数字` };
|
|
2429
|
-
}
|
|
2430
|
-
return { ok: true, value: num };
|
|
2431
|
-
},
|
|
2432
|
-
|
|
2433
|
-
applyOpenclawStructuredToText() {
|
|
2434
|
-
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
2435
|
-
if (!parsed.ok) {
|
|
2436
|
-
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
2437
|
-
return;
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
const config = parsed.data;
|
|
2441
|
-
const agents = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
2442
|
-
? config.agents
|
|
2443
|
-
: {};
|
|
2444
|
-
const defaults = agents.defaults && typeof agents.defaults === 'object' && !Array.isArray(agents.defaults)
|
|
2445
|
-
? agents.defaults
|
|
2446
|
-
: {};
|
|
2447
|
-
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
2448
|
-
? defaults.model
|
|
2449
|
-
: {};
|
|
2450
|
-
|
|
2451
|
-
const primary = (this.openclawStructured.agentPrimary || '').trim();
|
|
2452
|
-
const fallbacks = this.normalizeStringList(this.openclawStructured.agentFallbacks);
|
|
2453
|
-
if (primary) {
|
|
2454
|
-
model.primary = primary;
|
|
2455
|
-
}
|
|
2456
|
-
if (fallbacks.length) {
|
|
2457
|
-
model.fallbacks = fallbacks;
|
|
2458
|
-
}
|
|
2459
|
-
if (primary || fallbacks.length) {
|
|
2460
|
-
defaults.model = model;
|
|
2461
|
-
}
|
|
2462
|
-
if (primary && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
2463
|
-
config.agent.model = primary;
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
const workspace = (this.openclawStructured.workspace || '').trim();
|
|
2467
|
-
if (workspace) {
|
|
2468
|
-
defaults.workspace = workspace;
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
const timeout = this.parseOptionalNumber(this.openclawStructured.timeout, 'Timeout');
|
|
2472
|
-
if (!timeout.ok) {
|
|
2473
|
-
this.showMessage(timeout.error, 'error');
|
|
2474
|
-
return;
|
|
2475
|
-
}
|
|
2476
|
-
if (timeout.value !== null) {
|
|
2477
|
-
defaults.timeout = timeout.value;
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
const contextTokens = this.parseOptionalNumber(this.openclawStructured.contextTokens, 'Context Tokens');
|
|
2481
|
-
if (!contextTokens.ok) {
|
|
2482
|
-
this.showMessage(contextTokens.error, 'error');
|
|
2483
|
-
return;
|
|
2484
|
-
}
|
|
2485
|
-
if (contextTokens.value !== null) {
|
|
2486
|
-
defaults.contextTokens = contextTokens.value;
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
const maxConcurrent = this.parseOptionalNumber(this.openclawStructured.maxConcurrent, 'Max Concurrent');
|
|
2490
|
-
if (!maxConcurrent.ok) {
|
|
2491
|
-
this.showMessage(maxConcurrent.error, 'error');
|
|
2492
|
-
return;
|
|
2493
|
-
}
|
|
2494
|
-
if (maxConcurrent.value !== null) {
|
|
2495
|
-
defaults.maxConcurrent = maxConcurrent.value;
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
if (Object.keys(defaults).length > 0) {
|
|
2499
|
-
config.agents = agents;
|
|
2500
|
-
config.agents.defaults = defaults;
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
const envResult = this.normalizeEnvItems(this.openclawStructured.envItems);
|
|
2504
|
-
if (!envResult.ok) {
|
|
2505
|
-
this.showMessage(envResult.error, 'error');
|
|
2506
|
-
return;
|
|
2507
|
-
}
|
|
2508
|
-
if (Object.keys(envResult.items).length > 0) {
|
|
2509
|
-
config.env = envResult.items;
|
|
2510
|
-
} else if (config.env) {
|
|
2511
|
-
delete config.env;
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
|
-
const profile = (this.openclawStructured.toolsProfile || '').trim();
|
|
2515
|
-
const allowList = this.normalizeStringList(this.openclawStructured.toolsAllow);
|
|
2516
|
-
const denyList = this.normalizeStringList(this.openclawStructured.toolsDeny);
|
|
2517
|
-
const hasTools = profile || allowList.length || denyList.length || (config.tools && typeof config.tools === 'object');
|
|
2518
|
-
if (hasTools) {
|
|
2519
|
-
const tools = config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
2520
|
-
? config.tools
|
|
2521
|
-
: {};
|
|
2522
|
-
tools.profile = profile || tools.profile || 'default';
|
|
2523
|
-
tools.allow = allowList;
|
|
2524
|
-
tools.deny = denyList;
|
|
2525
|
-
config.tools = tools;
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
2529
|
-
this.refreshOpenclawProviders(config);
|
|
2530
|
-
this.refreshOpenclawAgentsList(config);
|
|
2531
|
-
this.fillOpenclawQuickFromConfig(config);
|
|
2532
|
-
this.showMessage('已写入', 'success');
|
|
2533
|
-
},
|
|
2534
|
-
|
|
2535
|
-
applyOpenclawQuickToText() {
|
|
2536
|
-
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
2537
|
-
if (!parsed.ok) {
|
|
2538
|
-
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
2539
|
-
return;
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
const providerName = (this.openclawQuick.providerName || '').trim();
|
|
2543
|
-
const modelId = (this.openclawQuick.modelId || '').trim();
|
|
2544
|
-
if (!providerName) {
|
|
2545
|
-
this.showMessage('请填写名称', 'error');
|
|
2546
|
-
return;
|
|
2547
|
-
}
|
|
2548
|
-
if (providerName.includes('/')) {
|
|
2549
|
-
this.showMessage('Provider 名称不能包含 "/"', 'error');
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2552
|
-
if (!modelId) {
|
|
2553
|
-
this.showMessage('请填写模型', 'error');
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
const config = parsed.data;
|
|
2558
|
-
const ensureObject = (value) => (value && typeof value === 'object' && !Array.isArray(value)) ? value : {};
|
|
2559
|
-
const models = ensureObject(config.models);
|
|
2560
|
-
const providers = ensureObject(models.providers);
|
|
2561
|
-
const provider = ensureObject(providers[providerName]);
|
|
2562
|
-
const baseUrl = (this.openclawQuick.baseUrl || '').trim();
|
|
2563
|
-
if (!baseUrl && !provider.baseUrl) {
|
|
2564
|
-
this.showMessage('请填写 URL', 'error');
|
|
2565
|
-
return;
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
const contextWindow = this.parseOptionalNumber(this.openclawQuick.contextWindow, '上下文长度');
|
|
2569
|
-
if (!contextWindow.ok) {
|
|
2570
|
-
this.showMessage(contextWindow.error, 'error');
|
|
2571
|
-
return;
|
|
2572
|
-
}
|
|
2573
|
-
const maxTokens = this.parseOptionalNumber(this.openclawQuick.maxTokens, '最大输出');
|
|
2574
|
-
if (!maxTokens.ok) {
|
|
2575
|
-
this.showMessage(maxTokens.error, 'error');
|
|
2576
|
-
return;
|
|
2577
|
-
}
|
|
2578
|
-
|
|
2579
|
-
const shouldOverrideProvider = !!this.openclawQuick.overrideProvider;
|
|
2580
|
-
const apiKey = (this.openclawQuick.apiKey || '').trim();
|
|
2581
|
-
const apiType = (this.openclawQuick.apiType || '').trim();
|
|
2582
|
-
const setProviderField = (key, value) => {
|
|
2583
|
-
if (!value) return;
|
|
2584
|
-
if (shouldOverrideProvider || provider[key] === undefined || provider[key] === null || provider[key] === '') {
|
|
2585
|
-
provider[key] = value;
|
|
2586
|
-
}
|
|
2587
|
-
};
|
|
2588
|
-
setProviderField('baseUrl', baseUrl);
|
|
2589
|
-
setProviderField('api', apiType);
|
|
2590
|
-
if (apiKey) {
|
|
2591
|
-
setProviderField('apiKey', apiKey);
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
const modelName = (this.openclawQuick.modelName || '').trim() || modelId;
|
|
2595
|
-
const modelEntry = {
|
|
2596
|
-
id: modelId,
|
|
2597
|
-
name: modelName,
|
|
2598
|
-
reasoning: false,
|
|
2599
|
-
input: ['text'],
|
|
2600
|
-
cost: {
|
|
2601
|
-
input: 0,
|
|
2602
|
-
output: 0,
|
|
2603
|
-
cacheRead: 0,
|
|
2604
|
-
cacheWrite: 0
|
|
2605
|
-
}
|
|
2606
|
-
};
|
|
2607
|
-
if (contextWindow.value !== null) {
|
|
2608
|
-
modelEntry.contextWindow = contextWindow.value;
|
|
2609
|
-
}
|
|
2610
|
-
if (maxTokens.value !== null) {
|
|
2611
|
-
modelEntry.maxTokens = maxTokens.value;
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
const existingModels = Array.isArray(provider.models) ? [...provider.models] : [];
|
|
2615
|
-
if (this.openclawQuick.overrideModels || existingModels.length === 0) {
|
|
2616
|
-
provider.models = [modelEntry];
|
|
2617
|
-
} else {
|
|
2618
|
-
const idx = existingModels.findIndex(item => item && item.id === modelId);
|
|
2619
|
-
if (idx >= 0) {
|
|
2620
|
-
existingModels[idx] = this.mergeOpenclawModelEntry(existingModels[idx], modelEntry, false);
|
|
2621
|
-
} else {
|
|
2622
|
-
existingModels.push(modelEntry);
|
|
2623
|
-
}
|
|
2624
|
-
provider.models = existingModels;
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
providers[providerName] = provider;
|
|
2628
|
-
models.providers = providers;
|
|
2629
|
-
config.models = models;
|
|
2630
|
-
|
|
2631
|
-
if (this.openclawQuick.setPrimary) {
|
|
2632
|
-
const agents = ensureObject(config.agents);
|
|
2633
|
-
const defaults = ensureObject(agents.defaults);
|
|
2634
|
-
const modelConfig = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
2635
|
-
? defaults.model
|
|
2636
|
-
: {};
|
|
2637
|
-
modelConfig.primary = `${providerName}/${modelId}`;
|
|
2638
|
-
defaults.model = modelConfig;
|
|
2639
|
-
agents.defaults = defaults;
|
|
2640
|
-
config.agents = agents;
|
|
2641
|
-
if (config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
2642
|
-
config.agent.model = modelConfig.primary;
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
2647
|
-
this.fillOpenclawStructured(config);
|
|
2648
|
-
this.refreshOpenclawProviders(config);
|
|
2649
|
-
this.refreshOpenclawAgentsList(config);
|
|
2650
|
-
this.showMessage('配置已写入', 'success');
|
|
2651
|
-
},
|
|
2652
|
-
|
|
2653
|
-
addOpenclawFallback() {
|
|
2654
|
-
this.openclawStructured.agentFallbacks.push('');
|
|
2655
|
-
},
|
|
2656
|
-
|
|
2657
|
-
removeOpenclawFallback(index) {
|
|
2658
|
-
this.openclawStructured.agentFallbacks.splice(index, 1);
|
|
2659
|
-
if (this.openclawStructured.agentFallbacks.length === 0) {
|
|
2660
|
-
this.openclawStructured.agentFallbacks.push('');
|
|
2661
|
-
}
|
|
2662
|
-
},
|
|
2663
|
-
|
|
2664
|
-
addOpenclawEnvItem() {
|
|
2665
|
-
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
2666
|
-
},
|
|
2667
|
-
|
|
2668
|
-
removeOpenclawEnvItem(index) {
|
|
2669
|
-
this.openclawStructured.envItems.splice(index, 1);
|
|
2670
|
-
if (this.openclawStructured.envItems.length === 0) {
|
|
2671
|
-
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
2672
|
-
}
|
|
2673
|
-
},
|
|
2674
|
-
|
|
2675
|
-
toggleOpenclawEnvItem(index) {
|
|
2676
|
-
const item = this.openclawStructured.envItems[index];
|
|
2677
|
-
if (item) {
|
|
2678
|
-
item.show = !item.show;
|
|
2679
|
-
}
|
|
2680
|
-
},
|
|
2681
|
-
|
|
2682
|
-
addOpenclawToolsAllow() {
|
|
2683
|
-
this.openclawStructured.toolsAllow.push('');
|
|
2684
|
-
},
|
|
2685
|
-
|
|
2686
|
-
removeOpenclawToolsAllow(index) {
|
|
2687
|
-
this.openclawStructured.toolsAllow.splice(index, 1);
|
|
2688
|
-
if (this.openclawStructured.toolsAllow.length === 0) {
|
|
2689
|
-
this.openclawStructured.toolsAllow.push('');
|
|
2690
|
-
}
|
|
2691
|
-
},
|
|
2692
|
-
|
|
2693
|
-
addOpenclawToolsDeny() {
|
|
2694
|
-
this.openclawStructured.toolsDeny.push('');
|
|
2695
|
-
},
|
|
2696
|
-
|
|
2697
|
-
removeOpenclawToolsDeny(index) {
|
|
2698
|
-
this.openclawStructured.toolsDeny.splice(index, 1);
|
|
2699
|
-
if (this.openclawStructured.toolsDeny.length === 0) {
|
|
2700
|
-
this.openclawStructured.toolsDeny.push('');
|
|
2701
|
-
}
|
|
2702
|
-
},
|
|
2703
|
-
|
|
2704
|
-
openclawHasContent(config) {
|
|
2705
|
-
return !!(config && typeof config.content === 'string' && config.content.trim());
|
|
2706
|
-
},
|
|
2707
|
-
|
|
2708
|
-
openclawSubtitle(config) {
|
|
2709
|
-
if (!this.openclawHasContent(config)) {
|
|
2710
|
-
return '未设置配置';
|
|
2711
|
-
}
|
|
2712
|
-
const length = config.content.trim().length;
|
|
2713
|
-
return `已保存 ${length} 字符`;
|
|
2714
|
-
},
|
|
2715
|
-
|
|
2716
|
-
saveOpenclawConfigs() {
|
|
2717
|
-
localStorage.setItem('openclawConfigs', JSON.stringify(this.openclawConfigs));
|
|
2718
|
-
},
|
|
2719
|
-
|
|
2720
|
-
openOpenclawAddModal() {
|
|
2721
|
-
this.openclawEditorTitle = '添加 OpenClaw 配置';
|
|
2722
|
-
this.openclawEditing = {
|
|
2723
|
-
name: '',
|
|
2724
|
-
content: '',
|
|
2725
|
-
lockName: false
|
|
2726
|
-
};
|
|
2727
|
-
this.openclawConfigPath = '';
|
|
2728
|
-
this.openclawConfigExists = false;
|
|
2729
|
-
this.openclawLineEnding = '\n';
|
|
2730
|
-
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
2731
|
-
this.showOpenclawConfigModal = true;
|
|
2732
|
-
},
|
|
2733
|
-
|
|
2734
|
-
openOpenclawEditModal(name) {
|
|
2735
|
-
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
2736
|
-
this.openclawEditing = {
|
|
2737
|
-
name,
|
|
2738
|
-
content: '',
|
|
2739
|
-
lockName: true
|
|
2740
|
-
};
|
|
2741
|
-
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
2742
|
-
this.showOpenclawConfigModal = true;
|
|
2743
|
-
},
|
|
2744
|
-
|
|
2745
|
-
closeOpenclawConfigModal() {
|
|
2746
|
-
this.showOpenclawConfigModal = false;
|
|
2747
|
-
this.openclawEditing = { name: '', content: '', lockName: false };
|
|
2748
|
-
this.openclawSaving = false;
|
|
2749
|
-
this.openclawApplying = false;
|
|
2750
|
-
this.resetOpenclawStructured();
|
|
2751
|
-
this.resetOpenclawQuick();
|
|
2752
|
-
},
|
|
2753
|
-
|
|
2754
|
-
openInstallModal() {
|
|
2755
|
-
this.showInstallModal = true;
|
|
2756
|
-
},
|
|
2757
|
-
|
|
2758
|
-
closeInstallModal() {
|
|
2759
|
-
this.showInstallModal = false;
|
|
2760
|
-
},
|
|
2761
|
-
|
|
2762
|
-
async loadOpenclawConfigFromFile(options = {}) {
|
|
2763
|
-
const silent = !!options.silent;
|
|
2764
|
-
const force = !!options.force;
|
|
2765
|
-
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
2766
|
-
this.openclawFileLoading = true;
|
|
2767
|
-
try {
|
|
2768
|
-
const res = await api('get-openclaw-config');
|
|
2769
|
-
if (res.error) {
|
|
2770
|
-
if (!silent) {
|
|
2771
|
-
this.showMessage(res.error, 'error');
|
|
2772
|
-
}
|
|
2773
|
-
return;
|
|
2774
|
-
}
|
|
2775
|
-
this.openclawConfigPath = res.path || '';
|
|
2776
|
-
this.openclawConfigExists = !!res.exists;
|
|
2777
|
-
this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
2778
|
-
const hasContent = !!(res.content && res.content.trim());
|
|
2779
|
-
const shouldOverride = force || !this.openclawEditing.content || !this.openclawEditing.content.trim();
|
|
2780
|
-
if (hasContent && shouldOverride) {
|
|
2781
|
-
this.openclawEditing.content = res.content;
|
|
2782
|
-
} else if (!hasContent && shouldOverride && fallbackToTemplate) {
|
|
2783
|
-
this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
|
|
2784
|
-
}
|
|
2785
|
-
this.syncOpenclawStructuredFromText({ silent: true });
|
|
2786
|
-
if (!silent) {
|
|
2787
|
-
this.showMessage('加载完成', 'success');
|
|
2788
|
-
}
|
|
2789
|
-
} catch (e) {
|
|
2790
|
-
if (!silent) {
|
|
2791
|
-
this.showMessage('加载配置失败', 'error');
|
|
2792
|
-
}
|
|
2793
|
-
} finally {
|
|
2794
|
-
this.openclawFileLoading = false;
|
|
2795
|
-
}
|
|
2796
|
-
},
|
|
2797
|
-
|
|
2798
|
-
persistOpenclawConfig({ closeModal = true } = {}) {
|
|
2799
|
-
if (!this.openclawEditing.name || !this.openclawEditing.name.trim()) {
|
|
2800
|
-
this.showMessage('请输入名称', 'error');
|
|
2801
|
-
return '';
|
|
2802
|
-
}
|
|
2803
|
-
const name = this.openclawEditing.name.trim();
|
|
2804
|
-
if (!this.openclawEditing.lockName && this.openclawConfigs[name]) {
|
|
2805
|
-
this.showMessage('名称已存在', 'error');
|
|
2806
|
-
return '';
|
|
2807
|
-
}
|
|
2808
|
-
if (!this.openclawEditing.content || !this.openclawEditing.content.trim()) {
|
|
2809
|
-
this.showMessage('配置内容不能为空', 'error');
|
|
2810
|
-
return '';
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
this.openclawConfigs[name] = {
|
|
2814
|
-
content: this.openclawEditing.content
|
|
2815
|
-
};
|
|
2816
|
-
this.currentOpenclawConfig = name;
|
|
2817
|
-
this.saveOpenclawConfigs();
|
|
2818
|
-
if (closeModal) {
|
|
2819
|
-
this.closeOpenclawConfigModal();
|
|
2820
|
-
}
|
|
2821
|
-
return name;
|
|
2822
|
-
},
|
|
2823
|
-
|
|
2824
|
-
async saveOpenclawConfig() {
|
|
2825
|
-
this.openclawSaving = true;
|
|
2826
|
-
try {
|
|
2827
|
-
const name = this.persistOpenclawConfig();
|
|
2828
|
-
if (!name) return;
|
|
2829
|
-
this.showMessage('操作成功', 'success');
|
|
2830
|
-
} finally {
|
|
2831
|
-
this.openclawSaving = false;
|
|
2832
|
-
}
|
|
2833
|
-
},
|
|
2834
|
-
|
|
2835
|
-
async saveAndApplyOpenclawConfig() {
|
|
2836
|
-
this.openclawApplying = true;
|
|
2837
|
-
try {
|
|
2838
|
-
const name = this.persistOpenclawConfig({ closeModal: false });
|
|
2839
|
-
if (!name) return;
|
|
2840
|
-
const config = this.openclawConfigs[name];
|
|
2841
|
-
const res = await api('apply-openclaw-config', {
|
|
2842
|
-
content: config.content,
|
|
2843
|
-
lineEnding: this.openclawLineEnding
|
|
2844
|
-
});
|
|
2845
|
-
if (res.error || res.success === false) {
|
|
2846
|
-
this.showMessage(res.error || '应用配置失败', 'error');
|
|
2847
|
-
return;
|
|
2848
|
-
}
|
|
2849
|
-
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
2850
|
-
this.openclawConfigExists = true;
|
|
2851
|
-
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
2852
|
-
this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
|
|
2853
|
-
this.closeOpenclawConfigModal();
|
|
2854
|
-
} catch (e) {
|
|
2855
|
-
this.showMessage('应用配置失败', 'error');
|
|
2856
|
-
} finally {
|
|
2857
|
-
this.openclawApplying = false;
|
|
2858
|
-
}
|
|
2859
|
-
},
|
|
2860
|
-
|
|
2861
|
-
deleteOpenclawConfig(name) {
|
|
2862
|
-
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
2863
|
-
return this.showMessage('至少保留一项', 'error');
|
|
2864
|
-
}
|
|
2865
|
-
if (!confirm(`确定删除配置 "${name}"?`)) return;
|
|
2866
|
-
delete this.openclawConfigs[name];
|
|
2867
|
-
if (this.currentOpenclawConfig === name) {
|
|
2868
|
-
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
2869
|
-
}
|
|
2870
|
-
this.saveOpenclawConfigs();
|
|
2871
|
-
this.showMessage('操作成功', 'success');
|
|
2872
|
-
},
|
|
2873
|
-
|
|
2874
|
-
async applyOpenclawConfig(name) {
|
|
2875
|
-
this.currentOpenclawConfig = name;
|
|
2876
|
-
const config = this.openclawConfigs[name];
|
|
2877
|
-
if (!this.openclawHasContent(config)) {
|
|
2878
|
-
return this.showMessage('配置为空', 'error');
|
|
2879
|
-
}
|
|
2880
|
-
const res = await api('apply-openclaw-config', {
|
|
2881
|
-
content: config.content,
|
|
2882
|
-
lineEnding: this.openclawLineEnding
|
|
2883
|
-
});
|
|
2884
|
-
if (res.error || res.success === false) {
|
|
2885
|
-
this.showMessage(res.error || '应用配置失败', 'error');
|
|
2886
|
-
} else {
|
|
2887
|
-
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
2888
|
-
this.openclawConfigExists = true;
|
|
2889
|
-
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
2890
|
-
this.showMessage(`已应用 OpenClaw 配置: ${name}${targetTip}`, 'success');
|
|
2891
|
-
}
|
|
2892
|
-
},
|
|
2893
|
-
|
|
2894
|
-
formatLatency,
|
|
2895
|
-
|
|
2896
|
-
buildSpeedTestIssue(name, result) {
|
|
2897
|
-
return buildSpeedTestIssue(name, result);
|
|
2898
|
-
},
|
|
2899
|
-
|
|
2900
|
-
async runSpeedTest(name, options = {}) {
|
|
2901
|
-
if (!name || this.speedLoading[name]) return null;
|
|
2902
|
-
const silent = !!options.silent;
|
|
2903
|
-
this.speedLoading[name] = true;
|
|
2904
|
-
try {
|
|
2905
|
-
const res = await api('speed-test', { name });
|
|
2906
|
-
if (res.error) {
|
|
2907
|
-
this.speedResults[name] = { ok: false, error: res.error };
|
|
2908
|
-
if (!silent) {
|
|
2909
|
-
this.showMessage(res.error, 'error');
|
|
2910
|
-
}
|
|
2911
|
-
return { ok: false, error: res.error };
|
|
2912
|
-
}
|
|
2913
|
-
this.speedResults[name] = res;
|
|
2914
|
-
if (!silent) {
|
|
2915
|
-
const status = res.status ? ` (${res.status})` : '';
|
|
2916
|
-
this.showMessage(`Speed ${name}: ${this.formatLatency(res)}${status}`, 'success');
|
|
2917
|
-
}
|
|
2918
|
-
return res;
|
|
2919
|
-
} catch (e) {
|
|
2920
|
-
const message = e && e.message ? e.message : 'Speed test failed';
|
|
2921
|
-
this.speedResults[name] = { ok: false, error: message };
|
|
2922
|
-
if (!silent) {
|
|
2923
|
-
this.showMessage(message, 'error');
|
|
2924
|
-
}
|
|
2925
|
-
return { ok: false, error: message };
|
|
2926
|
-
} finally {
|
|
2927
|
-
this.speedLoading[name] = false;
|
|
2928
|
-
}
|
|
2929
|
-
},
|
|
2930
|
-
|
|
2931
|
-
async runClaudeSpeedTest(name, config) {
|
|
2932
|
-
if (!name || this.claudeSpeedLoading[name]) return null;
|
|
2933
|
-
const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
2934
|
-
this.claudeSpeedLoading[name] = true;
|
|
2935
|
-
try {
|
|
2936
|
-
if (!baseUrl) {
|
|
2937
|
-
const res = { ok: false, error: 'Missing base URL' };
|
|
2938
|
-
this.claudeSpeedResults[name] = res;
|
|
2939
|
-
return res;
|
|
2940
|
-
}
|
|
2941
|
-
const res = await api('speed-test', { url: baseUrl });
|
|
2942
|
-
if (res.error) {
|
|
2943
|
-
this.claudeSpeedResults[name] = { ok: false, error: res.error };
|
|
2944
|
-
return { ok: false, error: res.error };
|
|
2945
|
-
}
|
|
2946
|
-
this.claudeSpeedResults[name] = res;
|
|
2947
|
-
return res;
|
|
2948
|
-
} catch (e) {
|
|
2949
|
-
const message = e && e.message ? e.message : 'Speed test failed';
|
|
2950
|
-
const res = { ok: false, error: message };
|
|
2951
|
-
this.claudeSpeedResults[name] = res;
|
|
2952
|
-
return res;
|
|
2953
|
-
} finally {
|
|
2954
|
-
this.claudeSpeedLoading[name] = false;
|
|
2955
|
-
}
|
|
2956
|
-
},
|
|
2957
|
-
|
|
2958
|
-
showMessage(text, type) {
|
|
2959
|
-
this.message = text;
|
|
2960
|
-
this.messageType = type || 'info';
|
|
2961
|
-
setTimeout(() => {
|
|
2962
|
-
this.message = '';
|
|
2963
|
-
}, 3000);
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
});
|
|
2967
|
-
|
|
2968
|
-
app.mount('#app');
|
|
2969
|
-
});
|
|
2970
|
-
|