codexmate 0.0.27 → 0.0.29
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 +1 -1
- package/README.zh.md +1 -1
- package/cli/builtin-proxy.js +430 -4
- package/cli/openai-bridge.js +498 -13
- package/cli.js +130 -41
- package/lib/cli-models-utils.js +71 -10
- package/lib/cli-webhook.js +126 -0
- package/package.json +76 -74
- 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 +21 -16
- package/web-ui/index.html +1 -0
- package/web-ui/logic.codex.mjs +69 -0
- package/web-ui/modules/app.computed.dashboard.mjs +54 -0
- package/web-ui/modules/app.computed.session.mjs +22 -17
- package/web-ui/modules/app.methods.claude-config.mjs +24 -8
- package/web-ui/modules/app.methods.codex-config.mjs +35 -3
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.navigation.mjs +21 -3
- package/web-ui/modules/app.methods.providers.mjs +96 -7
- package/web-ui/modules/app.methods.session-actions.mjs +3 -6
- package/web-ui/modules/app.methods.session-browser.mjs +1 -6
- package/web-ui/modules/app.methods.session-trash.mjs +6 -7
- package/web-ui/modules/app.methods.startup-claude.mjs +8 -1
- package/web-ui/modules/app.methods.webhook.mjs +79 -0
- package/web-ui/modules/i18n.dict.mjs +1104 -104
- package/web-ui/modules/i18n.mjs +9 -3
- package/web-ui/modules/provider-url-display.mjs +17 -0
- package/web-ui/partials/index/layout-header.html +25 -0
- package/web-ui/partials/index/modals-basic.html +0 -3
- package/web-ui/partials/index/panel-config-claude.html +10 -3
- package/web-ui/partials/index/panel-config-codex.html +44 -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 +93 -177
- package/web-ui/partials/index/panel-trash.html +88 -0
- 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/docs-panel.css +63 -39
- package/web-ui/styles/layout-shell.css +69 -46
- 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/settings-panel.css +197 -33
- package/web-ui/styles/titles-cards.css +90 -26
- package/web-ui/styles/trash-panel.css +90 -0
- package/web-ui/styles/webhook.css +81 -0
- package/web-ui/styles.css +2 -0
|
@@ -43,6 +43,16 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
|
|
46
|
+
openCloneClaudeConfigModal(name, config) {
|
|
47
|
+
this.newClaudeConfig = {
|
|
48
|
+
name: '',
|
|
49
|
+
apiKey: config.apiKey || '',
|
|
50
|
+
baseUrl: config.baseUrl || '',
|
|
51
|
+
model: config.model || ''
|
|
52
|
+
};
|
|
53
|
+
this.showClaudeConfigModal = true;
|
|
54
|
+
},
|
|
55
|
+
|
|
46
56
|
openEditConfigModal(name) {
|
|
47
57
|
const config = this.claudeConfigs[name];
|
|
48
58
|
this.editingConfig = {
|
|
@@ -77,7 +87,7 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
77
87
|
|
|
78
88
|
const config = this.claudeConfigs[name];
|
|
79
89
|
if (!config.apiKey) {
|
|
80
|
-
this.showMessage('
|
|
90
|
+
this.showMessage('已保存(未填写 API Key)', 'info');
|
|
81
91
|
this.closeEditConfigModal();
|
|
82
92
|
if (name === this.currentClaudeConfig) {
|
|
83
93
|
this.refreshClaudeModelContext();
|
|
@@ -85,14 +95,17 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
85
95
|
return;
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
|
|
88
99
|
try {
|
|
89
100
|
const res = await api('apply-claude-config', { config });
|
|
90
101
|
if (res.error || res.success === false) {
|
|
91
102
|
this.showMessage(res.error || '应用配置失败', 'error');
|
|
92
103
|
} else {
|
|
93
104
|
this.currentClaudeConfig = name;
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
if (this._lastAppliedClaudeKey !== _claudeKey) {
|
|
106
|
+
this.showMessage('Claude 配置已生效', 'success');
|
|
107
|
+
this._lastAppliedClaudeKey = _claudeKey;
|
|
108
|
+
}
|
|
96
109
|
this.closeEditConfigModal();
|
|
97
110
|
this.refreshClaudeModelContext();
|
|
98
111
|
}
|
|
@@ -153,18 +166,21 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
153
166
|
|
|
154
167
|
if (!config.apiKey) {
|
|
155
168
|
if (config.externalCredentialType) {
|
|
156
|
-
return this.showMessage('
|
|
169
|
+
return this.showMessage('使用外部认证,无需 API Key', 'info');
|
|
157
170
|
}
|
|
158
171
|
return this.showMessage('请先配置 API Key', 'error');
|
|
159
172
|
}
|
|
160
173
|
|
|
174
|
+
const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
|
|
161
175
|
try {
|
|
162
176
|
const res = await api('apply-claude-config', { config });
|
|
163
177
|
if (res.error || res.success === false) {
|
|
164
178
|
this.showMessage(res.error || '应用配置失败', 'error');
|
|
165
179
|
} else {
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
if (this._lastAppliedClaudeKey !== _claudeKey2) {
|
|
181
|
+
this.showMessage('配置已应用', 'success');
|
|
182
|
+
this._lastAppliedClaudeKey = _claudeKey2;
|
|
183
|
+
}
|
|
168
184
|
}
|
|
169
185
|
} catch (_) {
|
|
170
186
|
this.showMessage('应用配置失败', 'error');
|
|
@@ -176,8 +192,8 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
176
192
|
this.newClaudeConfig = {
|
|
177
193
|
name: '',
|
|
178
194
|
apiKey: '',
|
|
179
|
-
baseUrl: '
|
|
180
|
-
model: '
|
|
195
|
+
baseUrl: '',
|
|
196
|
+
model: ''
|
|
181
197
|
};
|
|
182
198
|
}
|
|
183
199
|
};
|
|
@@ -180,7 +180,19 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
180
180
|
const previousModels = Array.isArray(this.models) ? [...this.models] : [];
|
|
181
181
|
const previousModelsSource = this.modelsSource;
|
|
182
182
|
const previousModelsHasCurrent = this.modelsHasCurrent;
|
|
183
|
+
// 切走前把上一个 provider 的 model 落到内存字典,避免切回时显示抖动。
|
|
184
|
+
if (previousProvider && typeof previousModel === 'string' && previousModel.trim() && previousModel !== '未设置') {
|
|
185
|
+
if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
|
|
186
|
+
this.currentModels[previousProvider] = previousModel.trim();
|
|
187
|
+
}
|
|
183
188
|
this.currentProvider = name;
|
|
189
|
+
// 立即按字典预填,让 UI 不出现空白;远端 /models 后台异步补齐。
|
|
190
|
+
const dictModel = typeof this.activeProviderModel === 'function'
|
|
191
|
+
? this.activeProviderModel(name)
|
|
192
|
+
: '';
|
|
193
|
+
if (dictModel) {
|
|
194
|
+
this.currentModel = dictModel;
|
|
195
|
+
}
|
|
184
196
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
185
197
|
|
|
186
198
|
// 不要把“切换提供商”强绑定到 /models 成功与否:
|
|
@@ -195,7 +207,11 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
195
207
|
|
|
196
208
|
await Promise.race([modelsTask, delay(250)]);
|
|
197
209
|
|
|
198
|
-
|
|
210
|
+
// 只在“字典中没有该 provider 记录”时才允许 remote 首项覆盖,否则尊重用户上次选择。
|
|
211
|
+
if (!dictModel
|
|
212
|
+
&& this.modelsSource === 'remote'
|
|
213
|
+
&& this.models.length > 0
|
|
214
|
+
&& !this.models.includes(this.currentModel)) {
|
|
199
215
|
this.currentModel = this.models[0];
|
|
200
216
|
this.modelsHasCurrent = true;
|
|
201
217
|
}
|
|
@@ -208,7 +224,10 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
208
224
|
await modelsTask;
|
|
209
225
|
|
|
210
226
|
if (this.currentProvider === name) {
|
|
211
|
-
if (
|
|
227
|
+
if (!dictModel
|
|
228
|
+
&& this.modelsSource === 'remote'
|
|
229
|
+
&& this.models.length > 0
|
|
230
|
+
&& !this.models.includes(this.currentModel)) {
|
|
212
231
|
this.currentModel = this.models[0];
|
|
213
232
|
this.modelsHasCurrent = true;
|
|
214
233
|
if (getProviderConfigModeMeta(this.configMode)) {
|
|
@@ -261,6 +280,12 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
261
280
|
},
|
|
262
281
|
|
|
263
282
|
async onModelChange() {
|
|
283
|
+
const name = String(this.currentProvider || '').trim();
|
|
284
|
+
const model = typeof this.currentModel === 'string' ? this.currentModel.trim() : '';
|
|
285
|
+
if (name && model) {
|
|
286
|
+
if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
|
|
287
|
+
this.currentModels[name] = model;
|
|
288
|
+
}
|
|
264
289
|
await this.applyCodexConfigDirect();
|
|
265
290
|
},
|
|
266
291
|
|
|
@@ -629,6 +654,8 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
629
654
|
this.modelContextWindowInput = modelContextWindow.text;
|
|
630
655
|
this.modelAutoCompactTokenLimitInput = modelAutoCompactTokenLimit.text;
|
|
631
656
|
|
|
657
|
+
const _codexKey = `${provider}|${model}|${this.serviceTier || ""}|${this.modelReasoningEffort || ""}|${modelContextWindow.value}|${modelAutoCompactTokenLimit.value}`;
|
|
658
|
+
|
|
632
659
|
this.codexApplying = true;
|
|
633
660
|
try {
|
|
634
661
|
const tplRes = await api('get-config-template', {
|
|
@@ -665,7 +692,12 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
665
692
|
}
|
|
666
693
|
|
|
667
694
|
if (options.silent !== true) {
|
|
668
|
-
this.
|
|
695
|
+
if (this._lastAppliedCodexKey !== _codexKey) {
|
|
696
|
+
this.showMessage('配置已应用', 'success');
|
|
697
|
+
this._lastAppliedCodexKey = _codexKey;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
this._lastAppliedCodexKey = _codexKey;
|
|
669
701
|
}
|
|
670
702
|
|
|
671
703
|
const refreshOptions = options.silent === true
|
|
@@ -29,6 +29,7 @@ import { createStartupClaudeMethods } from './app.methods.startup-claude.mjs';
|
|
|
29
29
|
import { createSkillsMethods } from './skills.methods.mjs';
|
|
30
30
|
import { createPluginsMethods } from './plugins.methods.mjs';
|
|
31
31
|
import { createI18nMethods } from './i18n.mjs';
|
|
32
|
+
import { createWebhookMethods } from './app.methods.webhook.mjs';
|
|
32
33
|
import {
|
|
33
34
|
CONFIG_MODE_SET,
|
|
34
35
|
getProviderConfigModeMeta
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
export function createAppMethods() {
|
|
44
45
|
return {
|
|
45
46
|
...createI18nMethods(),
|
|
47
|
+
...createWebhookMethods(),
|
|
46
48
|
...createStartupClaudeMethods({
|
|
47
49
|
api,
|
|
48
50
|
defaultModelContextWindow: DEFAULT_MODEL_CONTEXT_WINDOW,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function createNavigationMethods(options = {}) {
|
|
1
|
+
export function createNavigationMethods(options = {}) {
|
|
2
2
|
const {
|
|
3
3
|
configModeSet,
|
|
4
4
|
switchMainTabHelper,
|
|
@@ -14,7 +14,8 @@ export function createNavigationMethods(options = {}) {
|
|
|
14
14
|
'market',
|
|
15
15
|
'plugins',
|
|
16
16
|
'docs',
|
|
17
|
-
'settings'
|
|
17
|
+
'settings',
|
|
18
|
+
'trash'
|
|
18
19
|
]);
|
|
19
20
|
const loadDoctorOverview = async (vm, options = {}) => {
|
|
20
21
|
if (!vm || typeof vm !== 'object') return false;
|
|
@@ -67,7 +68,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
67
68
|
: vm.configMode;
|
|
68
69
|
const mainTab = typeof mainTabSource === 'string' ? mainTabSource.trim().toLowerCase() : '';
|
|
69
70
|
const configMode = typeof configModeSource === 'string' ? configModeSource.trim().toLowerCase() : '';
|
|
71
|
+
const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
|
|
70
72
|
const snapshot = {
|
|
73
|
+
settingsTab: settingsTab === 'data' ? 'data' : 'general',
|
|
71
74
|
mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'dashboard',
|
|
72
75
|
configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex'
|
|
73
76
|
};
|
|
@@ -77,6 +80,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
return {
|
|
83
|
+
saveNavState() {
|
|
84
|
+
persistNavState(this);
|
|
85
|
+
},
|
|
80
86
|
restoreNavStateFromStorage() {
|
|
81
87
|
if (this.__navStateRestoring) return false;
|
|
82
88
|
const restored = readNavState();
|
|
@@ -89,7 +95,11 @@ export function createNavigationMethods(options = {}) {
|
|
|
89
95
|
: '';
|
|
90
96
|
const shouldUpdateConfigMode = !!(nextConfigMode && configModeSet && configModeSet.has(nextConfigMode));
|
|
91
97
|
const shouldUpdateMainTab = !!(nextMainTab && MAIN_TAB_SET.has(nextMainTab) && nextMainTab !== this.mainTab);
|
|
92
|
-
|
|
98
|
+
const nextSettingsTab = restored && typeof restored.settingsTab === 'string'
|
|
99
|
+
? restored.settingsTab.trim().toLowerCase()
|
|
100
|
+
: '';
|
|
101
|
+
const shouldUpdateSettingsTab = !!(nextSettingsTab && (nextSettingsTab === 'general' || nextSettingsTab === 'data') && nextSettingsTab !== this.settingsTab);
|
|
102
|
+
if (!shouldUpdateConfigMode && !shouldUpdateMainTab && !shouldUpdateSettingsTab) {
|
|
93
103
|
return false;
|
|
94
104
|
}
|
|
95
105
|
this.__navStateRestoring = true;
|
|
@@ -97,6 +107,9 @@ export function createNavigationMethods(options = {}) {
|
|
|
97
107
|
if (shouldUpdateConfigMode) {
|
|
98
108
|
this.configMode = nextConfigMode;
|
|
99
109
|
}
|
|
110
|
+
if (shouldUpdateSettingsTab) {
|
|
111
|
+
this.settingsTab = nextSettingsTab;
|
|
112
|
+
}
|
|
100
113
|
if (shouldUpdateMainTab) {
|
|
101
114
|
this.switchMainTab(nextMainTab);
|
|
102
115
|
}
|
|
@@ -411,6 +424,11 @@ export function createNavigationMethods(options = {}) {
|
|
|
411
424
|
switchState.ticket += 1;
|
|
412
425
|
switchState.pendingTarget = '';
|
|
413
426
|
if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
|
|
427
|
+
if (targetTab === 'trash' && !this.sessionTrashLoadedOnce) {
|
|
428
|
+
if (typeof this.loadSessionTrash === 'function') {
|
|
429
|
+
void this.loadSessionTrash({ forceRefresh: false });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
414
432
|
void loadDoctorOverview(this);
|
|
415
433
|
}
|
|
416
434
|
if (
|
|
@@ -48,6 +48,12 @@ function normalizeProviderDraftState(target) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function maskKeyLocal(key) {
|
|
52
|
+
if (!key) return '';
|
|
53
|
+
if (key.length <= 8) return '****';
|
|
54
|
+
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
function getProviderValidationForContext(vm, mode = 'add') {
|
|
52
58
|
const draft = mode === 'edit' ? vm.editingProvider : vm.newProvider;
|
|
53
59
|
const editingName = mode === 'edit' ? normalizeText(draft && draft.name) : '';
|
|
@@ -149,15 +155,38 @@ export function createProvidersMethods(options = {}) {
|
|
|
149
155
|
if (this.newProvider && this.newProvider.useTransform) {
|
|
150
156
|
payload.useTransform = true;
|
|
151
157
|
}
|
|
158
|
+
const suggestedModel = typeof this.newProvider._suggestedModel === 'string'
|
|
159
|
+
? this.newProvider._suggestedModel.trim()
|
|
160
|
+
: '';
|
|
152
161
|
const res = await api('add-provider', payload);
|
|
153
162
|
if (res.error) {
|
|
154
163
|
this.showMessage(res.error, 'error');
|
|
155
164
|
return;
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
// 本地更新:构造新 provider 对象并追加到列表
|
|
168
|
+
const newProvider = {
|
|
169
|
+
name: validation.name,
|
|
170
|
+
url: validation.url,
|
|
171
|
+
upstreamUrl: '',
|
|
172
|
+
codexmate_bridge: payload.useTransform ? 'openai' : '',
|
|
173
|
+
key: maskKeyLocal(payload.key),
|
|
174
|
+
hasKey: !!payload.key,
|
|
175
|
+
models: [],
|
|
176
|
+
current: false,
|
|
177
|
+
readOnly: false,
|
|
178
|
+
nonDeletable: false,
|
|
179
|
+
nonEditable: false
|
|
180
|
+
};
|
|
181
|
+
this.providersList = [...this.providersList, newProvider];
|
|
182
|
+
|
|
158
183
|
this.showMessage('操作成功', 'success');
|
|
159
184
|
this.closeAddModal();
|
|
160
|
-
|
|
185
|
+
|
|
186
|
+
if (suggestedModel) {
|
|
187
|
+
if (!this.currentModels || typeof this.currentModels !== 'object') this.currentModels = {};
|
|
188
|
+
this.currentModels[validation.name] = suggestedModel;
|
|
189
|
+
}
|
|
161
190
|
} catch (e) {
|
|
162
191
|
this.showMessage('添加失败', 'error');
|
|
163
192
|
}
|
|
@@ -229,17 +258,42 @@ export function createProvidersMethods(options = {}) {
|
|
|
229
258
|
this.showMessage(res.error, 'error');
|
|
230
259
|
return;
|
|
231
260
|
}
|
|
261
|
+
|
|
262
|
+
// 本地更新:从列表中移除
|
|
263
|
+
this.providersList = this.providersList.filter(p => p.name !== name);
|
|
264
|
+
|
|
265
|
+
// 清理 currentModels
|
|
266
|
+
if (this.currentModels && this.currentModels[name]) {
|
|
267
|
+
delete this.currentModels[name];
|
|
268
|
+
}
|
|
269
|
+
|
|
232
270
|
if (res.switched && res.provider) {
|
|
271
|
+
this.currentProvider = res.provider;
|
|
272
|
+
if (res.model) this.currentModel = res.model;
|
|
273
|
+
// 更新 current 标记
|
|
274
|
+
this.providersList = this.providersList.map(p => ({
|
|
275
|
+
...p,
|
|
276
|
+
current: p.name === res.provider
|
|
277
|
+
}));
|
|
233
278
|
this.showMessage(`已删除提供商,自动切换到 ${res.provider}${res.model ? ` / ${res.model}` : ''}`, 'success');
|
|
234
279
|
} else {
|
|
235
280
|
this.showMessage('操作成功', 'success');
|
|
236
281
|
}
|
|
237
|
-
await this.loadAll();
|
|
238
282
|
} catch (_) {
|
|
239
283
|
this.showMessage('删除失败', 'error');
|
|
240
284
|
}
|
|
241
285
|
},
|
|
242
286
|
|
|
287
|
+
openCloneProviderModal(provider) {
|
|
288
|
+
this.newProvider = {
|
|
289
|
+
name: '',
|
|
290
|
+
url: normalizeProviderUrl(provider.url || ''),
|
|
291
|
+
key: '',
|
|
292
|
+
useTransform: !!(provider.codexmate_bridge || '').trim() || /\/bridge\/openai\//.test(provider.url || '')
|
|
293
|
+
};
|
|
294
|
+
this.showAddModal = true;
|
|
295
|
+
},
|
|
296
|
+
|
|
243
297
|
async openEditModal(provider) {
|
|
244
298
|
const requestId = Symbol('openEditModal');
|
|
245
299
|
this._openEditModalRequestId = requestId;
|
|
@@ -311,9 +365,22 @@ export function createProvidersMethods(options = {}) {
|
|
|
311
365
|
this.showMessage(res.error, 'error');
|
|
312
366
|
return;
|
|
313
367
|
}
|
|
368
|
+
|
|
369
|
+
// 本地更新:更新列表中对应 provider 的 url 和 key
|
|
370
|
+
this.providersList = this.providersList.map(p => {
|
|
371
|
+
if (p.name === validation.name) {
|
|
372
|
+
return {
|
|
373
|
+
...p,
|
|
374
|
+
url: validation.url,
|
|
375
|
+
key: params.key ? maskKeyLocal(params.key) : p.key,
|
|
376
|
+
hasKey: params.key ? true : p.hasKey
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return p;
|
|
380
|
+
});
|
|
381
|
+
|
|
314
382
|
this.closeEditModal();
|
|
315
383
|
this.showMessage('操作成功', 'success');
|
|
316
|
-
await this.loadAll();
|
|
317
384
|
} catch (e) {
|
|
318
385
|
this.showMessage('更新失败', 'error');
|
|
319
386
|
}
|
|
@@ -348,13 +415,26 @@ export function createProvidersMethods(options = {}) {
|
|
|
348
415
|
return this.showMessage('请输入模型', 'error');
|
|
349
416
|
}
|
|
350
417
|
try {
|
|
351
|
-
const
|
|
418
|
+
const modelName = this.newModelName.trim();
|
|
419
|
+
const res = await api('add-model', { model: modelName });
|
|
352
420
|
if (res.error) {
|
|
353
421
|
this.showMessage(res.error, 'error');
|
|
354
422
|
} else {
|
|
423
|
+
// 本地更新:在当前 provider 的 models 中追加
|
|
424
|
+
this.providersList = this.providersList.map(p => {
|
|
425
|
+
if (p.name === this.currentProvider) {
|
|
426
|
+
const exists = p.models.some(m => m.id === modelName);
|
|
427
|
+
if (!exists) {
|
|
428
|
+
return {
|
|
429
|
+
...p,
|
|
430
|
+
models: [...p.models, { id: modelName, name: modelName, cost: null, contextWindow: undefined, maxTokens: undefined }]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return p;
|
|
435
|
+
});
|
|
355
436
|
this.showMessage('操作成功', 'success');
|
|
356
437
|
this.closeModelModal();
|
|
357
|
-
await this.loadAll();
|
|
358
438
|
}
|
|
359
439
|
} catch (_) {
|
|
360
440
|
this.showMessage('新增模型失败', 'error');
|
|
@@ -367,8 +447,17 @@ export function createProvidersMethods(options = {}) {
|
|
|
367
447
|
if (res.error) {
|
|
368
448
|
this.showMessage(res.error, 'error');
|
|
369
449
|
} else {
|
|
450
|
+
// 本地更新:从当前 provider 的 models 中移除
|
|
451
|
+
this.providersList = this.providersList.map(p => {
|
|
452
|
+
if (p.name === this.currentProvider) {
|
|
453
|
+
return {
|
|
454
|
+
...p,
|
|
455
|
+
models: p.models.filter(m => m.id !== model)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return p;
|
|
459
|
+
});
|
|
370
460
|
this.showMessage('操作成功', 'success');
|
|
371
|
-
await this.loadAll();
|
|
372
461
|
}
|
|
373
462
|
} catch (_) {
|
|
374
463
|
this.showMessage('删除模型失败', 'error');
|
|
@@ -377,7 +466,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
377
466
|
|
|
378
467
|
closeAddModal() {
|
|
379
468
|
this.showAddModal = false;
|
|
380
|
-
this.newProvider = { name: '', url: '', key: '', useTransform: false };
|
|
469
|
+
this.newProvider = { name: '', url: '', key: '', useTransform: false, _suggestedModel: '' };
|
|
381
470
|
},
|
|
382
471
|
|
|
383
472
|
closeModelModal() {
|
|
@@ -181,12 +181,9 @@ export function createSessionActionMethods(options = {}) {
|
|
|
181
181
|
return `gemini -r ${arg}`;
|
|
182
182
|
}
|
|
183
183
|
if (source === 'claude') {
|
|
184
|
-
return `claude -r ${arg}`;
|
|
184
|
+
return `claude --dangerously-skip-permissions -r ${arg}`;
|
|
185
185
|
}
|
|
186
|
-
|
|
187
|
-
return `codex --yolo resume ${arg}`;
|
|
188
|
-
}
|
|
189
|
-
return `codex resume ${arg}`;
|
|
186
|
+
return `codex --yolo resume ${arg}`;
|
|
190
187
|
},
|
|
191
188
|
|
|
192
189
|
extractClaudeResumeKeyFromFilePath(filePath) {
|
|
@@ -254,7 +251,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
254
251
|
|
|
255
252
|
getShareCommandPrefixInvocation() {
|
|
256
253
|
const prefix = this.normalizeShareCommandPrefix(this.shareCommandPrefix);
|
|
257
|
-
return prefix === 'codexmate' ? 'codexmate' : 'npm start';
|
|
254
|
+
return prefix === 'codexmate' ? 'codexmate' : 'npm start --';
|
|
258
255
|
},
|
|
259
256
|
|
|
260
257
|
setShareCommandPrefix(value) {
|
|
@@ -249,11 +249,6 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
249
249
|
}
|
|
250
250
|
},
|
|
251
251
|
|
|
252
|
-
onSessionResumeYoloChange() {
|
|
253
|
-
const value = this.sessionResumeWithYolo ? '1' : '0';
|
|
254
|
-
localStorage.setItem('codexmateSessionResumeYolo', value);
|
|
255
|
-
},
|
|
256
|
-
|
|
257
252
|
normalizeSessionSortMode(value) {
|
|
258
253
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
259
254
|
return normalized === 'hot' ? 'hot' : 'time';
|
|
@@ -698,7 +693,7 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
698
693
|
for (const session of visible) {
|
|
699
694
|
if (!session || typeof session !== 'object') continue;
|
|
700
695
|
const messageCountRaw = Number(session.messageCount);
|
|
701
|
-
const shouldHydrate = !Number.isFinite(messageCountRaw) || messageCountRaw === 0;
|
|
696
|
+
const shouldHydrate = !Number.isFinite(messageCountRaw) || (messageCountRaw === 0 && !session.__messageCountExact);
|
|
702
697
|
if (!shouldHydrate) continue;
|
|
703
698
|
const key = this.getSessionExportKey(session);
|
|
704
699
|
if (!key) continue;
|
|
@@ -208,10 +208,10 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
208
208
|
},
|
|
209
209
|
|
|
210
210
|
normalizeSettingsTab(tab) {
|
|
211
|
-
if (tab === '
|
|
211
|
+
if (tab === 'general' || tab === 'data') {
|
|
212
212
|
return tab;
|
|
213
213
|
}
|
|
214
|
-
return '
|
|
214
|
+
return 'general';
|
|
215
215
|
},
|
|
216
216
|
|
|
217
217
|
async onSettingsTabClick(tab) {
|
|
@@ -221,12 +221,11 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
221
221
|
async switchSettingsTab(tab, options = {}) {
|
|
222
222
|
const nextTab = this.normalizeSettingsTab(tab);
|
|
223
223
|
this.settingsTab = nextTab;
|
|
224
|
-
if (
|
|
225
|
-
|
|
224
|
+
if (typeof this.saveNavState === 'function') {
|
|
225
|
+
this.saveNavState();
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await this.loadSessionTrash({ forceRefresh });
|
|
227
|
+
if (nextTab !== 'data') {
|
|
228
|
+
return;
|
|
230
229
|
}
|
|
231
230
|
},
|
|
232
231
|
|
|
@@ -60,7 +60,14 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
62
|
this.currentProvider = statusRes.provider;
|
|
63
|
-
this.
|
|
63
|
+
this.currentModels = statusRes.currentModels && typeof statusRes.currentModels === 'object'
|
|
64
|
+
? { ...statusRes.currentModels }
|
|
65
|
+
: {};
|
|
66
|
+
const dictModelForCurrent = this.currentProvider
|
|
67
|
+
&& typeof this.currentModels[this.currentProvider] === 'string'
|
|
68
|
+
? this.currentModels[this.currentProvider].trim()
|
|
69
|
+
: '';
|
|
70
|
+
this.currentModel = dictModelForCurrent || statusRes.model;
|
|
64
71
|
try {
|
|
65
72
|
const installRes = await withTimeout(api('install-status'), Math.max(0, Math.min(1200, timeLeftMs())));
|
|
66
73
|
if (installRes && !installRes.error) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { api } from './api.mjs';
|
|
2
|
+
|
|
3
|
+
export function createWebhookMethods() {
|
|
4
|
+
return {
|
|
5
|
+
async loadWebhookSettings() {
|
|
6
|
+
try {
|
|
7
|
+
const data = await api('get-webhook-config');
|
|
8
|
+
if (data && typeof data === 'object' && !data.error) {
|
|
9
|
+
this.webhookConfig = {
|
|
10
|
+
enabled: !!data.enabled,
|
|
11
|
+
url: typeof data.url === 'string' ? data.url : '',
|
|
12
|
+
events: Array.isArray(data.events) && data.events.length
|
|
13
|
+
? data.events.slice()
|
|
14
|
+
: this.webhookEventOptions.slice()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
} catch (e) {
|
|
18
|
+
this.webhookTestResult = { ok: false, error: e && e.message ? e.message : String(e) };
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async saveWebhookSettings() {
|
|
23
|
+
this.webhookSaving = true;
|
|
24
|
+
try {
|
|
25
|
+
const cfg = {
|
|
26
|
+
enabled: !!this.webhookConfig.enabled,
|
|
27
|
+
url: typeof this.webhookConfig.url === 'string' ? this.webhookConfig.url.trim() : '',
|
|
28
|
+
events: Array.isArray(this.webhookConfig.events) ? this.webhookConfig.events.slice() : []
|
|
29
|
+
};
|
|
30
|
+
const saved = await api('set-webhook-config', { config: cfg });
|
|
31
|
+
if (saved && typeof saved === 'object' && !saved.error) {
|
|
32
|
+
this.webhookConfig = {
|
|
33
|
+
enabled: !!saved.enabled,
|
|
34
|
+
url: typeof saved.url === 'string' ? saved.url : '',
|
|
35
|
+
events: Array.isArray(saved.events) ? saved.events.slice() : []
|
|
36
|
+
};
|
|
37
|
+
this.webhookTestResult = { ok: true, status: 'saved' };
|
|
38
|
+
} else {
|
|
39
|
+
this.webhookTestResult = { ok: false, error: (saved && saved.error) || 'save failed' };
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
this.webhookTestResult = { ok: false, error: e && e.message ? e.message : String(e) };
|
|
43
|
+
} finally {
|
|
44
|
+
this.webhookSaving = false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async testWebhook() {
|
|
49
|
+
this.webhookTesting = true;
|
|
50
|
+
try {
|
|
51
|
+
const cfg = {
|
|
52
|
+
enabled: true,
|
|
53
|
+
url: typeof this.webhookConfig.url === 'string' ? this.webhookConfig.url.trim() : '',
|
|
54
|
+
events: Array.isArray(this.webhookConfig.events) && this.webhookConfig.events.length
|
|
55
|
+
? this.webhookConfig.events.slice()
|
|
56
|
+
: this.webhookEventOptions.slice()
|
|
57
|
+
};
|
|
58
|
+
const r = await api('test-webhook', { config: cfg });
|
|
59
|
+
this.webhookTestResult = r || { ok: false, error: 'no result' };
|
|
60
|
+
} catch (e) {
|
|
61
|
+
this.webhookTestResult = { ok: false, error: e && e.message ? e.message : String(e) };
|
|
62
|
+
} finally {
|
|
63
|
+
this.webhookTesting = false;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
toggleWebhookEvent(eventName) {
|
|
68
|
+
if (!Array.isArray(this.webhookConfig.events)) {
|
|
69
|
+
this.webhookConfig.events = [];
|
|
70
|
+
}
|
|
71
|
+
const idx = this.webhookConfig.events.indexOf(eventName);
|
|
72
|
+
if (idx === -1) {
|
|
73
|
+
this.webhookConfig.events.push(eventName);
|
|
74
|
+
} else {
|
|
75
|
+
this.webhookConfig.events.splice(idx, 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|