codexmate 0.1.10 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/README.vi.md +4 -7
- package/README.zh.md +5 -7
- package/cli/agents-files.js +27 -3
- package/cli/prompt-history.js +171 -0
- package/cli/system-prompt-files.js +20 -2
- package/cli.js +162 -5
- package/package.json +1 -1
- package/web-ui/app.js +95 -4
- package/web-ui/index.html +2 -1
- package/web-ui/modules/app.methods.agents.mjs +90 -0
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.navigation.mjs +7 -1
- package/web-ui/modules/app.methods.pi-config.mjs +371 -0
- package/web-ui/modules/app.methods.session-actions.mjs +57 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
- package/web-ui/modules/app.methods.system-prompt.mjs +81 -0
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +8 -1
- package/web-ui/modules/config-mode.computed.mjs +2 -1
- package/web-ui/modules/i18n/locales/en.mjs +52 -0
- package/web-ui/modules/i18n/locales/ja.mjs +52 -0
- package/web-ui/modules/i18n/locales/vi.mjs +52 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +52 -0
- package/web-ui/modules/i18n/locales/zh.mjs +52 -0
- package/web-ui/partials/index/layout-header.html +20 -0
- package/web-ui/partials/index/panel-config-claude.html +3 -3
- package/web-ui/partials/index/panel-config-codex.html +3 -3
- package/web-ui/partials/index/panel-config-kilocode.html +5 -5
- package/web-ui/partials/index/panel-config-openclaw.html +3 -3
- package/web-ui/partials/index/panel-config-opencode.html +5 -5
- package/web-ui/partials/index/panel-config-pi.html +171 -0
- package/web-ui/partials/index/panel-prompts.html +52 -0
- package/web-ui/partials/index/panel-sessions.html +12 -0
- package/web-ui/partials/index/panel-settings.html +42 -0
- package/web-ui/res/web-ui-render.precompiled.js +1101 -252
- package/web-ui/styles/modals-core.css +86 -0
- package/web-ui/styles/sessions-preview.css +38 -0
- package/web-ui/styles/settings-panel.css +7 -0
- package/web-ui/styles/titles-cards.css +2 -2
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { api } from './api.mjs';
|
|
2
|
+
|
|
3
|
+
function isPiPlainObject(value) {
|
|
4
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createPiConfigMethods({ api: apiClient }) {
|
|
8
|
+
return {
|
|
9
|
+
async loadPiProviders() {
|
|
10
|
+
const result = await apiClient('read-pi-models');
|
|
11
|
+
const providers = isPiPlainObject(result && result.providers) ? result.providers : {};
|
|
12
|
+
return providers;
|
|
13
|
+
},
|
|
14
|
+
async loadPiSettings() {
|
|
15
|
+
const result = await apiClient('read-pi-settings');
|
|
16
|
+
const settings = isPiPlainObject(result && result.settings) ? result.settings : {};
|
|
17
|
+
this.piActiveProvider = typeof settings.defaultProvider === 'string' ? settings.defaultProvider : '';
|
|
18
|
+
this.piActiveModel = typeof settings.defaultModel === 'string' ? settings.defaultModel : '';
|
|
19
|
+
return settings;
|
|
20
|
+
},
|
|
21
|
+
async switchPiActiveProvider(providerId) {
|
|
22
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
23
|
+
const provider = this.piProviders[providerId];
|
|
24
|
+
if (!isPiPlainObject(provider)) return;
|
|
25
|
+
const firstModel = Array.isArray(provider.models) && provider.models.length > 0
|
|
26
|
+
? (provider.models[0] && provider.models[0].id) || ''
|
|
27
|
+
: '';
|
|
28
|
+
const updates = { defaultProvider: providerId };
|
|
29
|
+
if (firstModel) updates.defaultModel = firstModel;
|
|
30
|
+
try {
|
|
31
|
+
const result = await apiClient('write-pi-settings', updates);
|
|
32
|
+
if (result && result.error) throw new Error(result.error);
|
|
33
|
+
await this.loadPiSettings();
|
|
34
|
+
this.message = '已切换为使用该供应商';
|
|
35
|
+
this.messageType = 'success';
|
|
36
|
+
} catch (e) {
|
|
37
|
+
this.message = e && e.message ? e.message : '切换默认供应商失败';
|
|
38
|
+
this.messageType = 'error';
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
async savePiProviders(providers) {
|
|
42
|
+
return apiClient('write-pi-models', { providers });
|
|
43
|
+
},
|
|
44
|
+
async removePiProvider(providerId) {
|
|
45
|
+
const providers = await this.loadPiProviders();
|
|
46
|
+
const existing = providers[providerId] || {};
|
|
47
|
+
const updated = { ...providers };
|
|
48
|
+
delete updated[providerId];
|
|
49
|
+
await this.savePiProviders(updated);
|
|
50
|
+
return { success: true, provider: existing };
|
|
51
|
+
},
|
|
52
|
+
async persistPiProvider(providerId, values) {
|
|
53
|
+
const providers = await this.loadPiProviders();
|
|
54
|
+
const existing = providers[providerId] || {};
|
|
55
|
+
const current = isPiPlainObject(values) ? values : {};
|
|
56
|
+
|
|
57
|
+
const updatedProvider = {
|
|
58
|
+
...existing,
|
|
59
|
+
...current
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return this.savePiProviders({ ...providers, [providerId]: updatedProvider });
|
|
63
|
+
},
|
|
64
|
+
async loadPiSources() {
|
|
65
|
+
this.piProviderLoading = true;
|
|
66
|
+
try {
|
|
67
|
+
const [providers] = await Promise.all([
|
|
68
|
+
this.loadPiProviders(),
|
|
69
|
+
this.loadPiSettings()
|
|
70
|
+
]);
|
|
71
|
+
this.piProviders = providers || {};
|
|
72
|
+
this.piProviderIds = Object.keys(this.piProviders || {}).sort();
|
|
73
|
+
} catch (e) {
|
|
74
|
+
this.message = e && e.message ? e.message : '读取 Pi 配置失败';
|
|
75
|
+
this.messageType = 'error';
|
|
76
|
+
} finally {
|
|
77
|
+
this.piProviderLoading = false;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
async savePiProvider() {
|
|
81
|
+
this.piSaving = true;
|
|
82
|
+
try {
|
|
83
|
+
const providerId = this.editingPiProvider.id;
|
|
84
|
+
const values = this.editingPiProvider.form;
|
|
85
|
+
const extras = this.editingPiProvider.extras || {};
|
|
86
|
+
const provider = this.piProviders[providerId] || {};
|
|
87
|
+
|
|
88
|
+
const merged = {
|
|
89
|
+
...extras,
|
|
90
|
+
baseUrl: values.baseUrl,
|
|
91
|
+
api: values.api,
|
|
92
|
+
apiKey: values.apiKey || '',
|
|
93
|
+
models: (Array.isArray(values.models) ? values.models : []).map((model) => ({
|
|
94
|
+
...model,
|
|
95
|
+
id: model.id || '',
|
|
96
|
+
name: model.id || ''
|
|
97
|
+
}))
|
|
98
|
+
};
|
|
99
|
+
if (isPiPlainObject(provider.headers)) merged.headers = provider.headers;
|
|
100
|
+
if (typeof provider.title === 'string') merged.title = provider.title;
|
|
101
|
+
else if (typeof provider.name === 'string') merged.name = provider.name;
|
|
102
|
+
|
|
103
|
+
await this.persistPiProvider(providerId, merged);
|
|
104
|
+
this.piProviders = { ...this.piProviders, [providerId]: merged };
|
|
105
|
+
this.piProviderIds = Object.keys(this.piProviders).sort();
|
|
106
|
+
this.resetPiProviderEditing();
|
|
107
|
+
this.message = '配置已保存';
|
|
108
|
+
this.messageType = 'success';
|
|
109
|
+
} catch (e) {
|
|
110
|
+
this.message = e && e.message ? e.message : '保存 Pi 配置失败';
|
|
111
|
+
this.messageType = 'error';
|
|
112
|
+
} finally {
|
|
113
|
+
this.piSaving = false;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
async addPiProviderFromModal() {
|
|
117
|
+
let createdId = null;
|
|
118
|
+
this.piSaving = true;
|
|
119
|
+
try {
|
|
120
|
+
const baseUrl = (this.addingPiProviderBaseUrl || '').trim();
|
|
121
|
+
if (!baseUrl) throw new Error('Base URL 不能为空');
|
|
122
|
+
const providerId = this.derivePiProviderId(baseUrl);
|
|
123
|
+
const modelId = (this.addingPiProviderModel || '').trim();
|
|
124
|
+
const values = {
|
|
125
|
+
baseUrl,
|
|
126
|
+
api: this.addingPiProviderApi || 'openai-completions',
|
|
127
|
+
apiKey: (this.addingPiProviderApiKey || '').trim(),
|
|
128
|
+
models: modelId ? [{
|
|
129
|
+
id: modelId,
|
|
130
|
+
name: modelId,
|
|
131
|
+
reasoning: false,
|
|
132
|
+
contextWindow: 128000,
|
|
133
|
+
maxTokens: 32000,
|
|
134
|
+
input: ''
|
|
135
|
+
}] : []
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
await this.persistPiProvider(providerId, values);
|
|
139
|
+
this.piProviders = { ...this.piProviders, [providerId]: values };
|
|
140
|
+
this.piProviderIds = Object.keys(this.piProviders).sort();
|
|
141
|
+
this.cancelAddPiProviderModal();
|
|
142
|
+
this.message = '供应商已添加';
|
|
143
|
+
this.messageType = 'success';
|
|
144
|
+
createdId = providerId;
|
|
145
|
+
} catch (e) {
|
|
146
|
+
this.message = e && e.message ? e.message : '添加 Pi 供应商失败';
|
|
147
|
+
this.messageType = 'error';
|
|
148
|
+
} finally {
|
|
149
|
+
this.piSaving = false;
|
|
150
|
+
if (createdId) this.openEditPiProvider(createdId);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
derivePiProviderId(baseUrl) {
|
|
154
|
+
let base = '';
|
|
155
|
+
try {
|
|
156
|
+
base = new URL(baseUrl).hostname.replace(/^www\./, '');
|
|
157
|
+
} catch (_) {
|
|
158
|
+
base = '';
|
|
159
|
+
}
|
|
160
|
+
const candidate = (base || 'pi-provider').replace(/[^a-zA-Z0-9-]/g, '-')
|
|
161
|
+
.replace(/-+/g, '-').replace(/^-|-$/g, '') || 'pi-provider';
|
|
162
|
+
if (!this.piProviders[candidate]) return candidate;
|
|
163
|
+
let suffix = 2;
|
|
164
|
+
while (this.piProviders[`${candidate}-${suffix}`]) suffix += 1;
|
|
165
|
+
return `${candidate}-${suffix}`;
|
|
166
|
+
},
|
|
167
|
+
startAddPiProvider() {
|
|
168
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
169
|
+
this.addingPiProviderId = '';
|
|
170
|
+
this.addingPiProviderName = '';
|
|
171
|
+
this.addingPiProviderBaseUrl = '';
|
|
172
|
+
this.addingPiProviderApi = 'openai-completions';
|
|
173
|
+
this.addingPiProviderApiKey = '';
|
|
174
|
+
this.addingPiProviderModel = '';
|
|
175
|
+
this.addingPiRemoteModels = [];
|
|
176
|
+
this.addingPiRemoteLoading = false;
|
|
177
|
+
this.addingPiRemoteError = '';
|
|
178
|
+
this.showAddPiProviderModal = true;
|
|
179
|
+
this.editingPiProvider = null;
|
|
180
|
+
},
|
|
181
|
+
cancelAddPiProviderModal() {
|
|
182
|
+
this.showAddPiProviderModal = false;
|
|
183
|
+
this.addingPiProviderId = '';
|
|
184
|
+
this.addingPiProviderName = '';
|
|
185
|
+
this.addingPiProviderBaseUrl = '';
|
|
186
|
+
this.addingPiProviderApi = '';
|
|
187
|
+
this.addingPiProviderApiKey = '';
|
|
188
|
+
this.addingPiProviderModel = '';
|
|
189
|
+
this.addingPiRemoteModels = [];
|
|
190
|
+
this.addingPiRemoteLoading = false;
|
|
191
|
+
this.addingPiRemoteError = '';
|
|
192
|
+
},
|
|
193
|
+
async fetchAddingPiRemoteModels() {
|
|
194
|
+
const baseUrl = (this.addingPiProviderBaseUrl || '').trim();
|
|
195
|
+
if (!baseUrl || !/^https?:\/\//i.test(baseUrl)) {
|
|
196
|
+
this.addingPiRemoteModels = [];
|
|
197
|
+
this.addingPiRemoteError = '';
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.addingPiRemoteLoading = true;
|
|
201
|
+
this.addingPiRemoteError = '';
|
|
202
|
+
try {
|
|
203
|
+
const result = await apiClient('fetch-pi-remote-models', {
|
|
204
|
+
baseUrl,
|
|
205
|
+
apiKey: (this.addingPiProviderApiKey || '').trim()
|
|
206
|
+
});
|
|
207
|
+
if (!this.showAddPiProviderModal) return;
|
|
208
|
+
if (result && Array.isArray(result.models) && result.models.length > 0) {
|
|
209
|
+
this.addingPiRemoteModels = result.models;
|
|
210
|
+
} else {
|
|
211
|
+
this.addingPiRemoteModels = [];
|
|
212
|
+
this.addingPiRemoteError = (result && result.error) || '未获取到可用模型';
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {
|
|
215
|
+
if (!this.showAddPiProviderModal) return;
|
|
216
|
+
this.addingPiRemoteModels = [];
|
|
217
|
+
this.addingPiRemoteError = e && e.message ? e.message : '模型列表获取失败';
|
|
218
|
+
} finally {
|
|
219
|
+
if (this.showAddPiProviderModal) this.addingPiRemoteLoading = false;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
onAddingPiEndpointChange() {
|
|
223
|
+
if (this.addingPiRemoteDebounce) clearTimeout(this.addingPiRemoteDebounce);
|
|
224
|
+
this.addingPiRemoteDebounce = setTimeout(() => {
|
|
225
|
+
this.addingPiRemoteDebounce = null;
|
|
226
|
+
this.fetchAddingPiRemoteModels();
|
|
227
|
+
}, 600);
|
|
228
|
+
},
|
|
229
|
+
piFilteredAddingRemoteModels() {
|
|
230
|
+
const query = (this.addingPiProviderModel || '').trim().toLowerCase();
|
|
231
|
+
if (!query) return [];
|
|
232
|
+
return this.addingPiRemoteModels.filter((id) => id.toLowerCase().includes(query)).slice(0, 50);
|
|
233
|
+
},
|
|
234
|
+
openEditPiProvider(providerId) {
|
|
235
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
236
|
+
this.piRemoteModels = [];
|
|
237
|
+
this.piRemoteModelError = '';
|
|
238
|
+
this.piModelSearch = '';
|
|
239
|
+
const provider = this.piProviders[providerId] || {};
|
|
240
|
+
const knownKeys = ['id', 'name', 'title', 'baseUrl', 'api', 'apiKey', 'models', 'headers', 'configJson'];
|
|
241
|
+
const extras = {};
|
|
242
|
+
Object.keys(provider).forEach((key) => {
|
|
243
|
+
if (!knownKeys.includes(key)) extras[key] = provider[key];
|
|
244
|
+
});
|
|
245
|
+
const form = {
|
|
246
|
+
id: provider.id || providerId,
|
|
247
|
+
baseUrl: provider.baseUrl || '',
|
|
248
|
+
api: provider.api || '',
|
|
249
|
+
apiKey: provider.apiKey || '',
|
|
250
|
+
configJsonDraft: JSON.stringify(extras, null, 2),
|
|
251
|
+
models: Array.isArray(provider.models) && provider.models.length
|
|
252
|
+
? provider.models.map((model) => ({
|
|
253
|
+
id: model?.id || '',
|
|
254
|
+
name: model?.name || '',
|
|
255
|
+
reasoning: model?.reasoning || '',
|
|
256
|
+
contextWindow: model?.contextWindow ?? model?.context_window ?? '',
|
|
257
|
+
maxTokens: model?.maxTokens ?? model?.max_tokens ?? '',
|
|
258
|
+
input: model?.input || ''
|
|
259
|
+
}))
|
|
260
|
+
: []
|
|
261
|
+
};
|
|
262
|
+
this.editingPiProvider = {
|
|
263
|
+
id: providerId,
|
|
264
|
+
form,
|
|
265
|
+
extras,
|
|
266
|
+
original: provider
|
|
267
|
+
};
|
|
268
|
+
this.showAddPiProviderModal = false;
|
|
269
|
+
this.fetchPiRemoteModels();
|
|
270
|
+
},
|
|
271
|
+
resetPiProviderEditing() {
|
|
272
|
+
this.editingPiProvider = null;
|
|
273
|
+
this.piRemoteModels = [];
|
|
274
|
+
this.piRemoteModelError = '';
|
|
275
|
+
this.piModelSearch = '';
|
|
276
|
+
},
|
|
277
|
+
removePiProviderModel(index) {
|
|
278
|
+
if (!this.editingPiProvider) return;
|
|
279
|
+
const list = this.editingPiProvider.form.models;
|
|
280
|
+
if (Array.isArray(list)) list.splice(index, 1);
|
|
281
|
+
},
|
|
282
|
+
async fetchPiRemoteModels() {
|
|
283
|
+
const provider = this.editingPiProvider;
|
|
284
|
+
if (!provider) return;
|
|
285
|
+
this.piRemoteModelsLoading = true;
|
|
286
|
+
this.piRemoteModelError = '';
|
|
287
|
+
try {
|
|
288
|
+
const result = await apiClient('fetch-pi-remote-models', {
|
|
289
|
+
baseUrl: provider.form.baseUrl || '',
|
|
290
|
+
apiKey: provider.form.apiKey || ''
|
|
291
|
+
});
|
|
292
|
+
if (this.editingPiProvider !== provider) return;
|
|
293
|
+
if (result && Array.isArray(result.models) && result.models.length > 0) {
|
|
294
|
+
this.piRemoteModels = result.models;
|
|
295
|
+
} else {
|
|
296
|
+
this.piRemoteModels = [];
|
|
297
|
+
this.piRemoteModelError = (result && result.error) || '未获取到可用模型';
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
if (this.editingPiProvider !== provider) return;
|
|
301
|
+
this.piRemoteModels = [];
|
|
302
|
+
this.piRemoteModelError = e && e.message ? e.message : '模型列表获取失败';
|
|
303
|
+
} finally {
|
|
304
|
+
if (this.editingPiProvider === provider) this.piRemoteModelsLoading = false;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
piFilteredRemoteModels() {
|
|
308
|
+
const query = (this.piModelSearch || '').trim().toLowerCase();
|
|
309
|
+
if (!query) return [];
|
|
310
|
+
return this.piRemoteModels.filter((id) => id.toLowerCase().includes(query)).slice(0, 50);
|
|
311
|
+
},
|
|
312
|
+
addPiRemoteModel(modelId) {
|
|
313
|
+
if (!this.isToolConfigWriteAllowed('pi') || !this.editingPiProvider) return;
|
|
314
|
+
const trimmed = typeof modelId === 'string' ? modelId.trim() : '';
|
|
315
|
+
if (!trimmed) return;
|
|
316
|
+
const list = this.editingPiProvider.form.models;
|
|
317
|
+
if (!Array.isArray(list)) return;
|
|
318
|
+
list.splice(0, list.length, {
|
|
319
|
+
id: trimmed,
|
|
320
|
+
name: trimmed,
|
|
321
|
+
reasoning: false,
|
|
322
|
+
contextWindow: 128000,
|
|
323
|
+
maxTokens: 32000,
|
|
324
|
+
input: ''
|
|
325
|
+
});
|
|
326
|
+
this.piModelSearch = '';
|
|
327
|
+
},
|
|
328
|
+
async confirmDeletePiProvider() {
|
|
329
|
+
if (!this.editingPiProvider) return;
|
|
330
|
+
const providerId = this.editingPiProvider.id;
|
|
331
|
+
const confirmed = await this.requestConfirmDialog({
|
|
332
|
+
title: this.t('pi.providers.deleteTitle'),
|
|
333
|
+
message: this.t('pi.providers.deleteMessage', { name: this.piProviderName(providerId) }),
|
|
334
|
+
confirmText: this.t('confirm.ok'),
|
|
335
|
+
cancelText: this.t('confirm.cancel'),
|
|
336
|
+
danger: true
|
|
337
|
+
});
|
|
338
|
+
if (!confirmed) return;
|
|
339
|
+
await this.removePiProvider(providerId);
|
|
340
|
+
const updated = { ...this.piProviders };
|
|
341
|
+
delete updated[providerId];
|
|
342
|
+
this.piProviders = updated;
|
|
343
|
+
this.piProviderIds = Object.keys(this.piProviders).sort();
|
|
344
|
+
this.resetPiProviderEditing();
|
|
345
|
+
this.message = '供应商已删除';
|
|
346
|
+
this.messageType = 'success';
|
|
347
|
+
},
|
|
348
|
+
piProviderName(providerId) {
|
|
349
|
+
const provider = this.piProviders[providerId] || {};
|
|
350
|
+
if (provider.title || provider.name) return provider.title || provider.name;
|
|
351
|
+
const urlTitle = this.piProviderUrlTitle(provider);
|
|
352
|
+
return urlTitle || providerId;
|
|
353
|
+
},
|
|
354
|
+
piProviderUrlTitle(provider) {
|
|
355
|
+
const baseUrl = provider && provider.baseUrl ? String(provider.baseUrl) : '';
|
|
356
|
+
if (!baseUrl) return '';
|
|
357
|
+
try {
|
|
358
|
+
const hostname = new URL(baseUrl).hostname;
|
|
359
|
+
return hostname.replace(/^www\./, '');
|
|
360
|
+
} catch (e) {
|
|
361
|
+
return '';
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
piProviderSummary(providerId) {
|
|
365
|
+
const provider = this.piProviders[providerId] || {};
|
|
366
|
+
const baseUrl = provider.baseUrl || '';
|
|
367
|
+
const api = provider.api || '';
|
|
368
|
+
return [baseUrl, api].filter(Boolean).join(' • ') || 'Piper provider';
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
@@ -357,6 +357,42 @@ export function createSessionActionMethods(options = {}) {
|
|
|
357
357
|
}
|
|
358
358
|
},
|
|
359
359
|
|
|
360
|
+
normalizeConfigModeVisibility(value) {
|
|
361
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
362
|
+
const result = {};
|
|
363
|
+
for (const mode of ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi']) {
|
|
364
|
+
result[mode] = typeof source[mode] === 'boolean' ? source[mode] : true;
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
isConfigModeVisible(mode) {
|
|
370
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
371
|
+
if (!this.configModeVisibility) return true;
|
|
372
|
+
return this.configModeVisibility[normalizedMode] !== false;
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
setConfigModeVisibility(mode, visible) {
|
|
376
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
377
|
+
if (!normalizedMode || !['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].includes(normalizedMode)) return;
|
|
378
|
+
const visibility = this.configModeVisibility || { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true, pi: true };
|
|
379
|
+
const next = { ...visibility, [normalizedMode]: !!visible };
|
|
380
|
+
const visibleCount = Object.values(next).filter(Boolean).length;
|
|
381
|
+
if (visibleCount === 0) {
|
|
382
|
+
this.showMessage(typeof this.t === 'function' ? this.t('settings.configTabs.minRequired') : 'At least one tab must remain visible', 'info');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
this.configModeVisibility = next;
|
|
386
|
+
if (typeof this.persistWebUiPreferences === 'function') {
|
|
387
|
+
this.persistWebUiPreferences({ configModeVisibility: next });
|
|
388
|
+
}
|
|
389
|
+
if (this.mainTab === 'config' && this.configMode === normalizedMode && !next[normalizedMode]) {
|
|
390
|
+
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].find(m => next[m] !== false);
|
|
391
|
+
if (fallback) this.switchConfigMode(fallback);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
|
|
360
396
|
getShareCommandPrefixInvocation() {
|
|
361
397
|
const prefix = this.normalizeShareCommandPrefix(this.shareCommandPrefix);
|
|
362
398
|
return prefix === 'codexmate' ? 'codexmate' : 'npm start --';
|
|
@@ -393,6 +429,27 @@ export function createSessionActionMethods(options = {}) {
|
|
|
393
429
|
}
|
|
394
430
|
},
|
|
395
431
|
|
|
432
|
+
async copySessionMessage(msg) {
|
|
433
|
+
const text = msg && typeof msg.text === 'string' ? msg.text : '';
|
|
434
|
+
if (!text) {
|
|
435
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.empty') : 'Nothing to copy', 'info');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const ok = this.fallbackCopyText(text);
|
|
439
|
+
if (ok) {
|
|
440
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
445
|
+
await navigator.clipboard.writeText(text);
|
|
446
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
} catch (_) {}
|
|
450
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.fail') : 'Copy failed', 'error');
|
|
451
|
+
},
|
|
452
|
+
|
|
396
453
|
copyAgentsContent() {
|
|
397
454
|
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
398
455
|
if (!text) {
|
|
@@ -145,7 +145,8 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
145
145
|
claude: statusRes.toolConfigPermissions.claude === true,
|
|
146
146
|
opencode: statusRes.toolConfigPermissions.opencode === true,
|
|
147
147
|
kilocode: statusRes.toolConfigPermissions.kilocode === true,
|
|
148
|
-
openclaw: statusRes.toolConfigPermissions.openclaw === true
|
|
148
|
+
openclaw: statusRes.toolConfigPermissions.openclaw === true,
|
|
149
|
+
pi: statusRes.toolConfigPermissions.pi === true
|
|
149
150
|
};
|
|
150
151
|
}
|
|
151
152
|
this.providersList = listRes.providers;
|
|
@@ -223,6 +223,9 @@ export function createSystemPromptMethods(options = {}) {
|
|
|
223
223
|
this.showMessage(res.error, 'error');
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
|
+
if (typeof res.historyBucket === 'string' && res.historyBucket) {
|
|
227
|
+
this.sysHistoryBucket = res.historyBucket;
|
|
228
|
+
}
|
|
226
229
|
this.showMessage(this.t('sysPrompt.toast.saved'), 'success');
|
|
227
230
|
await this.loadSystemPrompt();
|
|
228
231
|
} catch (e) {
|
|
@@ -289,6 +292,84 @@ export function createSystemPromptMethods(options = {}) {
|
|
|
289
292
|
return { text: this.t('sysPrompt.hint.twoStepSave'), warn: false };
|
|
290
293
|
}
|
|
291
294
|
return null;
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
resolveSysHistoryBucket() {
|
|
298
|
+
if (typeof this.sysHistoryBucket === 'string' && this.sysHistoryBucket) {
|
|
299
|
+
return this.sysHistoryBucket;
|
|
300
|
+
}
|
|
301
|
+
const scope = this.sysPromptScope || 'global';
|
|
302
|
+
return 'system_' + scope + '_global';
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
async openSysHistory() {
|
|
306
|
+
if (this.sysHistoryLoading) return;
|
|
307
|
+
const bucket = this.resolveSysHistoryBucket();
|
|
308
|
+
this.sysHistoryVisible = true;
|
|
309
|
+
this.sysHistoryError = '';
|
|
310
|
+
await this.loadSysHistory(bucket);
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
async loadSysHistory(bucketArg) {
|
|
314
|
+
if (this.sysHistoryLoading) return;
|
|
315
|
+
const bucket = typeof bucketArg === 'string' && bucketArg.trim()
|
|
316
|
+
? bucketArg.trim()
|
|
317
|
+
: this.resolveSysHistoryBucket();
|
|
318
|
+
this.sysHistoryBucket = bucket;
|
|
319
|
+
this.sysHistoryLoading = true;
|
|
320
|
+
this.sysHistoryError = '';
|
|
321
|
+
this.sysHistoryItems = [];
|
|
322
|
+
this.sysHistoryPreviewId = '';
|
|
323
|
+
this.sysHistoryPreviewContent = '';
|
|
324
|
+
try {
|
|
325
|
+
const res = await api('list-prompt-history', { bucket });
|
|
326
|
+
if (res && res.error) {
|
|
327
|
+
this.sysHistoryError = res.error;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.sysHistoryItems = Array.isArray(res) ? res : [];
|
|
331
|
+
} catch (e) {
|
|
332
|
+
this.sysHistoryError = this.t('toast.load.fail');
|
|
333
|
+
} finally {
|
|
334
|
+
this.sysHistoryLoading = false;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
closeSysHistory() {
|
|
339
|
+
this.sysHistoryVisible = false;
|
|
340
|
+
this.sysHistoryPreviewId = '';
|
|
341
|
+
this.sysHistoryPreviewContent = '';
|
|
342
|
+
this.sysHistoryError = '';
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
async viewSysHistoryItem(item) {
|
|
346
|
+
if (!item || !item.id) return;
|
|
347
|
+
if (this.sysHistoryLoading) return;
|
|
348
|
+
if (this.sysHistoryPreviewId === item.id && this.sysHistoryPreviewContent) return;
|
|
349
|
+
this.sysHistoryPreviewId = item.id;
|
|
350
|
+
this.sysHistoryPreviewContent = '';
|
|
351
|
+
try {
|
|
352
|
+
const res = await api('get-prompt-history', { bucket: this.sysHistoryBucket, id: item.id });
|
|
353
|
+
if (res && res.error) {
|
|
354
|
+
this.sysHistoryError = res.error;
|
|
355
|
+
this.sysHistoryPreviewId = '';
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
this.sysHistoryPreviewContent = typeof res.content === 'string' ? res.content : '';
|
|
359
|
+
} catch (e) {
|
|
360
|
+
this.sysHistoryError = this.t('toast.load.fail');
|
|
361
|
+
this.sysHistoryPreviewId = '';
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
applySysHistoryToEditor() {
|
|
366
|
+
if (!this.sysHistoryPreviewContent) return;
|
|
367
|
+
if (this.sysPromptSaving || this.sysPromptDiffVisible) return;
|
|
368
|
+
this.sysPromptContent = this.sysHistoryPreviewContent;
|
|
369
|
+
this.sysHistoryVisible = false;
|
|
370
|
+
this.sysHistoryPreviewId = '';
|
|
371
|
+
this.sysHistoryPreviewContent = '';
|
|
372
|
+
this.showMessage(this.t('toast.history.restored'), 'success');
|
|
292
373
|
}
|
|
293
374
|
};
|
|
294
375
|
}
|
|
@@ -3,7 +3,7 @@ export function createToolConfigPermissionMethods(options = {}) {
|
|
|
3
3
|
|
|
4
4
|
function normalizeTarget(value) {
|
|
5
5
|
const target = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
6
|
-
return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' || target === 'openclaw' ? target : '';
|
|
6
|
+
return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' || target === 'openclaw' || target === 'pi' ? target : '';
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function normalizePermissions(value) {
|
|
@@ -13,7 +13,8 @@ export function createToolConfigPermissionMethods(options = {}) {
|
|
|
13
13
|
claude: source.claude === true,
|
|
14
14
|
opencode: source.opencode === true,
|
|
15
15
|
kilocode: source.kilocode === true,
|
|
16
|
-
openclaw: source.openclaw === true
|
|
16
|
+
openclaw: source.openclaw === true,
|
|
17
|
+
pi: source.pi === true
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -347,6 +347,9 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
347
347
|
? source.projectClaudeMdPath
|
|
348
348
|
: (typeof this.projectClaudeMdPath === 'string' ? this.projectClaudeMdPath : ''),
|
|
349
349
|
sidebarCollapsed: normalizeBoolean(hasOwn(source, 'sidebarCollapsed') ? source.sidebarCollapsed : this.sidebarCollapsed, false),
|
|
350
|
+
configModeVisibility: typeof this.normalizeConfigModeVisibility === 'function'
|
|
351
|
+
? this.normalizeConfigModeVisibility(hasOwn(source, 'configModeVisibility') ? source.configModeVisibility : this.configModeVisibility)
|
|
352
|
+
: { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true },
|
|
350
353
|
starPrompted: normalizeBoolean(hasOwn(source, 'starPrompted') ? source.starPrompted : this.starPrompted, false),
|
|
351
354
|
sessionLoadNativeDialog: normalizeBoolean(hasOwn(source, 'sessionLoadNativeDialog') ? source.sessionLoadNativeDialog : this.sessionLoadNativeDialog, false),
|
|
352
355
|
language: typeof source.language === 'string'
|
|
@@ -412,6 +415,9 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
412
415
|
if (hasOwn(source, 'sidebarCollapsed')) {
|
|
413
416
|
this.sidebarCollapsed = normalizeBoolean(source.sidebarCollapsed, false);
|
|
414
417
|
}
|
|
418
|
+
if (hasOwn(source, 'configModeVisibility') && typeof this.normalizeConfigModeVisibility === 'function') {
|
|
419
|
+
this.configModeVisibility = this.normalizeConfigModeVisibility(source.configModeVisibility);
|
|
420
|
+
}
|
|
415
421
|
if (hasOwn(source, 'starPrompted')) {
|
|
416
422
|
this.starPrompted = normalizeBoolean(source.starPrompted, false);
|
|
417
423
|
}
|
|
@@ -451,7 +457,8 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
451
457
|
claude: source.toolConfigPermissions.claude === true,
|
|
452
458
|
opencode: source.toolConfigPermissions.opencode === true,
|
|
453
459
|
kilocode: source.toolConfigPermissions.kilocode === true,
|
|
454
|
-
openclaw: source.toolConfigPermissions.openclaw === true
|
|
460
|
+
openclaw: source.toolConfigPermissions.openclaw === true,
|
|
461
|
+
pi: source.toolConfigPermissions.pi === true
|
|
455
462
|
};
|
|
456
463
|
}
|
|
457
464
|
if (Array.isArray(source.deletedClaudeSettingsImports)) {
|