codexmate 0.0.25 → 0.0.27
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 +11 -3
- package/README.zh.md +10 -2
- package/cli/builtin-proxy.js +315 -95
- package/cli/openai-bridge.js +99 -5
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli.js +547 -32
- package/package.json +74 -74
- package/web-ui/app.js +24 -2
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +151 -0
- package/web-ui/modules/app.computed.dashboard.mjs +44 -1
- package/web-ui/modules/app.computed.session.mjs +336 -12
- package/web-ui/modules/app.methods.claude-config.mjs +11 -1
- package/web-ui/modules/app.methods.codex-config.mjs +76 -0
- package/web-ui/modules/app.methods.navigation.mjs +51 -3
- package/web-ui/modules/app.methods.session-actions.mjs +55 -3
- package/web-ui/modules/app.methods.session-browser.mjs +270 -3
- package/web-ui/modules/app.methods.session-timeline.mjs +34 -3
- package/web-ui/modules/app.methods.session-trash.mjs +16 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +234 -125
- package/web-ui/modules/i18n.dict.mjs +76 -0
- package/web-ui/partials/index/panel-config-claude.html +12 -4
- package/web-ui/partials/index/panel-sessions.html +33 -10
- package/web-ui/partials/index/panel-settings.html +16 -0
- package/web-ui/partials/index/panel-usage.html +95 -85
- package/web-ui/session-helpers.mjs +3 -0
- package/web-ui/styles/base-theme.css +29 -25
- package/web-ui/styles/layout-shell.css +1 -1
- package/web-ui/styles/navigation-panels.css +9 -9
- package/web-ui/styles/sessions-list.css +17 -0
- package/web-ui/styles/sessions-toolbar-trash.css +62 -0
- package/web-ui/styles/sessions-usage.css +211 -83
- package/web-ui/styles/settings-panel.css +19 -0
|
@@ -16,101 +16,137 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
16
16
|
|
|
17
17
|
return {
|
|
18
18
|
async loadAll(options = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.loading = true;
|
|
19
|
+
if (this._loadAllPromise) {
|
|
20
|
+
this._loadAllPendingOptions = options;
|
|
21
|
+
return this._loadAllPromise;
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const preserveLoading = !!options.preserveLoading;
|
|
24
|
+
const run = async () => {
|
|
25
|
+
const configLoadTimeoutMs = 6000;
|
|
26
|
+
const withTimeout = async (promise, ms) => {
|
|
27
|
+
const timeoutMs = Number.isFinite(Number(ms)) ? Math.max(0, Number(ms)) : 0;
|
|
28
|
+
if (!timeoutMs) {
|
|
29
|
+
return promise;
|
|
30
|
+
}
|
|
31
|
+
let timer = null;
|
|
32
|
+
try {
|
|
33
|
+
return await Promise.race([
|
|
34
|
+
promise,
|
|
35
|
+
new Promise((_, reject) => {
|
|
36
|
+
timer = setTimeout(() => reject(new Error('timeout')), timeoutMs);
|
|
37
|
+
})
|
|
38
|
+
]);
|
|
39
|
+
} finally {
|
|
40
|
+
if (timer) {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
if (!preserveLoading) {
|
|
46
|
+
this.loading = true;
|
|
47
|
+
}
|
|
48
|
+
this.initError = '';
|
|
49
|
+
const startedAt = Date.now();
|
|
50
|
+
const timeLeftMs = () => configLoadTimeoutMs - (Date.now() - startedAt);
|
|
51
|
+
try {
|
|
52
|
+
const statusRes = await withTimeout(api('status'), timeLeftMs());
|
|
53
|
+
if (statusRes && statusRes.error) {
|
|
54
|
+
this.initError = statusRes.error;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const listRes = await withTimeout(api('list'), timeLeftMs());
|
|
31
58
|
if (listRes && listRes.error) {
|
|
32
59
|
this.initError = listRes.error;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
if (typeof installRes.packageManager === 'string' && typeof this.normalizeInstallPackageManager === 'function') {
|
|
45
|
-
this.installPackageManager = this.normalizeInstallPackageManager(installRes.packageManager);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
} catch (_) {}
|
|
49
|
-
{
|
|
50
|
-
const tier = typeof statusRes.serviceTier === 'string'
|
|
51
|
-
? statusRes.serviceTier.trim().toLowerCase()
|
|
52
|
-
: '';
|
|
53
|
-
this.serviceTier = tier === 'fast' ? 'fast' : (tier ? 'standard' : 'fast');
|
|
54
|
-
}
|
|
55
|
-
{
|
|
56
|
-
const effort = typeof statusRes.modelReasoningEffort === 'string'
|
|
57
|
-
? statusRes.modelReasoningEffort.trim().toLowerCase()
|
|
58
|
-
: '';
|
|
59
|
-
const allowedReasoningEfforts = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
60
|
-
this.modelReasoningEffort = allowedReasoningEfforts.has(effort) ? effort : 'medium';
|
|
61
|
-
}
|
|
62
|
-
{
|
|
63
|
-
const contextWindow = this.normalizePositiveIntegerInput(
|
|
64
|
-
statusRes.modelContextWindow,
|
|
65
|
-
'model_context_window',
|
|
66
|
-
defaultModelContextWindow
|
|
67
|
-
);
|
|
68
|
-
if (this.editingCodexBudgetField !== 'modelContextWindowInput') {
|
|
69
|
-
this.modelContextWindowInput = contextWindow.ok && contextWindow.text
|
|
70
|
-
? contextWindow.text
|
|
71
|
-
: String(defaultModelContextWindow);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
this.currentProvider = statusRes.provider;
|
|
63
|
+
this.currentModel = statusRes.model;
|
|
64
|
+
try {
|
|
65
|
+
const installRes = await withTimeout(api('install-status'), Math.max(0, Math.min(1200, timeLeftMs())));
|
|
66
|
+
if (installRes && !installRes.error) {
|
|
67
|
+
const targets = Array.isArray(installRes.targets) ? installRes.targets : null;
|
|
68
|
+
if (targets) {
|
|
69
|
+
this.installStatusTargets = targets;
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const autoCompactTokenLimit = this.normalizePositiveIntegerInput(
|
|
76
|
-
statusRes.modelAutoCompactTokenLimit,
|
|
77
|
-
'model_auto_compact_token_limit',
|
|
78
|
-
defaultModelAutoCompactTokenLimit
|
|
79
|
-
);
|
|
80
|
-
if (this.editingCodexBudgetField !== 'modelAutoCompactTokenLimitInput') {
|
|
81
|
-
this.modelAutoCompactTokenLimitInput = autoCompactTokenLimit.ok && autoCompactTokenLimit.text
|
|
82
|
-
? autoCompactTokenLimit.text
|
|
83
|
-
: String(defaultModelAutoCompactTokenLimit);
|
|
71
|
+
if (typeof installRes.packageManager === 'string' && typeof this.normalizeInstallPackageManager === 'function') {
|
|
72
|
+
this.installPackageManager = this.normalizeInstallPackageManager(installRes.packageManager);
|
|
84
73
|
}
|
|
85
74
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
} catch (_) {}
|
|
76
|
+
{
|
|
77
|
+
const tier = typeof statusRes.serviceTier === 'string'
|
|
78
|
+
? statusRes.serviceTier.trim().toLowerCase()
|
|
79
|
+
: '';
|
|
80
|
+
this.serviceTier = tier === 'fast' ? 'fast' : (tier ? 'standard' : 'fast');
|
|
81
|
+
}
|
|
82
|
+
{
|
|
83
|
+
const effort = typeof statusRes.modelReasoningEffort === 'string'
|
|
84
|
+
? statusRes.modelReasoningEffort.trim().toLowerCase()
|
|
85
|
+
: '';
|
|
86
|
+
const allowedReasoningEfforts = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
87
|
+
this.modelReasoningEffort = allowedReasoningEfforts.has(effort) ? effort : 'medium';
|
|
88
|
+
}
|
|
89
|
+
{
|
|
90
|
+
const contextWindow = this.normalizePositiveIntegerInput(
|
|
91
|
+
statusRes.modelContextWindow,
|
|
92
|
+
'model_context_window',
|
|
93
|
+
defaultModelContextWindow
|
|
94
|
+
);
|
|
95
|
+
if (this.editingCodexBudgetField !== 'modelContextWindowInput') {
|
|
96
|
+
this.modelContextWindowInput = contextWindow.ok && contextWindow.text
|
|
97
|
+
? contextWindow.text
|
|
98
|
+
: String(defaultModelContextWindow);
|
|
89
99
|
}
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
}
|
|
101
|
+
{
|
|
102
|
+
const autoCompactTokenLimit = this.normalizePositiveIntegerInput(
|
|
103
|
+
statusRes.modelAutoCompactTokenLimit,
|
|
104
|
+
'model_auto_compact_token_limit',
|
|
105
|
+
defaultModelAutoCompactTokenLimit
|
|
106
|
+
);
|
|
107
|
+
if (this.editingCodexBudgetField !== 'modelAutoCompactTokenLimitInput') {
|
|
108
|
+
this.modelAutoCompactTokenLimitInput = autoCompactTokenLimit.ok && autoCompactTokenLimit.text
|
|
109
|
+
? autoCompactTokenLimit.text
|
|
110
|
+
: String(defaultModelAutoCompactTokenLimit);
|
|
92
111
|
}
|
|
93
|
-
|
|
112
|
+
}
|
|
113
|
+
this.providersList = listRes.providers;
|
|
114
|
+
if (statusRes.configReady === false) {
|
|
115
|
+
this.showMessage('配置已加载', 'info');
|
|
116
|
+
}
|
|
117
|
+
if (statusRes.initNotice) {
|
|
118
|
+
this.showMessage('配置就绪', 'info');
|
|
119
|
+
}
|
|
120
|
+
this.maybeShowStarPrompt();
|
|
121
|
+
return true;
|
|
122
|
+
} catch (e) {
|
|
123
|
+
this.initError = e && e.message === 'timeout'
|
|
124
|
+
? '读取配置超时'
|
|
125
|
+
: '连接失败: ' + (e && e.message ? e.message : '');
|
|
126
|
+
return false;
|
|
127
|
+
} finally {
|
|
128
|
+
if (!preserveLoading) {
|
|
129
|
+
this.loading = false;
|
|
94
130
|
}
|
|
95
131
|
}
|
|
96
|
-
}
|
|
97
|
-
this.initError = '连接失败: ' + e.message;
|
|
98
|
-
} finally {
|
|
99
|
-
if (!preserveLoading) {
|
|
100
|
-
this.loading = false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
132
|
+
};
|
|
103
133
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
await this.loadCodexAuthProfiles();
|
|
110
|
-
} catch (_) {}
|
|
111
|
-
}
|
|
134
|
+
this._loadAllPromise = run()
|
|
135
|
+
.finally(() => {
|
|
136
|
+
this._loadAllPromise = null;
|
|
137
|
+
});
|
|
112
138
|
|
|
113
|
-
|
|
139
|
+
const result = await this._loadAllPromise;
|
|
140
|
+
if (result) {
|
|
141
|
+
Promise.resolve(this.loadModelsForProvider(this.currentProvider)).catch(() => {});
|
|
142
|
+
Promise.resolve(this.loadCodexAuthProfiles()).catch(() => {});
|
|
143
|
+
}
|
|
144
|
+
const pending = this._loadAllPendingOptions;
|
|
145
|
+
this._loadAllPendingOptions = null;
|
|
146
|
+
if (pending) {
|
|
147
|
+
return this.loadAll(pending);
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
114
150
|
},
|
|
115
151
|
|
|
116
152
|
async loadModelsForProvider(providerName, options = {}) {
|
|
@@ -240,54 +276,104 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
240
276
|
},
|
|
241
277
|
|
|
242
278
|
async refreshClaudeSelectionFromSettings(options = {}) {
|
|
279
|
+
if (this._refreshClaudeSelectionPromise) {
|
|
280
|
+
this._refreshClaudeSelectionPendingOptions = options;
|
|
281
|
+
return this._refreshClaudeSelectionPromise;
|
|
282
|
+
}
|
|
243
283
|
const silent = !!options.silent;
|
|
244
284
|
const silentModelError = !!options.silentModelError || silent;
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
285
|
+
const run = async () => {
|
|
286
|
+
const configLoadTimeoutMs = 6000;
|
|
287
|
+
const withTimeout = async (promise, ms) => {
|
|
288
|
+
const timeoutMs = Number.isFinite(Number(ms)) ? Math.max(0, Number(ms)) : 0;
|
|
289
|
+
if (!timeoutMs) {
|
|
290
|
+
return promise;
|
|
250
291
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
292
|
+
let timer = null;
|
|
293
|
+
try {
|
|
294
|
+
return await Promise.race([
|
|
295
|
+
promise,
|
|
296
|
+
new Promise((_, reject) => {
|
|
297
|
+
timer = setTimeout(() => reject(new Error('timeout')), timeoutMs);
|
|
298
|
+
})
|
|
299
|
+
]);
|
|
300
|
+
} finally {
|
|
301
|
+
if (timer) {
|
|
302
|
+
clearTimeout(timer);
|
|
303
|
+
}
|
|
257
304
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
305
|
+
};
|
|
306
|
+
try {
|
|
307
|
+
const res = await withTimeout(api('get-claude-settings'), configLoadTimeoutMs);
|
|
308
|
+
if (res && res.error) {
|
|
309
|
+
if (!silent) {
|
|
310
|
+
this.showMessage('读取配置失败', 'error');
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
|
|
315
|
+
if (matchName) {
|
|
316
|
+
if (this.currentClaudeConfig !== matchName) {
|
|
317
|
+
this.currentClaudeConfig = matchName;
|
|
318
|
+
try { localStorage.setItem('currentClaudeConfig', matchName); } catch (_) {}
|
|
319
|
+
}
|
|
320
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
321
|
+
return;
|
|
265
322
|
}
|
|
266
|
-
this.
|
|
323
|
+
const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
|
|
324
|
+
if (importedName) {
|
|
325
|
+
if (this.currentClaudeConfig !== importedName) {
|
|
326
|
+
this.currentClaudeConfig = importedName;
|
|
327
|
+
try { localStorage.setItem('currentClaudeConfig', importedName); } catch (_) {}
|
|
328
|
+
}
|
|
329
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
330
|
+
if (!silent) {
|
|
331
|
+
this.showMessage(`检测到外部 Claude 配置,已自动导入:${importedName}`, 'success');
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
{
|
|
336
|
+
const configNames = Object.keys(this.claudeConfigs || {});
|
|
337
|
+
const current = typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig.trim() : '';
|
|
338
|
+
const fallback = current && this.claudeConfigs && this.claudeConfigs[current]
|
|
339
|
+
? current
|
|
340
|
+
: (configNames[0] || '');
|
|
341
|
+
if (!fallback) {
|
|
342
|
+
this.currentClaudeConfig = '';
|
|
343
|
+
try { localStorage.setItem('currentClaudeConfig', ''); } catch (_) {}
|
|
344
|
+
this.currentClaudeModel = '';
|
|
345
|
+
this.resetClaudeModelsState();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (this.currentClaudeConfig !== fallback) {
|
|
349
|
+
this.currentClaudeConfig = fallback;
|
|
350
|
+
try { localStorage.setItem('currentClaudeConfig', fallback); } catch (_) {}
|
|
351
|
+
}
|
|
352
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
353
|
+
}
|
|
354
|
+
} catch (e) {
|
|
267
355
|
if (!silent) {
|
|
268
|
-
this.showMessage(
|
|
356
|
+
this.showMessage(e && e.message === 'timeout' ? '读取配置超时' : '读取配置失败', 'error');
|
|
269
357
|
}
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
this.currentClaudeConfig = '';
|
|
273
|
-
this.currentClaudeModel = '';
|
|
274
|
-
this.resetClaudeModelsState();
|
|
275
|
-
if (!silent) {
|
|
276
|
-
const tip = res && res.exists
|
|
277
|
-
? '当前 Claude settings.json 与本地配置不匹配,已取消选中'
|
|
278
|
-
: '未检测到 Claude settings.json,已取消选中';
|
|
279
|
-
this.showMessage(tip, 'info');
|
|
280
|
-
}
|
|
281
|
-
} catch (_) {
|
|
282
|
-
if (!silent) {
|
|
283
|
-
this.showMessage('读取配置失败', 'error');
|
|
284
358
|
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
this._refreshClaudeSelectionPromise = Promise.resolve(run())
|
|
362
|
+
.finally(() => {
|
|
363
|
+
this._refreshClaudeSelectionPromise = null;
|
|
364
|
+
});
|
|
365
|
+
await this._refreshClaudeSelectionPromise;
|
|
366
|
+
const pending = this._refreshClaudeSelectionPendingOptions;
|
|
367
|
+
this._refreshClaudeSelectionPendingOptions = null;
|
|
368
|
+
if (pending) {
|
|
369
|
+
return this.refreshClaudeSelectionFromSettings(pending);
|
|
285
370
|
}
|
|
286
371
|
},
|
|
287
372
|
|
|
288
373
|
syncClaudeModelFromConfig() {
|
|
289
374
|
const config = this.getCurrentClaudeConfig();
|
|
290
375
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
376
|
+
this.claudeCustomModelDraft = this.currentClaudeModel;
|
|
291
377
|
},
|
|
292
378
|
|
|
293
379
|
refreshClaudeModelContext(options = {}) {
|
|
@@ -308,7 +394,6 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
308
394
|
},
|
|
309
395
|
|
|
310
396
|
async loadClaudeModels(options = {}) {
|
|
311
|
-
const silentError = !!options.silentError;
|
|
312
397
|
const config = this.getCurrentClaudeConfig();
|
|
313
398
|
const requestSeq = (Number(this.claudeModelsRequestSeq) || 0) + 1;
|
|
314
399
|
this.claudeModelsRequestSeq = requestSeq;
|
|
@@ -340,7 +425,32 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
340
425
|
return;
|
|
341
426
|
}
|
|
342
427
|
|
|
343
|
-
|
|
428
|
+
const cache = typeof globalThis !== 'undefined'
|
|
429
|
+
? (globalThis.__codexmateClaudeModelsCache || (globalThis.__codexmateClaudeModelsCache = new Map()))
|
|
430
|
+
: new Map();
|
|
431
|
+
const cacheKey = baseUrl;
|
|
432
|
+
const ttlMs = 2 * 60 * 1000;
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
const cached = cache.get(cacheKey);
|
|
435
|
+
const cachedOk = cached
|
|
436
|
+
&& Number.isFinite(cached.ts)
|
|
437
|
+
&& now - cached.ts < ttlMs
|
|
438
|
+
&& Array.isArray(cached.models);
|
|
439
|
+
if (cachedOk) {
|
|
440
|
+
this.claudeModels = cached.models;
|
|
441
|
+
this.claudeModelsSource = cached.source || 'remote';
|
|
442
|
+
if (this.claudeModels.length) {
|
|
443
|
+
this.updateClaudeModelsCurrent();
|
|
444
|
+
} else {
|
|
445
|
+
this.claudeModelsHasCurrent = true;
|
|
446
|
+
}
|
|
447
|
+
this.claudeModelsLoading = false;
|
|
448
|
+
} else if (localCatalog.length) {
|
|
449
|
+
this.claudeModels = localCatalog;
|
|
450
|
+
this.claudeModelsSource = 'catalog';
|
|
451
|
+
this.updateClaudeModelsCurrent();
|
|
452
|
+
this.claudeModelsLoading = false;
|
|
453
|
+
}
|
|
344
454
|
const isLatestRequest = () => {
|
|
345
455
|
if (requestSeq !== Number(this.claudeModelsRequestSeq || 0)) {
|
|
346
456
|
return false;
|
|
@@ -357,6 +467,9 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
357
467
|
&& (latestConfig.apiKey || '').trim() === apiKey
|
|
358
468
|
&& (typeof latestConfig.externalCredentialType === 'string' ? latestConfig.externalCredentialType.trim() : '') === externalCredentialType;
|
|
359
469
|
};
|
|
470
|
+
if (cachedOk) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
360
473
|
try {
|
|
361
474
|
const res = await api('models-by-url', { baseUrl, apiKey });
|
|
362
475
|
if (!isLatestRequest()) {
|
|
@@ -366,12 +479,10 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
366
479
|
this.claudeModels = [];
|
|
367
480
|
this.claudeModelsSource = 'unlimited';
|
|
368
481
|
this.claudeModelsHasCurrent = true;
|
|
482
|
+
cache.set(cacheKey, { ts: Date.now(), models: [], source: 'unlimited' });
|
|
369
483
|
return;
|
|
370
484
|
}
|
|
371
485
|
if (res.error) {
|
|
372
|
-
if (!silentError) {
|
|
373
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
374
|
-
}
|
|
375
486
|
this.claudeModels = [];
|
|
376
487
|
this.claudeModelsSource = 'error';
|
|
377
488
|
this.claudeModelsHasCurrent = true;
|
|
@@ -381,13 +492,11 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
381
492
|
this.claudeModels = list;
|
|
382
493
|
this.claudeModelsSource = res.source || 'remote';
|
|
383
494
|
this.updateClaudeModelsCurrent();
|
|
495
|
+
cache.set(cacheKey, { ts: Date.now(), models: list, source: this.claudeModelsSource });
|
|
384
496
|
} catch (_) {
|
|
385
497
|
if (!isLatestRequest()) {
|
|
386
498
|
return;
|
|
387
499
|
}
|
|
388
|
-
if (!silentError) {
|
|
389
|
-
this.showMessage('获取模型列表失败', 'error');
|
|
390
|
-
}
|
|
391
500
|
this.claudeModels = [];
|
|
392
501
|
this.claudeModelsSource = 'error';
|
|
393
502
|
this.claudeModelsHasCurrent = true;
|
|
@@ -554,6 +554,9 @@ const DICT = Object.freeze({
|
|
|
554
554
|
'sessions.preview.moving': '移入中...',
|
|
555
555
|
'sessions.preview.export': '导出记录',
|
|
556
556
|
'sessions.preview.exporting': '导出中...',
|
|
557
|
+
'sessions.preview.convert': '生成派生会话',
|
|
558
|
+
'sessions.preview.converting': '生成中...',
|
|
559
|
+
'sessions.preview.convert.loadedOnly': '仅转换已加载消息',
|
|
557
560
|
'sessions.preview.openStandalone': '新页查看',
|
|
558
561
|
'sessions.preview.loadingBody': '正在加载会话内容...',
|
|
559
562
|
'sessions.preview.emptyMsgs': '当前会话暂无可展示消息',
|
|
@@ -576,8 +579,17 @@ const DICT = Object.freeze({
|
|
|
576
579
|
'sessions.time.30d': '近 30 天',
|
|
577
580
|
'sessions.time.90d': '近 90 天'
|
|
578
581
|
,
|
|
582
|
+
'sessions.sort.time': '按时间',
|
|
583
|
+
'sessions.sort.hot': '按热度',
|
|
584
|
+
'sessions.sort.hotBadge': '热'
|
|
585
|
+
,
|
|
579
586
|
'sessions.filters.copyLink': '复制筛选链接',
|
|
580
587
|
'sessions.filters.urlBuildFail': '无法生成链接',
|
|
588
|
+
'sessions.filters.source': '来源',
|
|
589
|
+
'sessions.filters.path': '路径',
|
|
590
|
+
'sessions.filters.keyword': '关键词',
|
|
591
|
+
'sessions.filters.role': '角色',
|
|
592
|
+
'sessions.filters.time': '时间',
|
|
581
593
|
'sessions.roleLabel.user': 'User',
|
|
582
594
|
'sessions.roleLabel.system': 'System',
|
|
583
595
|
'sessions.roleLabel.assistant': 'Assistant'
|
|
@@ -589,6 +601,9 @@ const DICT = Object.freeze({
|
|
|
589
601
|
'usage.range.7d': '近 7 天',
|
|
590
602
|
'usage.range.30d': '近 30 天',
|
|
591
603
|
'usage.range.all': '全部',
|
|
604
|
+
'usage.compare.toggle': '对比上周期',
|
|
605
|
+
'usage.compare.prev': '上周期',
|
|
606
|
+
'usage.compare.delta': '变化',
|
|
592
607
|
'usage.refresh': '刷新统计',
|
|
593
608
|
'usage.refreshing': '刷新中...',
|
|
594
609
|
'usage.loading': '正在加载 Usage 统计...',
|
|
@@ -601,6 +616,17 @@ const DICT = Object.freeze({
|
|
|
601
616
|
'usage.daily.title': '每天消耗',
|
|
602
617
|
'usage.daily.subtitle': '按天汇总 token 与预估费用(费用各自按最大值归一显示)。',
|
|
603
618
|
'usage.daily.note': '说明:预估费用默认不含 Claude;仅在可匹配模型单价且会话记录 input/output token 时计算。',
|
|
619
|
+
'usage.heatmap.title': '活动热力图',
|
|
620
|
+
'usage.heatmap.subtitle': '按每天会话数聚合,支持 hover 查看详细数据。',
|
|
621
|
+
'usage.heatmap.legend.less': '少',
|
|
622
|
+
'usage.heatmap.legend.more': '多',
|
|
623
|
+
'usage.heatmap.tooltip': '{date} · {sessions} 会话 · {messages} 消息 · {tokens} token',
|
|
624
|
+
'usage.heatmap.aria': '{date},{sessions} 会话',
|
|
625
|
+
'usage.hourlyHeatmap.title': '7×24 活跃热力图',
|
|
626
|
+
'usage.hourlyHeatmap.subtitle': '按星期 × 小时聚合会话分布,深色 = 高活跃。',
|
|
627
|
+
'usage.hourlyHeatmap.tooltip': '{weekday} {hour}:00 · {sessions} 会话 · {messages} 消息 · {tokens} token',
|
|
628
|
+
'usage.hourlyHeatmap.legend.less': '少',
|
|
629
|
+
'usage.hourlyHeatmap.legend.more': '多',
|
|
604
630
|
'usage.legend.tokens': 'Token',
|
|
605
631
|
'usage.legend.cost': '预估费用',
|
|
606
632
|
'usage.table.date': '日期',
|
|
@@ -648,6 +674,14 @@ const DICT = Object.freeze({
|
|
|
648
674
|
'usage.copyTokenDay': '已复制:Token({day})',
|
|
649
675
|
'usage.copyCostDay': '已复制:预估费用({day})'
|
|
650
676
|
,
|
|
677
|
+
'usage.dayDetail.title': '{day} 详情',
|
|
678
|
+
'usage.dayDetail.subtitle': '选择日期可快速查看当天构成。',
|
|
679
|
+
'usage.dayDetail.pick': '选择日期',
|
|
680
|
+
'usage.dayDetail.empty': '请选择一个日期以查看当天构成。',
|
|
681
|
+
'usage.dayDetail.clear': '清除',
|
|
682
|
+
'usage.dayDetail.topSessions': 'Top 会话',
|
|
683
|
+
'usage.dayDetail.topModels': 'Top 模型'
|
|
684
|
+
,
|
|
651
685
|
'usage.models.title': '使用模型',
|
|
652
686
|
'usage.models.subtitle': '只列真实落盘的 model 名。',
|
|
653
687
|
'usage.models.kicker': '已识别 {modeled}/{total}',
|
|
@@ -915,6 +949,10 @@ const DICT = Object.freeze({
|
|
|
915
949
|
'settings.trash.workspace': '工作区',
|
|
916
950
|
'settings.trash.originalFile': '原文件',
|
|
917
951
|
'settings.trash.loadMore': '加载更多(剩余 {count} 项)',
|
|
952
|
+
'settings.trash.retention': '自动清理',
|
|
953
|
+
'settings.trash.retentionMeta': '超过保留天数的回收站记录将自动清除',
|
|
954
|
+
'settings.trash.retentionLabel': '保留天数',
|
|
955
|
+
'settings.trash.retentionHint': '范围 1-365 天,默认 30 天。每次加载回收站时自动清理过期记录。',
|
|
918
956
|
|
|
919
957
|
'settings.templateConfirm.title': '配置模板二次确认',
|
|
920
958
|
'settings.templateConfirm.meta': '降低误写入风险',
|
|
@@ -1579,6 +1617,9 @@ const DICT = Object.freeze({
|
|
|
1579
1617
|
'sessions.preview.moving': 'Moving...',
|
|
1580
1618
|
'sessions.preview.export': 'Export',
|
|
1581
1619
|
'sessions.preview.exporting': 'Exporting...',
|
|
1620
|
+
'sessions.preview.convert': 'Create derived',
|
|
1621
|
+
'sessions.preview.converting': 'Creating...',
|
|
1622
|
+
'sessions.preview.convert.loadedOnly': 'Converted loaded messages only',
|
|
1582
1623
|
'sessions.preview.openStandalone': 'Open in new tab',
|
|
1583
1624
|
'sessions.preview.loadingBody': 'Loading session content...',
|
|
1584
1625
|
'sessions.preview.emptyMsgs': 'No messages to display',
|
|
@@ -1601,8 +1642,17 @@ const DICT = Object.freeze({
|
|
|
1601
1642
|
'sessions.time.30d': 'Last 30 days',
|
|
1602
1643
|
'sessions.time.90d': 'Last 90 days'
|
|
1603
1644
|
,
|
|
1645
|
+
'sessions.sort.time': 'Sort: time',
|
|
1646
|
+
'sessions.sort.hot': 'Sort: hot',
|
|
1647
|
+
'sessions.sort.hotBadge': 'Hot'
|
|
1648
|
+
,
|
|
1604
1649
|
'sessions.filters.copyLink': 'Copy filter link',
|
|
1605
1650
|
'sessions.filters.urlBuildFail': 'Failed to build link',
|
|
1651
|
+
'sessions.filters.source': 'Source',
|
|
1652
|
+
'sessions.filters.path': 'Path',
|
|
1653
|
+
'sessions.filters.keyword': 'Keyword',
|
|
1654
|
+
'sessions.filters.role': 'Role',
|
|
1655
|
+
'sessions.filters.time': 'Time',
|
|
1606
1656
|
'sessions.roleLabel.user': 'User',
|
|
1607
1657
|
'sessions.roleLabel.system': 'System',
|
|
1608
1658
|
'sessions.roleLabel.assistant': 'Assistant'
|
|
@@ -1614,6 +1664,9 @@ const DICT = Object.freeze({
|
|
|
1614
1664
|
'usage.range.7d': 'Last 7 days',
|
|
1615
1665
|
'usage.range.30d': 'Last 30 days',
|
|
1616
1666
|
'usage.range.all': 'All',
|
|
1667
|
+
'usage.compare.toggle': 'Compare previous',
|
|
1668
|
+
'usage.compare.prev': 'Prev',
|
|
1669
|
+
'usage.compare.delta': 'Delta',
|
|
1617
1670
|
'usage.refresh': 'Refresh stats',
|
|
1618
1671
|
'usage.refreshing': 'Refreshing...',
|
|
1619
1672
|
'usage.loading': 'Loading usage stats...',
|
|
@@ -1626,6 +1679,17 @@ const DICT = Object.freeze({
|
|
|
1626
1679
|
'usage.daily.title': 'Daily usage',
|
|
1627
1680
|
'usage.daily.subtitle': 'Daily aggregated tokens and estimated cost (normalized by max values).',
|
|
1628
1681
|
'usage.daily.note': 'Note: Estimated cost excludes Claude by default; only computed when model pricing and input/output tokens are available.',
|
|
1682
|
+
'usage.heatmap.title': 'Activity heatmap',
|
|
1683
|
+
'usage.heatmap.subtitle': 'Aggregated by sessions per day; hover to see details.',
|
|
1684
|
+
'usage.heatmap.legend.less': 'Less',
|
|
1685
|
+
'usage.heatmap.legend.more': 'More',
|
|
1686
|
+
'usage.heatmap.tooltip': '{date} · {sessions} sessions · {messages} messages · {tokens} tokens',
|
|
1687
|
+
'usage.heatmap.aria': '{date}, {sessions} sessions',
|
|
1688
|
+
'usage.hourlyHeatmap.title': '7×24 Activity Heatmap',
|
|
1689
|
+
'usage.hourlyHeatmap.subtitle': 'Session distribution by weekday × hour; darker = more active.',
|
|
1690
|
+
'usage.hourlyHeatmap.tooltip': '{weekday} {hour}:00 · {sessions} sessions · {messages} messages · {tokens} tokens',
|
|
1691
|
+
'usage.hourlyHeatmap.legend.less': 'Less',
|
|
1692
|
+
'usage.hourlyHeatmap.legend.more': 'More',
|
|
1629
1693
|
'usage.legend.tokens': 'Tokens',
|
|
1630
1694
|
'usage.legend.cost': 'Estimated cost',
|
|
1631
1695
|
'usage.table.date': 'Date',
|
|
@@ -1673,6 +1737,14 @@ const DICT = Object.freeze({
|
|
|
1673
1737
|
'usage.copyTokenDay': 'Copied: Tokens ({day})',
|
|
1674
1738
|
'usage.copyCostDay': 'Copied: Estimated cost ({day})'
|
|
1675
1739
|
,
|
|
1740
|
+
'usage.dayDetail.title': '{day} detail',
|
|
1741
|
+
'usage.dayDetail.subtitle': 'Pick a day to inspect its breakdown.',
|
|
1742
|
+
'usage.dayDetail.pick': 'Pick a day',
|
|
1743
|
+
'usage.dayDetail.empty': 'Pick a day to inspect its breakdown.',
|
|
1744
|
+
'usage.dayDetail.clear': 'Clear',
|
|
1745
|
+
'usage.dayDetail.topSessions': 'Top sessions',
|
|
1746
|
+
'usage.dayDetail.topModels': 'Top models'
|
|
1747
|
+
,
|
|
1676
1748
|
'usage.models.title': 'Models used',
|
|
1677
1749
|
'usage.models.subtitle': 'Only includes model names present in saved records.',
|
|
1678
1750
|
'usage.models.kicker': 'Identified {modeled}/{total}',
|
|
@@ -1940,6 +2012,10 @@ const DICT = Object.freeze({
|
|
|
1940
2012
|
'settings.trash.workspace': 'Workspace',
|
|
1941
2013
|
'settings.trash.originalFile': 'Original file',
|
|
1942
2014
|
'settings.trash.loadMore': 'Load more (remaining {count})',
|
|
2015
|
+
'settings.trash.retention': 'Auto-purge',
|
|
2016
|
+
'settings.trash.retentionMeta': 'Trash entries older than retention days are auto-purged',
|
|
2017
|
+
'settings.trash.retentionLabel': 'Retention days',
|
|
2018
|
+
'settings.trash.retentionHint': 'Range 1-365 days, default 30. Expired entries are purged on each trash load.',
|
|
1943
2019
|
|
|
1944
2020
|
'settings.templateConfirm.title': 'Template apply confirmation',
|
|
1945
2021
|
'settings.templateConfirm.meta': 'Reduce accidental writes',
|
|
@@ -64,6 +64,9 @@
|
|
|
64
64
|
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AiHubMix'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://aihubmix.com'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">AiHubMix</button>
|
|
65
65
|
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'DMXAPI'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.dmxapi.cn'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">DMXAPI</button>
|
|
66
66
|
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'PackyCode'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://www.packyapi.com'; newClaudeConfig.model = 'glm-4.7'; showClaudeConfigModal = true">PackyCode</button>
|
|
67
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'AnyRouter'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://anyrouter.top'; newClaudeConfig.model = 'claude-opus-4-7[1m]'; showClaudeConfigModal = true">AnyRouter</button>
|
|
68
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi MiMo'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://api.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showClaudeConfigModal = true">Xiaomi MiMo</button>
|
|
69
|
+
<button type="button" class="btn-mini" @click="newClaudeConfig.name = 'Xiaomi Token Plan'; newClaudeConfig.apiKey = ''; newClaudeConfig.baseUrl = 'https://token-plan-cn.xiaomimimo.com/anthropic'; newClaudeConfig.model = 'mimo-v2.5-pro'; showClaudeConfigModal = true">Xiaomi Token Plan</button>
|
|
67
70
|
</div>
|
|
68
71
|
</div>
|
|
69
72
|
|
|
@@ -71,14 +74,19 @@
|
|
|
71
74
|
<div class="selector-header">
|
|
72
75
|
<span class="selector-title">{{ t('claude.model') }}</span>
|
|
73
76
|
</div>
|
|
74
|
-
<
|
|
77
|
+
<input
|
|
75
78
|
v-if="claudeModelHasList"
|
|
76
|
-
class="model-
|
|
79
|
+
class="model-input"
|
|
77
80
|
v-model="currentClaudeModel"
|
|
78
81
|
@change="onClaudeModelChange"
|
|
82
|
+
@blur="onClaudeModelChange"
|
|
83
|
+
@keyup.enter="onClaudeModelChange"
|
|
84
|
+
:placeholder="t('claude.model.placeholder')"
|
|
85
|
+
list="claude-model-options"
|
|
79
86
|
>
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
<datalist v-if="claudeModelHasList" id="claude-model-options">
|
|
88
|
+
<option v-for="model in claudeModelOptions" :key="model" :value="model"></option>
|
|
89
|
+
</datalist>
|
|
82
90
|
<input
|
|
83
91
|
v-else
|
|
84
92
|
class="model-input"
|