codexmate 0.0.22 → 0.0.24
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 +5 -3
- package/README.zh.md +8 -5
- package/cli/auth-profiles.js +23 -7
- package/cli/doctor-core.js +903 -0
- package/cli/import-skills-url.js +334 -0
- package/cli.js +304 -208
- package/lib/cli-models-utils.js +0 -40
- package/lib/cli-network-utils.js +28 -2
- package/package.json +5 -2
- package/plugins/README.md +20 -0
- package/plugins/README.zh-CN.md +20 -0
- package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
- package/plugins/prompt-templates/computed.mjs +253 -0
- package/plugins/prompt-templates/index.mjs +8 -0
- package/plugins/prompt-templates/manifest.mjs +15 -0
- package/plugins/prompt-templates/methods.mjs +619 -0
- package/plugins/prompt-templates/overview.mjs +90 -0
- package/plugins/prompt-templates/ownership.mjs +19 -0
- package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
- package/plugins/prompt-templates/storage.mjs +64 -0
- package/plugins/registry.mjs +16 -0
- package/res/logo-pack.webp +0 -0
- package/web-ui/app.js +68 -34
- package/web-ui/index.html +4 -3
- package/web-ui/modules/app.computed.dashboard.mjs +22 -22
- package/web-ui/modules/app.computed.main-tabs.mjs +9 -2
- package/web-ui/modules/app.methods.agents.mjs +91 -3
- package/web-ui/modules/app.methods.codex-config.mjs +153 -164
- package/web-ui/modules/app.methods.install.mjs +16 -0
- package/web-ui/modules/app.methods.navigation.mjs +76 -0
- package/web-ui/modules/app.methods.runtime.mjs +24 -2
- package/web-ui/modules/app.methods.session-browser.mjs +73 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +12 -0
- package/web-ui/modules/app.methods.task-orchestration.mjs +96 -11
- package/web-ui/modules/config-mode.computed.mjs +1 -3
- package/web-ui/modules/i18n.dict.mjs +2039 -0
- package/web-ui/modules/i18n.mjs +2 -1555
- package/web-ui/modules/plugins.computed.mjs +2 -219
- package/web-ui/modules/plugins.methods.mjs +2 -619
- package/web-ui/modules/plugins.storage.mjs +11 -37
- package/web-ui/modules/sessions-filters-url.mjs +85 -0
- package/web-ui/partials/index/layout-header.html +38 -34
- package/web-ui/partials/index/modal-config-template-agents.html +3 -4
- package/web-ui/partials/index/modal-health-check.html +33 -60
- package/web-ui/partials/index/panel-config-claude.html +56 -15
- package/web-ui/partials/index/panel-config-codex.html +68 -19
- package/web-ui/partials/index/panel-config-openclaw.html +8 -3
- package/web-ui/partials/index/panel-dashboard.html +186 -0
- package/web-ui/partials/index/panel-docs.html +1 -1
- package/web-ui/partials/index/panel-market.html +3 -0
- package/web-ui/partials/index/panel-orchestration.html +105 -111
- package/web-ui/partials/index/panel-plugins.html +48 -12
- package/web-ui/partials/index/panel-sessions.html +12 -3
- package/web-ui/partials/index/panel-settings.html +1 -1
- package/web-ui/partials/index/panel-usage.html +7 -6
- package/web-ui/styles/controls-forms.css +16 -2
- package/web-ui/styles/dashboard.css +274 -0
- package/web-ui/styles/layout-shell.css +11 -5
- package/web-ui/styles/navigation-panels.css +8 -0
- package/web-ui/styles/plugins-panel.css +5 -0
- package/web-ui/styles/sessions-list.css +3 -3
- package/web-ui/styles/sessions-usage.css +37 -0
- package/web-ui/styles/skills-market.css +12 -2
- package/web-ui/styles/task-orchestration.css +57 -11
- package/web-ui/styles.css +1 -0
- package/res/logo.png +0 -0
|
@@ -193,8 +193,8 @@ export function createAgentsMethods(options = {}) {
|
|
|
193
193
|
const fileName = (options.fileName || this.openclawWorkspaceFileName || 'AGENTS.md').trim();
|
|
194
194
|
this.agentsContext = 'openclaw-workspace';
|
|
195
195
|
this.agentsWorkspaceFileName = fileName;
|
|
196
|
-
this.agentsModalTitle = `OpenClaw 工作区文件: ${fileName}
|
|
197
|
-
this.agentsModalHint = `保存后会写入 OpenClaw Workspace 下的 ${fileName}
|
|
196
|
+
this.agentsModalTitle = tr('modal.agents.title.openclawWorkspaceFile', `OpenClaw 工作区文件: ${fileName}`, { fileName });
|
|
197
|
+
this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`, { fileName });
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
@@ -224,7 +224,47 @@ export function createAgentsMethods(options = {}) {
|
|
|
224
224
|
this._agentsDiffPreviewRequestToken = null;
|
|
225
225
|
},
|
|
226
226
|
handleGlobalKeydown(event) {
|
|
227
|
-
if (!event
|
|
227
|
+
if (!event) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const isCmdLike = !!(event.metaKey || event.ctrlKey);
|
|
231
|
+
const key = typeof event.key === 'string' ? event.key : '';
|
|
232
|
+
const isSearchHotkey = isCmdLike && !event.altKey && (key === 'k' || key === 'K');
|
|
233
|
+
if (isSearchHotkey) {
|
|
234
|
+
const target = event.target;
|
|
235
|
+
const tag = target && target.tagName ? String(target.tagName).toUpperCase() : '';
|
|
236
|
+
const isTypingTarget = !!(
|
|
237
|
+
tag === 'INPUT'
|
|
238
|
+
|| tag === 'TEXTAREA'
|
|
239
|
+
|| tag === 'SELECT'
|
|
240
|
+
|| (target && target.isContentEditable)
|
|
241
|
+
);
|
|
242
|
+
if (!isTypingTarget) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
try {
|
|
246
|
+
const focusSelector = (() => {
|
|
247
|
+
if (this.showSkillsModal) return '.skills-filter-row input.form-input';
|
|
248
|
+
if (this.mainTab === 'sessions') return '#panel-sessions .session-query-input';
|
|
249
|
+
if (this.mainTab === 'plugins' && this.pluginsActiveId === 'prompt-templates' && this.promptTemplatesMode !== 'compose') {
|
|
250
|
+
return '#panel-plugins .prompt-templates-toolbar input.form-input';
|
|
251
|
+
}
|
|
252
|
+
return '';
|
|
253
|
+
})();
|
|
254
|
+
if (focusSelector) {
|
|
255
|
+
const el = document.querySelector(focusSelector);
|
|
256
|
+
if (el && typeof el.focus === 'function') {
|
|
257
|
+
el.focus();
|
|
258
|
+
if (typeof el.select === 'function') {
|
|
259
|
+
el.select();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (_) {}
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (key !== 'Escape') {
|
|
228
268
|
return;
|
|
229
269
|
}
|
|
230
270
|
if (this.showConfirmDialog) {
|
|
@@ -233,6 +273,54 @@ export function createAgentsMethods(options = {}) {
|
|
|
233
273
|
this.resolveConfirmDialog(false);
|
|
234
274
|
return;
|
|
235
275
|
}
|
|
276
|
+
if (this.showSkillsModal && typeof this.closeSkillsModal === 'function') {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
event.stopPropagation();
|
|
279
|
+
this.closeSkillsModal();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (this.showOpenclawConfigModal && typeof this.closeOpenclawConfigModal === 'function') {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
event.stopPropagation();
|
|
285
|
+
this.closeOpenclawConfigModal();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (this.showConfigTemplateModal && typeof this.closeConfigTemplateModal === 'function') {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
event.stopPropagation();
|
|
291
|
+
this.closeConfigTemplateModal();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (this.showEditConfigModal && typeof this.closeEditConfigModal === 'function') {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
event.stopPropagation();
|
|
297
|
+
this.closeEditConfigModal();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (this.showClaudeConfigModal && typeof this.closeClaudeConfigModal === 'function') {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
event.stopPropagation();
|
|
303
|
+
this.closeClaudeConfigModal();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (this.showModelModal && typeof this.closeModelModal === 'function') {
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
event.stopPropagation();
|
|
309
|
+
this.closeModelModal();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (this.showAddModal && typeof this.closeAddModal === 'function') {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
event.stopPropagation();
|
|
315
|
+
this.closeAddModal();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (this.showEditModal && typeof this.closeEditModal === 'function') {
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
event.stopPropagation();
|
|
321
|
+
this.closeEditModal();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
236
324
|
if (!this.showAgentsModal) {
|
|
237
325
|
return;
|
|
238
326
|
}
|
|
@@ -271,53 +271,164 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
271
271
|
});
|
|
272
272
|
},
|
|
273
273
|
|
|
274
|
-
async runHealthCheck() {
|
|
274
|
+
async runHealthCheck(options = {}) {
|
|
275
275
|
this.healthCheckLoading = true;
|
|
276
276
|
this.healthCheckResult = null;
|
|
277
|
-
|
|
277
|
+
this.healthCheckBatchTotal = 0;
|
|
278
|
+
this.healthCheckBatchDone = 0;
|
|
279
|
+
this.healthCheckBatchFailed = 0;
|
|
278
280
|
try {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
const silent = !!(options && options.silent);
|
|
282
|
+
const forceRefresh = !!(options && options.forceRefresh);
|
|
283
|
+
const useDoctor = !!(options && options.doctor);
|
|
284
|
+
if (useDoctor) {
|
|
285
|
+
const res = await api('doctor', {
|
|
286
|
+
lang: this.lang,
|
|
287
|
+
remote: true,
|
|
288
|
+
range: this.sessionsUsageTimeRange,
|
|
289
|
+
targetApp: this.skillsTargetApp,
|
|
290
|
+
includeUsage: true,
|
|
291
|
+
includeTasks: true,
|
|
292
|
+
includeSkills: true,
|
|
293
|
+
includeInstall: true,
|
|
294
|
+
forceRefresh
|
|
295
|
+
});
|
|
296
|
+
if (hasResponseError(res)) {
|
|
297
|
+
this.healthCheckResult = null;
|
|
298
|
+
if (!silent) {
|
|
299
|
+
this.showMessage(getResponseMessage(res, '检查失败'), 'error');
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (res && typeof res === 'object') {
|
|
304
|
+
this.healthCheckResult = res;
|
|
305
|
+
const report = res.report && typeof res.report === 'object' ? res.report : null;
|
|
306
|
+
const summary = report && report.summary && typeof report.summary === 'object' ? report.summary : null;
|
|
307
|
+
const total = summary && Number.isFinite(Number(summary.total))
|
|
308
|
+
? Math.max(0, Math.floor(Number(summary.total)))
|
|
309
|
+
: (Array.isArray(res.issues) ? res.issues.length : 0);
|
|
310
|
+
const errors = summary && Number.isFinite(Number(summary.error)) ? Math.max(0, Math.floor(Number(summary.error))) : 0;
|
|
311
|
+
const warns = summary && Number.isFinite(Number(summary.warn)) ? Math.max(0, Math.floor(Number(summary.warn))) : 0;
|
|
312
|
+
this.healthCheckBatchTotal = total;
|
|
313
|
+
this.healthCheckBatchDone = total;
|
|
314
|
+
this.healthCheckBatchFailed = errors + warns;
|
|
315
|
+
if (!silent && res.ok) {
|
|
316
|
+
this.showMessage('检查通过', 'success');
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.healthCheckResult = null;
|
|
321
|
+
if (!silent) {
|
|
322
|
+
this.showMessage('检查失败', 'error');
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.configMode === 'claude') {
|
|
328
|
+
const entries = Object.entries(this.claudeConfigs || {});
|
|
329
|
+
this.healthCheckBatchTotal = entries.length;
|
|
330
|
+
|
|
331
|
+
const speedTasks = entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)
|
|
332
|
+
.then((result) => {
|
|
333
|
+
if (!result || result.ok !== true) {
|
|
334
|
+
this.healthCheckBatchFailed += 1;
|
|
335
|
+
}
|
|
336
|
+
return { name, result };
|
|
337
|
+
})
|
|
338
|
+
.catch((err) => {
|
|
339
|
+
this.healthCheckBatchFailed += 1;
|
|
340
|
+
return {
|
|
341
|
+
name,
|
|
342
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
343
|
+
};
|
|
344
|
+
})
|
|
345
|
+
.finally(() => {
|
|
346
|
+
this.healthCheckBatchDone += 1;
|
|
347
|
+
})
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const pairs = await Promise.all(speedTasks);
|
|
351
|
+
const results = {};
|
|
352
|
+
const issues = [];
|
|
353
|
+
for (const pair of pairs) {
|
|
354
|
+
results[pair.name] = pair.result || null;
|
|
355
|
+
if (typeof this.buildSpeedTestIssue === 'function') {
|
|
356
|
+
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
357
|
+
if (issue) issues.push(issue);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const ok = issues.length === 0 && this.healthCheckBatchFailed === 0;
|
|
361
|
+
this.healthCheckResult = {
|
|
362
|
+
ok,
|
|
363
|
+
issues,
|
|
364
|
+
remote: {
|
|
365
|
+
type: 'speed-test',
|
|
366
|
+
speedTests: results
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
if (ok && !silent) {
|
|
370
|
+
this.showMessage('检查通过', 'success');
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const shouldRunSpeedTests = this.configMode === 'codex';
|
|
376
|
+
const speedTimeoutMs = shouldRunSpeedTests ? 3500 : 0;
|
|
377
|
+
const providers = shouldRunSpeedTests
|
|
378
|
+
? (this.providersList || [])
|
|
379
|
+
.map((provider) => typeof provider === 'string'
|
|
380
|
+
? provider.trim()
|
|
381
|
+
: String((provider && provider.name) || '').trim())
|
|
382
|
+
.filter(Boolean)
|
|
383
|
+
: [];
|
|
384
|
+
const currentProvider = String(this.currentProvider || '').trim();
|
|
385
|
+
const orderedProviders = currentProvider && providers.includes(currentProvider)
|
|
386
|
+
? [currentProvider, ...providers.filter((name) => name !== currentProvider)]
|
|
387
|
+
: providers;
|
|
388
|
+
this.healthCheckBatchTotal = orderedProviders.length;
|
|
389
|
+
|
|
390
|
+
const speedTasks = orderedProviders.map((provider) => this.runSpeedTest(provider, { silent: true, timeoutMs: speedTimeoutMs })
|
|
391
|
+
.then((result) => {
|
|
392
|
+
if (!result || result.ok !== true) {
|
|
393
|
+
this.healthCheckBatchFailed += 1;
|
|
394
|
+
}
|
|
395
|
+
return { name: provider, result };
|
|
396
|
+
})
|
|
397
|
+
.catch((err) => {
|
|
398
|
+
this.healthCheckBatchFailed += 1;
|
|
399
|
+
return {
|
|
400
|
+
name: provider,
|
|
401
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
402
|
+
};
|
|
403
|
+
})
|
|
404
|
+
.finally(() => {
|
|
405
|
+
this.healthCheckBatchDone += 1;
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const configTask = api('config-health-check', { remote: this.configMode === 'codex' });
|
|
410
|
+
const [res, pairs] = await Promise.all([
|
|
411
|
+
configTask,
|
|
412
|
+
Promise.all(speedTasks)
|
|
413
|
+
]);
|
|
282
414
|
if (hasResponseError(res)) {
|
|
283
415
|
this.healthCheckResult = null;
|
|
284
|
-
|
|
416
|
+
if (!silent) {
|
|
417
|
+
this.showMessage(getResponseMessage(res, '检查失败'), 'error');
|
|
418
|
+
}
|
|
285
419
|
} else if (res && typeof res === 'object') {
|
|
286
|
-
shouldRunClaudeSpeedTests = true;
|
|
287
420
|
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
288
421
|
let remote = res.remote || null;
|
|
289
|
-
{
|
|
290
|
-
const providers = (this.providersList || [])
|
|
291
|
-
.map((provider) => typeof provider === 'string'
|
|
292
|
-
? provider.trim()
|
|
293
|
-
: String((provider && provider.name) || '').trim())
|
|
294
|
-
.filter(Boolean);
|
|
295
|
-
const tasks = providers.map(provider =>
|
|
296
|
-
this.runSpeedTest(provider, { silent: true })
|
|
297
|
-
.then(result => ({ name: provider, result }))
|
|
298
|
-
.catch(err => ({
|
|
299
|
-
name: provider,
|
|
300
|
-
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
301
|
-
}))
|
|
302
|
-
);
|
|
303
|
-
const pairs = await Promise.all(tasks);
|
|
422
|
+
if (shouldRunSpeedTests) {
|
|
304
423
|
const results = {};
|
|
305
424
|
for (const pair of pairs) {
|
|
306
425
|
results[pair.name] = pair.result || null;
|
|
307
426
|
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
308
427
|
if (issue) issues.push(issue);
|
|
309
428
|
}
|
|
310
|
-
|
|
311
|
-
remote
|
|
312
|
-
|
|
313
|
-
speedTests: results
|
|
314
|
-
};
|
|
315
|
-
} else {
|
|
316
|
-
remote = {
|
|
317
|
-
type: 'speed-test',
|
|
318
|
-
speedTests: results
|
|
319
|
-
};
|
|
320
|
-
}
|
|
429
|
+
remote = remote && typeof remote === 'object'
|
|
430
|
+
? { ...remote, speedTests: results }
|
|
431
|
+
: { type: 'speed-test', speedTests: results };
|
|
321
432
|
}
|
|
322
433
|
|
|
323
434
|
const ok = issues.length === 0;
|
|
@@ -327,146 +438,24 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
327
438
|
issues,
|
|
328
439
|
remote
|
|
329
440
|
};
|
|
330
|
-
if (ok) {
|
|
441
|
+
if (ok && !silent) {
|
|
331
442
|
this.showMessage('检查通过', 'success');
|
|
332
443
|
}
|
|
333
444
|
} else {
|
|
334
445
|
this.healthCheckResult = null;
|
|
335
|
-
|
|
446
|
+
if (!silent) {
|
|
447
|
+
this.showMessage('检查失败', 'error');
|
|
448
|
+
}
|
|
336
449
|
}
|
|
337
450
|
} catch (e) {
|
|
338
451
|
this.healthCheckResult = null;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (shouldRunClaudeSpeedTests && this.configMode === 'claude') {
|
|
342
|
-
try {
|
|
343
|
-
const entries = Object.entries(this.claudeConfigs || {});
|
|
344
|
-
await Promise.all(entries.map(([name, config]) => this.runClaudeSpeedTest(name, config)));
|
|
345
|
-
} catch (e) {}
|
|
346
|
-
}
|
|
347
|
-
this.healthCheckLoading = false;
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
|
|
351
|
-
buildDefaultHealthCheckPrompt() {
|
|
352
|
-
return '请简短回复:连接正常。';
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
openHealthCheckDialog(options = {}) {
|
|
356
|
-
const providerName = typeof options.providerName === 'string'
|
|
357
|
-
? options.providerName.trim()
|
|
358
|
-
: '';
|
|
359
|
-
const locked = !!options.locked && !!providerName;
|
|
360
|
-
if (locked && providerName && providerName !== String(this.currentProvider || '').trim()) {
|
|
361
|
-
if (typeof this.showMessage === 'function') {
|
|
362
|
-
this.showMessage('请先切换到该提供商再进行健康聊天测试', 'info');
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const nextProvider = providerName
|
|
367
|
-
|| String(this.healthCheckDialogSelectedProvider || '').trim()
|
|
368
|
-
|| String(this.currentProvider || '').trim()
|
|
369
|
-
|| String(((this.displayProvidersList || [])[0] || {}).name || '').trim();
|
|
370
|
-
|
|
371
|
-
this.showHealthCheckDialog = true;
|
|
372
|
-
this.healthCheckDialogLockedProvider = locked ? nextProvider : '';
|
|
373
|
-
this.healthCheckDialogSelectedProvider = nextProvider;
|
|
374
|
-
this.healthCheckDialogPrompt = this.buildDefaultHealthCheckPrompt();
|
|
375
|
-
this.healthCheckDialogMessages = [];
|
|
376
|
-
this.healthCheckDialogLastResult = null;
|
|
377
|
-
},
|
|
378
|
-
|
|
379
|
-
closeHealthCheckDialog(options = {}) {
|
|
380
|
-
if (this.healthCheckDialogSending && !options.force) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
this.showHealthCheckDialog = false;
|
|
384
|
-
this.healthCheckDialogLockedProvider = '';
|
|
385
|
-
this.healthCheckDialogSelectedProvider = '';
|
|
386
|
-
this.healthCheckDialogPrompt = this.buildDefaultHealthCheckPrompt();
|
|
387
|
-
this.healthCheckDialogMessages = [];
|
|
388
|
-
this.healthCheckDialogLastResult = null;
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
async sendHealthCheckDialogMessage() {
|
|
392
|
-
if (this.healthCheckDialogSending) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const provider = String(
|
|
397
|
-
this.healthCheckDialogLockedProvider || this.healthCheckDialogSelectedProvider || ''
|
|
398
|
-
).trim();
|
|
399
|
-
const prompt = String(this.healthCheckDialogPrompt || '').trim();
|
|
400
|
-
if (!provider) {
|
|
401
|
-
this.showMessage('请先选择提供商', 'error');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (!prompt) {
|
|
405
|
-
this.showMessage('请输入消息内容', 'error');
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
this.healthCheckDialogMessages.push({
|
|
410
|
-
id: `user-${Date.now()}`,
|
|
411
|
-
role: 'user',
|
|
412
|
-
text: prompt
|
|
413
|
-
});
|
|
414
|
-
this.healthCheckDialogSending = true;
|
|
415
|
-
this.healthCheckDialogLastResult = null;
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
const res = await api('provider-chat-check', {
|
|
419
|
-
name: provider,
|
|
420
|
-
prompt
|
|
421
|
-
});
|
|
422
|
-
this.healthCheckDialogLastResult = res;
|
|
423
|
-
|
|
424
|
-
if (hasResponseError(res) || res.ok === false) {
|
|
425
|
-
const message = getResponseMessage(res, '健康聊天测试失败');
|
|
426
|
-
this.healthCheckDialogMessages.push({
|
|
427
|
-
id: `assistant-${Date.now()}`,
|
|
428
|
-
role: 'assistant',
|
|
429
|
-
text: message,
|
|
430
|
-
ok: false,
|
|
431
|
-
status: Number.isFinite(res && res.status) ? res.status : 0,
|
|
432
|
-
durationMs: Number.isFinite(res && res.durationMs) ? res.durationMs : 0,
|
|
433
|
-
model: typeof (res && res.model) === 'string' ? res.model : '',
|
|
434
|
-
rawPreview: typeof (res && res.rawPreview) === 'string' ? res.rawPreview : ''
|
|
435
|
-
});
|
|
436
|
-
this.showMessage(message, 'error');
|
|
437
|
-
return;
|
|
452
|
+
if (!(options && options.silent)) {
|
|
453
|
+
this.showMessage('检查失败', 'error');
|
|
438
454
|
}
|
|
439
|
-
|
|
440
|
-
const reply = typeof res.reply === 'string' && res.reply.trim()
|
|
441
|
-
? res.reply.trim()
|
|
442
|
-
: '已收到回复,但未解析到可展示文本。';
|
|
443
|
-
this.healthCheckDialogMessages.push({
|
|
444
|
-
id: `assistant-${Date.now()}`,
|
|
445
|
-
role: 'assistant',
|
|
446
|
-
text: reply,
|
|
447
|
-
ok: true,
|
|
448
|
-
status: Number.isFinite(res.status) ? res.status : 0,
|
|
449
|
-
durationMs: Number.isFinite(res.durationMs) ? res.durationMs : 0,
|
|
450
|
-
model: typeof res.model === 'string' ? res.model : '',
|
|
451
|
-
rawPreview: typeof res.rawPreview === 'string' ? res.rawPreview : ''
|
|
452
|
-
});
|
|
453
|
-
this.healthCheckDialogPrompt = '';
|
|
454
|
-
} catch (e) {
|
|
455
|
-
const message = e && e.message ? e.message : '健康聊天测试失败';
|
|
456
|
-
this.healthCheckDialogMessages.push({
|
|
457
|
-
id: `assistant-${Date.now()}`,
|
|
458
|
-
role: 'assistant',
|
|
459
|
-
text: message,
|
|
460
|
-
ok: false,
|
|
461
|
-
status: 0,
|
|
462
|
-
durationMs: 0,
|
|
463
|
-
model: '',
|
|
464
|
-
rawPreview: ''
|
|
465
|
-
});
|
|
466
|
-
this.healthCheckDialogLastResult = { ok: false, error: message };
|
|
467
|
-
this.showMessage(message, 'error');
|
|
468
455
|
} finally {
|
|
469
|
-
this.
|
|
456
|
+
this.healthCheckBatchTotal = this.healthCheckBatchTotal || 0;
|
|
457
|
+
this.healthCheckBatchDone = Math.min(this.healthCheckBatchDone || 0, this.healthCheckBatchTotal || 0);
|
|
458
|
+
this.healthCheckLoading = false;
|
|
470
459
|
}
|
|
471
460
|
},
|
|
472
461
|
|
|
@@ -156,6 +156,22 @@ export function createInstallMethods() {
|
|
|
156
156
|
|
|
157
157
|
setInstallRegistryPreset(presetName) {
|
|
158
158
|
this.installRegistryPreset = this.normalizeInstallRegistryPreset(presetName);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
getInstallStatusTarget(targetId) {
|
|
162
|
+
const key = typeof targetId === 'string' ? targetId.trim() : '';
|
|
163
|
+
if (!key) return null;
|
|
164
|
+
const list = Array.isArray(this.installStatusTargets) ? this.installStatusTargets : [];
|
|
165
|
+
return list.find((item) => item && item.id === key) || null;
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
isInstallTargetInstalled(targetId) {
|
|
169
|
+
const target = this.getInstallStatusTarget(targetId);
|
|
170
|
+
return !!(target && target.installed === true);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
shouldShowCliInstallPlaceholder(targetId) {
|
|
174
|
+
return Array.isArray(this.installStatusTargets) && !this.isInstallTargetInstalled(targetId);
|
|
159
175
|
}
|
|
160
176
|
};
|
|
161
177
|
}
|
|
@@ -4,6 +4,70 @@ export function createNavigationMethods(options = {}) {
|
|
|
4
4
|
switchMainTabHelper,
|
|
5
5
|
loadMoreSessionMessagesHelper
|
|
6
6
|
} = options;
|
|
7
|
+
const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
|
|
8
|
+
const MAIN_TAB_SET = new Set([
|
|
9
|
+
'dashboard',
|
|
10
|
+
'config',
|
|
11
|
+
'sessions',
|
|
12
|
+
'usage',
|
|
13
|
+
'orchestration',
|
|
14
|
+
'market',
|
|
15
|
+
'plugins',
|
|
16
|
+
'docs',
|
|
17
|
+
'settings'
|
|
18
|
+
]);
|
|
19
|
+
const loadDoctorOverview = async (vm, options = {}) => {
|
|
20
|
+
if (!vm || typeof vm !== 'object') return false;
|
|
21
|
+
if (vm.__doctorLoading) return false;
|
|
22
|
+
const forceRefresh = !!(options && options.forceRefresh);
|
|
23
|
+
vm.__doctorLoading = true;
|
|
24
|
+
let ok = true;
|
|
25
|
+
try {
|
|
26
|
+
if (typeof vm.runHealthCheck === 'function') {
|
|
27
|
+
await vm.runHealthCheck({ doctor: true, silent: true, forceRefresh });
|
|
28
|
+
}
|
|
29
|
+
vm.__doctorLoadedOnce = true;
|
|
30
|
+
return true;
|
|
31
|
+
} catch (_) {
|
|
32
|
+
ok = false;
|
|
33
|
+
vm.__doctorLoadedOnce = true;
|
|
34
|
+
return false;
|
|
35
|
+
} finally {
|
|
36
|
+
vm.__doctorLoading = false;
|
|
37
|
+
if (!ok) {
|
|
38
|
+
vm.__doctorLoadedOnce = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const readNavState = () => {
|
|
43
|
+
if (typeof localStorage === 'undefined') return null;
|
|
44
|
+
let raw = '';
|
|
45
|
+
try {
|
|
46
|
+
raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
|
|
47
|
+
} catch (_) {
|
|
48
|
+
raw = '';
|
|
49
|
+
}
|
|
50
|
+
if (!raw) return null;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
54
|
+
} catch (_) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const persistNavState = (vm) => {
|
|
59
|
+
if (!vm || vm.__navStateRestoring) return;
|
|
60
|
+
if (typeof localStorage === 'undefined') return;
|
|
61
|
+
const mainTab = typeof vm.mainTab === 'string' ? vm.mainTab.trim().toLowerCase() : '';
|
|
62
|
+
const configMode = typeof vm.configMode === 'string' ? vm.configMode.trim().toLowerCase() : '';
|
|
63
|
+
const snapshot = {
|
|
64
|
+
mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'dashboard',
|
|
65
|
+
configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex'
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
localStorage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot));
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
};
|
|
7
71
|
|
|
8
72
|
return {
|
|
9
73
|
switchConfigMode(mode) {
|
|
@@ -34,6 +98,7 @@ export function createNavigationMethods(options = {}) {
|
|
|
34
98
|
this.scheduleAfterFrame(() => {
|
|
35
99
|
this.clearMainTabSwitchIntent('config');
|
|
36
100
|
});
|
|
101
|
+
persistNavState(this);
|
|
37
102
|
return;
|
|
38
103
|
}
|
|
39
104
|
this.switchMainTab('config');
|
|
@@ -300,6 +365,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
300
365
|
if (targetTab === previousTab) {
|
|
301
366
|
switchState.ticket += 1;
|
|
302
367
|
switchState.pendingTarget = '';
|
|
368
|
+
if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
|
|
369
|
+
void loadDoctorOverview(this);
|
|
370
|
+
}
|
|
303
371
|
if (
|
|
304
372
|
targetTab === 'sessions'
|
|
305
373
|
&& typeof this.prepareSessionTabRender === 'function'
|
|
@@ -324,6 +392,10 @@ export function createNavigationMethods(options = {}) {
|
|
|
324
392
|
switchState.ticket += 1;
|
|
325
393
|
switchState.pendingTarget = '';
|
|
326
394
|
const result = switchMainTabHelper.call(this, targetTab);
|
|
395
|
+
persistNavState(this);
|
|
396
|
+
if (targetTab === 'dashboard') {
|
|
397
|
+
void loadDoctorOverview(this);
|
|
398
|
+
}
|
|
327
399
|
this.scheduleAfterFrame(() => {
|
|
328
400
|
this.clearMainTabSwitchIntent(normalizedTab);
|
|
329
401
|
});
|
|
@@ -338,6 +410,10 @@ export function createNavigationMethods(options = {}) {
|
|
|
338
410
|
const pendingTarget = liveState.pendingTarget || targetTab;
|
|
339
411
|
liveState.pendingTarget = '';
|
|
340
412
|
switchMainTabHelper.call(this, pendingTarget);
|
|
413
|
+
persistNavState(this);
|
|
414
|
+
if (pendingTarget === 'dashboard') {
|
|
415
|
+
void loadDoctorOverview(this);
|
|
416
|
+
}
|
|
341
417
|
this.clearMainTabSwitchIntent(normalizedTab);
|
|
342
418
|
});
|
|
343
419
|
},
|
|
@@ -37,7 +37,12 @@ export function createRuntimeMethods(options = {}) {
|
|
|
37
37
|
const silent = !!options.silent;
|
|
38
38
|
this.speedLoading[name] = true;
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
40
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1000, Number(options.timeoutMs)) : 0;
|
|
41
|
+
const payload = { name };
|
|
42
|
+
if (timeoutMs) {
|
|
43
|
+
payload.timeoutMs = timeoutMs;
|
|
44
|
+
}
|
|
45
|
+
const res = await api('speed-test', payload);
|
|
41
46
|
if (res.error) {
|
|
42
47
|
this.speedResults[name] = { ok: false, error: res.error };
|
|
43
48
|
if (!silent) {
|
|
@@ -66,6 +71,8 @@ export function createRuntimeMethods(options = {}) {
|
|
|
66
71
|
async runClaudeSpeedTest(name, config) {
|
|
67
72
|
if (!name || this.claudeSpeedLoading[name]) return null;
|
|
68
73
|
const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
|
|
74
|
+
const apiKey = config && typeof config.apiKey === 'string' ? config.apiKey.trim() : '';
|
|
75
|
+
const model = config && typeof config.model === 'string' ? config.model.trim() : '';
|
|
69
76
|
this.claudeSpeedLoading[name] = true;
|
|
70
77
|
try {
|
|
71
78
|
if (!baseUrl) {
|
|
@@ -73,7 +80,22 @@ export function createRuntimeMethods(options = {}) {
|
|
|
73
80
|
this.claudeSpeedResults[name] = res;
|
|
74
81
|
return res;
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
const res = { ok: false, error: 'Missing API key' };
|
|
85
|
+
this.claudeSpeedResults[name] = res;
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
if (!model) {
|
|
89
|
+
const res = { ok: false, error: 'Missing model' };
|
|
90
|
+
this.claudeSpeedResults[name] = res;
|
|
91
|
+
return res;
|
|
92
|
+
}
|
|
93
|
+
const res = await api('speed-test', {
|
|
94
|
+
kind: 'claude',
|
|
95
|
+
url: baseUrl,
|
|
96
|
+
apiKey,
|
|
97
|
+
model
|
|
98
|
+
});
|
|
77
99
|
if (res.error) {
|
|
78
100
|
this.claudeSpeedResults[name] = { ok: false, error: res.error };
|
|
79
101
|
return { ok: false, error: res.error };
|