codexmate 0.0.26 → 0.0.28
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 +7 -2
- package/README.zh.md +7 -2
- package/cli/builtin-proxy.js +636 -95
- package/cli/openai-bridge.js +497 -5
- package/cli.js +75 -29
- package/lib/cli-models-utils.js +71 -10
- package/package.json +3 -1
- package/plugins/prompt-templates/computed.mjs +1 -1
- package/plugins/prompt-templates/methods.mjs +0 -66
- package/plugins/prompt-templates/overview.mjs +1 -0
- package/web-ui/app.js +16 -16
- package/web-ui/logic.codex.mjs +56 -0
- package/web-ui/logic.sessions.mjs +56 -0
- package/web-ui/modules/app.computed.dashboard.mjs +54 -0
- package/web-ui/modules/app.computed.session.mjs +48 -0
- package/web-ui/modules/app.methods.claude-config.mjs +18 -7
- package/web-ui/modules/app.methods.codex-config.mjs +35 -3
- package/web-ui/modules/app.methods.providers.mjs +9 -1
- package/web-ui/modules/app.methods.session-actions.mjs +2 -5
- package/web-ui/modules/app.methods.session-browser.mjs +4 -5
- package/web-ui/modules/app.methods.session-trash.mjs +19 -4
- package/web-ui/modules/app.methods.startup-claude.mjs +12 -1
- package/web-ui/modules/i18n.dict.mjs +28 -32
- package/web-ui/modules/provider-url-display.mjs +17 -0
- package/web-ui/partials/index/panel-config-claude.html +5 -1
- package/web-ui/partials/index/panel-config-codex.html +33 -4
- package/web-ui/partials/index/panel-plugins.html +3 -29
- package/web-ui/partials/index/panel-sessions.html +0 -10
- package/web-ui/partials/index/panel-settings.html +62 -67
- package/web-ui/partials/index/panel-usage.html +31 -2
- package/web-ui/session-helpers.mjs +2 -2
- package/web-ui/styles/base-theme.css +47 -34
- package/web-ui/styles/controls-forms.css +27 -28
- package/web-ui/styles/layout-shell.css +37 -34
- package/web-ui/styles/modals-core.css +12 -10
- package/web-ui/styles/navigation-panels.css +36 -35
- package/web-ui/styles/responsive.css +4 -4
- package/web-ui/styles/sessions-list.css +10 -6
- package/web-ui/styles/sessions-usage.css +95 -0
- package/web-ui/styles/settings-panel.css +19 -0
- package/web-ui/styles/titles-cards.css +90 -26
package/cli.js
CHANGED
|
@@ -222,6 +222,7 @@ const DEFAULT_MODELS = ['gpt-5.3-codex', 'gpt-5.1-codex-max', 'gpt-4-turbo', 'gp
|
|
|
222
222
|
const SPEED_TEST_TIMEOUT_MS = 8000;
|
|
223
223
|
const MAX_SESSION_LIST_SIZE = 300;
|
|
224
224
|
const MAX_SESSION_TRASH_LIST_SIZE = 500;
|
|
225
|
+
const DEFAULT_SESSION_TRASH_RETENTION_DAYS = 30;
|
|
225
226
|
const MAX_EXPORT_MESSAGES = 1000;
|
|
226
227
|
const DEFAULT_SESSION_DETAIL_MESSAGES = 300;
|
|
227
228
|
const MAX_SESSION_DETAIL_MESSAGES = 1000;
|
|
@@ -5601,6 +5602,35 @@ function readSessionTrashEntries(options = {}) {
|
|
|
5601
5602
|
return normalizedEntries;
|
|
5602
5603
|
}
|
|
5603
5604
|
|
|
5605
|
+
function purgeExpiredSessionTrashEntries(retentionDays) {
|
|
5606
|
+
const days = Number.isFinite(Number(retentionDays)) && Number(retentionDays) > 0
|
|
5607
|
+
? Math.floor(Number(retentionDays))
|
|
5608
|
+
: DEFAULT_SESSION_TRASH_RETENTION_DAYS;
|
|
5609
|
+
const cutoffMs = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
5610
|
+
const entries = readSessionTrashEntries({ cleanup: false });
|
|
5611
|
+
if (entries.length === 0) {
|
|
5612
|
+
return { purged: 0 };
|
|
5613
|
+
}
|
|
5614
|
+
const remaining = [];
|
|
5615
|
+
let purgedCount = 0;
|
|
5616
|
+
for (const entry of entries) {
|
|
5617
|
+
const deletedAtMs = Date.parse(entry.deletedAt || entry.updatedAt || '') || 0;
|
|
5618
|
+
if (deletedAtMs > 0 && deletedAtMs < cutoffMs) {
|
|
5619
|
+
const trashFilePath = resolveSessionTrashFilePath(entry);
|
|
5620
|
+
if (trashFilePath) {
|
|
5621
|
+
try { fs.unlinkSync(trashFilePath); } catch (_) {}
|
|
5622
|
+
}
|
|
5623
|
+
purgedCount += 1;
|
|
5624
|
+
} else {
|
|
5625
|
+
remaining.push(entry);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
if (purgedCount > 0) {
|
|
5629
|
+
writeSessionTrashEntries(remaining);
|
|
5630
|
+
}
|
|
5631
|
+
return { purged: purgedCount };
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5604
5634
|
function buildSessionTrashEntry(summary, options = {}) {
|
|
5605
5635
|
const source = options.source === 'claude' ? 'claude' : 'codex';
|
|
5606
5636
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
@@ -5812,6 +5842,9 @@ async function listSessionTrashItems(params = {}) {
|
|
|
5812
5842
|
const limit = Number.isFinite(rawLimit)
|
|
5813
5843
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_TRASH_LIST_SIZE))
|
|
5814
5844
|
: 200;
|
|
5845
|
+
if (params.autoPurge !== false) {
|
|
5846
|
+
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
5847
|
+
}
|
|
5815
5848
|
const allEntries = readSessionTrashEntries();
|
|
5816
5849
|
let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
5817
5850
|
? allEntries.filter((entry) => entry.source === source)
|
|
@@ -9984,6 +10017,7 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
9984
10017
|
result = {
|
|
9985
10018
|
provider: config.model_provider || '未设置',
|
|
9986
10019
|
model: config.model || '未设置',
|
|
10020
|
+
currentModels: readCurrentModels(),
|
|
9987
10021
|
serviceTier,
|
|
9988
10022
|
modelReasoningEffort,
|
|
9989
10023
|
modelContextWindow,
|
|
@@ -12327,35 +12361,47 @@ function buildMcpProviderListPayload() {
|
|
|
12327
12361
|
configReady: !listConfigResult.isVirtual,
|
|
12328
12362
|
configErrorType: listConfigResult.errorType || '',
|
|
12329
12363
|
configNotice: listConfigResult.reason || '',
|
|
12330
|
-
providers: Object.entries(providers).map(([name, p]) =>
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12364
|
+
providers: Object.entries(providers).map(([name, p]) => {
|
|
12365
|
+
const bridge = typeof p.codexmate_bridge === 'string' ? p.codexmate_bridge.trim() : '';
|
|
12366
|
+
let upstreamUrl = '';
|
|
12367
|
+
if (bridge === 'openai') {
|
|
12368
|
+
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, name);
|
|
12369
|
+
if (upstream && !upstream.error && typeof upstream.baseUrl === 'string') {
|
|
12370
|
+
upstreamUrl = upstream.baseUrl.trim();
|
|
12371
|
+
}
|
|
12372
|
+
}
|
|
12373
|
+
return {
|
|
12374
|
+
name,
|
|
12375
|
+
url: p.base_url || '',
|
|
12376
|
+
upstreamUrl,
|
|
12377
|
+
codexmate_bridge: bridge,
|
|
12378
|
+
key: maskKey(p.preferred_auth_method || ''),
|
|
12379
|
+
hasKey: !!(p.preferred_auth_method && p.preferred_auth_method.trim()),
|
|
12380
|
+
models: Array.isArray(p.models)
|
|
12381
|
+
? p.models
|
|
12382
|
+
.filter((model) => model && typeof model === 'object' && !Array.isArray(model))
|
|
12383
|
+
.map((model) => ({
|
|
12384
|
+
id: typeof model.id === 'string' ? model.id : '',
|
|
12385
|
+
name: typeof model.name === 'string' ? model.name : '',
|
|
12386
|
+
cost: model.cost && typeof model.cost === 'object' && !Array.isArray(model.cost)
|
|
12387
|
+
? {
|
|
12388
|
+
input: model.cost.input,
|
|
12389
|
+
output: model.cost.output,
|
|
12390
|
+
cacheRead: model.cost.cacheRead,
|
|
12391
|
+
cacheWrite: model.cost.cacheWrite
|
|
12392
|
+
}
|
|
12393
|
+
: null,
|
|
12394
|
+
contextWindow: model.contextWindow,
|
|
12395
|
+
maxTokens: model.maxTokens
|
|
12396
|
+
}))
|
|
12397
|
+
.filter((model) => model.id)
|
|
12398
|
+
: [],
|
|
12399
|
+
current: name === current,
|
|
12400
|
+
readOnly: isBuiltinManagedProvider(name),
|
|
12401
|
+
nonDeletable: isNonDeletableProvider(name),
|
|
12402
|
+
nonEditable: isNonEditableProvider(name)
|
|
12403
|
+
};
|
|
12404
|
+
})
|
|
12359
12405
|
};
|
|
12360
12406
|
}
|
|
12361
12407
|
|
package/lib/cli-models-utils.js
CHANGED
|
@@ -48,37 +48,86 @@ const ANTHROPIC_CLAUDE_MODELS = Object.freeze([
|
|
|
48
48
|
'claude-3-haiku'
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
|
+
const DEEPSEEK_CLAUDE_COMPAT_MODELS = Object.freeze([
|
|
52
|
+
'DeepSeek-V3.2',
|
|
53
|
+
'DeepSeek-V3',
|
|
54
|
+
'DeepSeek-R1',
|
|
55
|
+
'deepseek-chat'
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const QWEN_CLAUDE_COMPAT_MODELS = Object.freeze([
|
|
59
|
+
'qwen3-coder',
|
|
60
|
+
'qwen-max',
|
|
61
|
+
'qwen-plus',
|
|
62
|
+
'qwen-turbo'
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const MODELSCOPE_CLAUDE_COMPAT_MODELS = Object.freeze([
|
|
66
|
+
'ZhipuAI/GLM-5'
|
|
67
|
+
]);
|
|
68
|
+
|
|
51
69
|
function normalizeModelCatalogId(value) {
|
|
52
70
|
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
function
|
|
73
|
+
function hasPathSegment(baseUrl, segment) {
|
|
56
74
|
const normalized = normalizeBaseUrl(baseUrl);
|
|
57
75
|
if (!normalized) return false;
|
|
58
76
|
try {
|
|
59
77
|
const parsed = new URL(normalized);
|
|
60
|
-
const host = String(parsed.hostname || '').toLowerCase();
|
|
61
78
|
const pathname = String(parsed.pathname || '').toLowerCase();
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
return isBigModelHost && hasAnthropicSegment;
|
|
79
|
+
const escaped = String(segment || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&').toLowerCase();
|
|
80
|
+
return new RegExp(`(^|/)${escaped}(/|$)`).test(pathname);
|
|
65
81
|
} catch (_) {
|
|
66
82
|
return false;
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
function
|
|
86
|
+
function getBaseUrlHost(baseUrl) {
|
|
71
87
|
const normalized = normalizeBaseUrl(baseUrl);
|
|
72
|
-
if (!normalized) return
|
|
88
|
+
if (!normalized) return '';
|
|
73
89
|
try {
|
|
74
90
|
const parsed = new URL(normalized);
|
|
75
|
-
|
|
76
|
-
return host === 'api.anthropic.com' || host.endsWith('.anthropic.com');
|
|
91
|
+
return String(parsed.hostname || '').toLowerCase();
|
|
77
92
|
} catch (_) {
|
|
78
|
-
return
|
|
93
|
+
return '';
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
96
|
|
|
97
|
+
function isHostOrSubdomain(host, domain) {
|
|
98
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isBigModelClaudeCompatibleBaseUrl(baseUrl) {
|
|
102
|
+
const host = getBaseUrlHost(baseUrl);
|
|
103
|
+
return isHostOrSubdomain(host, 'bigmodel.cn') && hasPathSegment(baseUrl, 'anthropic');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isAnthropicBaseUrl(baseUrl) {
|
|
107
|
+
const host = getBaseUrlHost(baseUrl);
|
|
108
|
+
return isHostOrSubdomain(host, 'anthropic.com');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isDeepSeekClaudeCompatibleBaseUrl(baseUrl) {
|
|
112
|
+
const host = getBaseUrlHost(baseUrl);
|
|
113
|
+
return isHostOrSubdomain(host, 'deepseek.com') && hasPathSegment(baseUrl, 'anthropic');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isQwenClaudeCompatibleBaseUrl(baseUrl) {
|
|
117
|
+
const host = getBaseUrlHost(baseUrl);
|
|
118
|
+
return isHostOrSubdomain(host, 'dashscope.aliyuncs.com') && hasPathSegment(baseUrl, 'anthropic');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isZaiClaudeCompatibleBaseUrl(baseUrl) {
|
|
122
|
+
const host = getBaseUrlHost(baseUrl);
|
|
123
|
+
return isHostOrSubdomain(host, 'z.ai') && hasPathSegment(baseUrl, 'anthropic');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isModelScopeBaseUrl(baseUrl) {
|
|
127
|
+
const host = getBaseUrlHost(baseUrl);
|
|
128
|
+
return isHostOrSubdomain(host, 'modelscope.cn');
|
|
129
|
+
}
|
|
130
|
+
|
|
82
131
|
function getSupplementalModelsForBaseUrl(baseUrl) {
|
|
83
132
|
if (isBigModelClaudeCompatibleBaseUrl(baseUrl)) {
|
|
84
133
|
return [...BIGMODEL_CLAUDE_COMPAT_MODELS];
|
|
@@ -86,6 +135,18 @@ function getSupplementalModelsForBaseUrl(baseUrl) {
|
|
|
86
135
|
if (isAnthropicBaseUrl(baseUrl)) {
|
|
87
136
|
return [...ANTHROPIC_CLAUDE_MODELS];
|
|
88
137
|
}
|
|
138
|
+
if (isDeepSeekClaudeCompatibleBaseUrl(baseUrl)) {
|
|
139
|
+
return [...DEEPSEEK_CLAUDE_COMPAT_MODELS];
|
|
140
|
+
}
|
|
141
|
+
if (isQwenClaudeCompatibleBaseUrl(baseUrl)) {
|
|
142
|
+
return [...QWEN_CLAUDE_COMPAT_MODELS];
|
|
143
|
+
}
|
|
144
|
+
if (isZaiClaudeCompatibleBaseUrl(baseUrl)) {
|
|
145
|
+
return [...BIGMODEL_CLAUDE_COMPAT_MODELS];
|
|
146
|
+
}
|
|
147
|
+
if (isModelScopeBaseUrl(baseUrl)) {
|
|
148
|
+
return [...MODELSCOPE_CLAUDE_COMPAT_MODELS];
|
|
149
|
+
}
|
|
89
150
|
return [];
|
|
90
151
|
}
|
|
91
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexmate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"test:ci": "node tools/ci/run-check.js all",
|
|
41
41
|
"test:unit": "node tests/unit/run.mjs",
|
|
42
42
|
"test:e2e": "node tests/e2e/run.js",
|
|
43
|
+
"setup:git": "git remote set-url origin https://github.com/SakuraByteCore/codexmate.git && gh auth setup-git",
|
|
44
|
+
"reset:dev": "node tools/dev/reset-and-dev.js",
|
|
43
45
|
"pretest": "node tools/ci/ensure-test-deps.js"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
@@ -98,7 +98,7 @@ function renderTemplate(templateText, values = {}) {
|
|
|
98
98
|
const name = String(key || '').trim();
|
|
99
99
|
if (!name) return '';
|
|
100
100
|
const value = map[name];
|
|
101
|
-
return value == null
|
|
101
|
+
return value == null || String(value).trim() === '' ? _whole : String(value);
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -358,72 +358,6 @@ export function createPluginsMethods() {
|
|
|
358
358
|
this.promptTemplateVarValuesRaw = {};
|
|
359
359
|
},
|
|
360
360
|
|
|
361
|
-
addPromptTemplateVariable() {
|
|
362
|
-
const draft = normalizePromptTemplateDraft(this.promptTemplateDraftRaw);
|
|
363
|
-
if (!draft || !draft.id) return;
|
|
364
|
-
if (draft.isBuiltin) {
|
|
365
|
-
this.showMessage(typeof this.t === 'function' ? this.t('toast.templates.builtinNotEditable') : 'Built-in templates are not editable', 'error');
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
this.promptTemplateVarDraftName = 'var';
|
|
369
|
-
this.promptTemplateVarDraftError = '';
|
|
370
|
-
this.showPromptTemplateVarModal = true;
|
|
371
|
-
if (typeof this.$nextTick === 'function') {
|
|
372
|
-
this.$nextTick(() => {
|
|
373
|
-
const input = this.$refs && this.$refs.promptTemplateVarNameInput
|
|
374
|
-
? this.$refs.promptTemplateVarNameInput
|
|
375
|
-
: null;
|
|
376
|
-
if (input && typeof input.focus === 'function') input.focus();
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
closePromptTemplateVarModal() {
|
|
382
|
-
this.showPromptTemplateVarModal = false;
|
|
383
|
-
this.promptTemplateVarDraftError = '';
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
confirmAddPromptTemplateVariable() {
|
|
387
|
-
const draft = normalizePromptTemplateDraft(this.promptTemplateDraftRaw);
|
|
388
|
-
if (!draft || !draft.id) return;
|
|
389
|
-
if (draft.isBuiltin) {
|
|
390
|
-
this.promptTemplateVarDraftError = typeof this.t === 'function'
|
|
391
|
-
? this.t('toast.templates.builtinNotEditable')
|
|
392
|
-
: 'Built-in templates are not editable';
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const key = typeof this.promptTemplateVarDraftName === 'string'
|
|
396
|
-
? this.promptTemplateVarDraftName.trim()
|
|
397
|
-
: '';
|
|
398
|
-
if (!key) {
|
|
399
|
-
this.promptTemplateVarDraftError = typeof this.t === 'function'
|
|
400
|
-
? this.t('toast.templates.varNameRequired')
|
|
401
|
-
: 'Variable name is required';
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (!/^[a-zA-Z0-9_.-]+$/.test(key)) {
|
|
405
|
-
this.promptTemplateVarDraftError = typeof this.t === 'function'
|
|
406
|
-
? this.t('toast.templates.varNameInvalid')
|
|
407
|
-
: 'Variable name may only contain letters, numbers, underscore, dash, dot';
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
const placeholder = `{{${key}}}`;
|
|
411
|
-
const current = typeof draft.template === 'string' ? draft.template : '';
|
|
412
|
-
if (current.includes(placeholder)) {
|
|
413
|
-
this.promptTemplateVarDraftError = typeof this.t === 'function'
|
|
414
|
-
? this.t('toast.templates.varExists')
|
|
415
|
-
: 'Variable already exists';
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const nextText = current && !current.endsWith('\n')
|
|
419
|
-
? `${current}\n${placeholder}\n`
|
|
420
|
-
: `${current}${placeholder}\n`;
|
|
421
|
-
this.promptTemplateDraftRaw = { ...draft, template: nextText };
|
|
422
|
-
this.showPromptTemplateVarModal = false;
|
|
423
|
-
this.promptTemplateVarDraftError = '';
|
|
424
|
-
this.showMessage(typeof this.t === 'function' ? this.t('toast.templates.varAdded') : 'Variable added', 'success');
|
|
425
|
-
},
|
|
426
|
-
|
|
427
361
|
setPromptVariableValue(name, value) {
|
|
428
362
|
const key = typeof name === 'string' ? name.trim() : '';
|
|
429
363
|
if (!key) return;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { buildBuiltinCommentPolishTemplate } from './comment-polish/index.mjs';
|
|
8
8
|
import { buildBuiltinRuleAckTemplate } from './rule-ack/index.mjs';
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
function ensureBuiltinTemplates(rawList, builtins) {
|
|
11
12
|
const list = Array.isArray(rawList) ? rawList.filter(Boolean) : [];
|
|
12
13
|
const builtinList = Array.isArray(builtins) ? builtins.filter(Boolean) : [];
|
package/web-ui/app.js
CHANGED
|
@@ -34,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
34
34
|
configMode: 'codex',
|
|
35
35
|
currentProvider: '',
|
|
36
36
|
currentModel: '',
|
|
37
|
+
currentModels: {},
|
|
37
38
|
serviceTier: 'fast',
|
|
38
39
|
modelReasoningEffort: 'medium',
|
|
39
40
|
modelContextWindowInput: String(DEFAULT_MODEL_CONTEXT_WINDOW),
|
|
@@ -81,9 +82,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
81
82
|
promptComposerPickerKeyword: '',
|
|
82
83
|
promptComposerSelectedTemplateId: '',
|
|
83
84
|
promptComposerVarValuesRaw: {},
|
|
84
|
-
showPromptTemplateVarModal: false,
|
|
85
|
-
promptTemplateVarDraftName: '',
|
|
86
|
-
promptTemplateVarDraftError: '',
|
|
87
85
|
showConfirmDialog: false,
|
|
88
86
|
confirmDialogTitle: '',
|
|
89
87
|
confirmDialogMessage: '',
|
|
@@ -112,7 +110,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
112
110
|
_pendingCodexApplyOptions: null,
|
|
113
111
|
agentsContent: '',
|
|
114
112
|
agentsPath: '',
|
|
115
|
-
agentsPath: '',
|
|
116
113
|
agentsExists: false,
|
|
117
114
|
agentsLineEnding: '\n',
|
|
118
115
|
agentsLoading: false,
|
|
@@ -158,7 +155,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
158
155
|
ticket: 0
|
|
159
156
|
},
|
|
160
157
|
sessionsViewMode: 'browser',
|
|
161
|
-
sessionsUsageTimeRange: '7d',
|
|
158
|
+
sessionsUsageTimeRange: (function () { try { const saved = localStorage.getItem('sessionsUsageTimeRange'); if (saved === '7d' || saved === '30d' || saved === 'all') return saved; } catch (_) {} return '7d'; })(),
|
|
162
159
|
sessionsUsageList: [],
|
|
163
160
|
sessionsUsageCompareEnabled: false,
|
|
164
161
|
sessionsUsageSelectedDayKey: '',
|
|
@@ -175,7 +172,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
175
172
|
sessionRoleFilter: 'all',
|
|
176
173
|
sessionTimePreset: 'all',
|
|
177
174
|
sessionSortMode: 'time',
|
|
178
|
-
sessionResumeWithYolo: true,
|
|
179
175
|
sessionPathOptions: [],
|
|
180
176
|
sessionPathOptionsLoading: false,
|
|
181
177
|
sessionPathOptionsMap: {
|
|
@@ -259,7 +255,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
259
255
|
installRegistryPreset: 'default',
|
|
260
256
|
installRegistryCustom: '',
|
|
261
257
|
installStatusTargets: null,
|
|
262
|
-
newProvider: { name: '', url: '', key: '', useTransform: false },
|
|
258
|
+
newProvider: { name: '', url: '', key: '', useTransform: false, _suggestedModel: '' },
|
|
263
259
|
resetConfigLoading: false,
|
|
264
260
|
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false },
|
|
265
261
|
newModelName: '',
|
|
@@ -346,7 +342,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
346
342
|
codexDownloadLoading: false,
|
|
347
343
|
codexDownloadProgress: 0,
|
|
348
344
|
codexDownloadTimer: null,
|
|
349
|
-
settingsTab: '
|
|
345
|
+
settingsTab: 'general',
|
|
350
346
|
sessionTrashEnabled: true,
|
|
351
347
|
sessionTrashItems: [],
|
|
352
348
|
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
|
|
@@ -363,6 +359,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
363
359
|
sessionTrashRestoring: {},
|
|
364
360
|
sessionTrashPurging: {},
|
|
365
361
|
sessionTrashClearing: false,
|
|
362
|
+
sessionTrashRetentionDays: 30,
|
|
366
363
|
claudeImportLoading: false,
|
|
367
364
|
codexImportLoading: false,
|
|
368
365
|
codexAuthProfiles: [],
|
|
@@ -469,16 +466,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
469
466
|
if (!this.taskOrchestrationTabEnabled && this.mainTab === 'orchestration') {
|
|
470
467
|
this.mainTab = 'config';
|
|
471
468
|
}
|
|
472
|
-
const savedSessionYolo = localStorage.getItem('codexmateSessionResumeYolo');
|
|
473
|
-
if (savedSessionYolo === '0' || savedSessionYolo === 'false') {
|
|
474
|
-
this.sessionResumeWithYolo = false;
|
|
475
|
-
} else if (savedSessionYolo === '1' || savedSessionYolo === 'true') {
|
|
476
|
-
this.sessionResumeWithYolo = true;
|
|
477
|
-
}
|
|
478
469
|
this.restoreSessionFilterCache();
|
|
479
470
|
this.restoreSessionPinnedMap();
|
|
480
471
|
this.shareCommandPrefix = this.normalizeShareCommandPrefix(localStorage.getItem('codexmateShareCommandPrefix'));
|
|
481
472
|
this.sessionTrashEnabled = this.normalizeSessionTrashEnabled(localStorage.getItem('codexmateSessionTrashEnabled'));
|
|
473
|
+
this.sessionTrashRetentionDays = this.normalizeSessionTrashRetentionDays(localStorage.getItem('codexmateSessionTrashRetentionDays'));
|
|
482
474
|
this.configTemplateDiffConfirmEnabled = loadConfigTemplateDiffConfirmEnabledFromStorage(localStorage);
|
|
483
475
|
window.addEventListener('resize', this.onWindowResize);
|
|
484
476
|
window.addEventListener('keydown', this.handleGlobalKeydown);
|
|
@@ -498,14 +490,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
498
490
|
console.error('加载 Claude 配置失败:', e);
|
|
499
491
|
}
|
|
500
492
|
}
|
|
493
|
+
{
|
|
494
|
+
const savedCurrentClaudeConfig = localStorage.getItem('currentClaudeConfig');
|
|
495
|
+
if (savedCurrentClaudeConfig && this.claudeConfigs[savedCurrentClaudeConfig]) {
|
|
496
|
+
this.currentClaudeConfig = savedCurrentClaudeConfig;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
501
499
|
if (!this.currentClaudeConfig) {
|
|
502
500
|
const claudeConfigNames = Object.keys(this.claudeConfigs || {});
|
|
503
501
|
if (claudeConfigNames.length > 0) {
|
|
504
502
|
this.currentClaudeConfig = claudeConfigNames[0];
|
|
505
|
-
const initialClaudeConfig = this.claudeConfigs[this.currentClaudeConfig];
|
|
506
|
-
this.currentClaudeModel = initialClaudeConfig && initialClaudeConfig.model ? initialClaudeConfig.model : '';
|
|
507
503
|
}
|
|
508
504
|
}
|
|
505
|
+
if (this.currentClaudeConfig && !this.currentClaudeModel) {
|
|
506
|
+
const initialClaudeConfig = this.claudeConfigs[this.currentClaudeConfig];
|
|
507
|
+
this.currentClaudeModel = initialClaudeConfig && initialClaudeConfig.model ? initialClaudeConfig.model : '';
|
|
508
|
+
}
|
|
509
509
|
const normalizeOpenclawConfigs = (configs) => {
|
|
510
510
|
const source = configs && typeof configs === 'object' && !Array.isArray(configs)
|
|
511
511
|
? configs
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 仅供 web-ui 的 codex 模型选择器与新增 provider 模板按钮使用。
|
|
2
|
+
// 镜像 logic.claude.mjs 的派生方式,但 codex provider 元信息不带 wire_api,
|
|
3
|
+
// 所以 catalog 仅按 baseUrl 的 host/path 命中。
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OPENAI_CODEX_CATALOG = Object.freeze([
|
|
6
|
+
'gpt-5-codex',
|
|
7
|
+
'gpt-5',
|
|
8
|
+
'gpt-5-mini',
|
|
9
|
+
'gpt-4.1',
|
|
10
|
+
'o4-mini',
|
|
11
|
+
'o3-mini'
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const HOST_RULES = Object.freeze([
|
|
15
|
+
{ match: (u) => /api\.openai\.com/i.test(u), models: DEFAULT_OPENAI_CODEX_CATALOG },
|
|
16
|
+
{ match: (u) => /api\.deepseek\.com/i.test(u), models: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner'] },
|
|
17
|
+
{ match: (u) => /dashscope\.aliyuncs\.com/i.test(u), models: ['qwen3-coder-plus', 'qwen3-coder-flash', 'qwen-max', 'qwen-plus'] },
|
|
18
|
+
{ match: (u) => /ark\..*volces\.com/i.test(u), models: ['doubao-seed-1-6-thinking', 'doubao-seed-1-6', 'doubao-1-5-pro-32k', 'doubao-pro-32k'] },
|
|
19
|
+
{ match: (u) => /open\.bigmodel\.cn/i.test(u), models: ['glm-4.6', 'glm-4.5', 'glm-4-plus', 'glm-coding'] },
|
|
20
|
+
{ match: (u) => /api\.moonshot\.cn|api\.kimi\.com/i.test(u), models: ['moonshot-v1-32k', 'moonshot-v1-128k', 'kimi-latest'] },
|
|
21
|
+
{ match: (u) => /api\.minimax/i.test(u), models: ['MiniMax-M2', 'abab6.5s-chat', 'abab6.5-chat'] },
|
|
22
|
+
{ match: (u) => /api-inference\.modelscope\.cn/i.test(u), models: ['Qwen/Qwen3-Coder-480B-A35B-Instruct', 'ZhipuAI/GLM-4.5'] },
|
|
23
|
+
{ match: (u) => /xiaomimimo\.com/i.test(u), models: ['mimo-v2-pro', 'mimo-v2'] },
|
|
24
|
+
{ match: (u) => /ai\.muapi\.cn/i.test(u), models: ['mimo-v2-pro'] }
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function normalizeUrl(url) {
|
|
28
|
+
return typeof url === 'string' ? url.trim().toLowerCase() : '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getCodexModelCatalogForProvider(provider) {
|
|
32
|
+
if (!provider || typeof provider !== 'object') return [];
|
|
33
|
+
const url = normalizeUrl(provider.url || provider.baseUrl || '');
|
|
34
|
+
const name = typeof provider.name === 'string' ? provider.name.toLowerCase() : '';
|
|
35
|
+
if (!url) {
|
|
36
|
+
if (/openai/.test(name)) return [...DEFAULT_OPENAI_CODEX_CATALOG];
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
for (const rule of HOST_RULES) {
|
|
40
|
+
if (rule.match(url)) return [...rule.models];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 服务模板表:供面板上的预设按钮使用。
|
|
46
|
+
// model 字段为可选首选项(添加后由前端写入内存字典 currentModels[name])。
|
|
47
|
+
// useTransform=true 表示该服务需通过内建 OpenAI bridge 转发。
|
|
48
|
+
export const CODEX_PROVIDER_TEMPLATES = Object.freeze([
|
|
49
|
+
{
|
|
50
|
+
label: 'MuAPI',
|
|
51
|
+
name: 'muapi',
|
|
52
|
+
url: 'https://ai.muapi.cn/v1',
|
|
53
|
+
model: 'mimo-v2-pro',
|
|
54
|
+
useTransform: true
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
@@ -301,6 +301,62 @@ export function buildUsageHeatmap(sessions = [], options = {}) {
|
|
|
301
301
|
};
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
export function buildUsageHourlyHeatmap(sessions = [], options = {}) {
|
|
305
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
306
|
+
const range = normalizeUsageRange(options.range);
|
|
307
|
+
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
308
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
309
|
+
const todayStart = toUtcDayStartMs(now);
|
|
310
|
+
|
|
311
|
+
const normalized = [];
|
|
312
|
+
for (const session of list) {
|
|
313
|
+
if (!session || typeof session !== 'object') continue;
|
|
314
|
+
const source = normalizeSessionSource(session.source, '');
|
|
315
|
+
if (source !== 'codex' && source !== 'claude') continue;
|
|
316
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
317
|
+
if (!Number.isFinite(updatedAtMs)) continue;
|
|
318
|
+
const dayStart = toUtcDayStartMs(updatedAtMs);
|
|
319
|
+
if (range !== 'all') {
|
|
320
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
321
|
+
const rangeStart = todayStart - ((rangeDays - 1) * dayMs);
|
|
322
|
+
if (dayStart < rangeStart || dayStart > todayStart) continue;
|
|
323
|
+
}
|
|
324
|
+
const stamp = new Date(updatedAtMs);
|
|
325
|
+
const weekday = (stamp.getUTCDay() + 6) % 7;
|
|
326
|
+
const hour = stamp.getUTCHours();
|
|
327
|
+
const messageCount = Number.isFinite(Number(session.messageCount))
|
|
328
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
329
|
+
: 0;
|
|
330
|
+
const tokenTotal = readSessionTotalTokens(session);
|
|
331
|
+
normalized.push({ weekday, hour, messageCount, tokenTotal });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const grid = Array.from({ length: 7 }, () =>
|
|
335
|
+
Array.from({ length: 24 }, () => ({ sessionCount: 0, messageCount: 0, tokenTotal: 0 }))
|
|
336
|
+
);
|
|
337
|
+
for (const item of normalized) {
|
|
338
|
+
const cell = grid[item.weekday][item.hour];
|
|
339
|
+
cell.sessionCount += 1;
|
|
340
|
+
cell.messageCount += item.messageCount;
|
|
341
|
+
cell.tokenTotal += item.tokenTotal;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let maxSessionCount = 0;
|
|
345
|
+
for (let day = 0; day < 7; day += 1) {
|
|
346
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
347
|
+
maxSessionCount = Math.max(maxSessionCount, grid[day][hour].sessionCount);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
range,
|
|
353
|
+
grid,
|
|
354
|
+
maxSessionCount: Math.max(1, maxSessionCount),
|
|
355
|
+
weekdayKeys: [0, 1, 2, 3, 4, 5, 6],
|
|
356
|
+
hourLabels: Array.from({ length: 24 }, (_, index) => String(index).padStart(2, '0'))
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
304
360
|
function buildUsageBuckets(normalizedSessions, options = {}) {
|
|
305
361
|
const range = normalizeUsageRange(options.range);
|
|
306
362
|
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|