codexmate 0.1.12 → 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 -3
- package/README.vi.md +4 -3
- package/README.zh.md +5 -3
- package/cli.js +139 -4
- package/package.json +1 -1
- package/web-ui/app.js +81 -5
- package/web-ui/index.html +2 -1
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.navigation.mjs +1 -1
- package/web-ui/modules/app.methods.pi-config.mjs +371 -0
- package/web-ui/modules/app.methods.session-actions.mjs +4 -4
- package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +2 -1
- package/web-ui/modules/config-mode.computed.mjs +2 -1
- package/web-ui/modules/i18n/locales/en.mjs +43 -0
- package/web-ui/modules/i18n/locales/ja.mjs +43 -0
- package/web-ui/modules/i18n/locales/vi.mjs +43 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +43 -0
- package/web-ui/modules/i18n/locales/zh.mjs +43 -0
- package/web-ui/partials/index/layout-header.html +15 -0
- package/web-ui/partials/index/panel-config-pi.html +171 -0
- package/web-ui/partials/index/panel-settings.html +7 -0
- package/web-ui/res/web-ui-render.precompiled.js +567 -49
- package/web-ui/styles/settings-panel.css +7 -0
|
@@ -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
|
+
}
|
|
@@ -360,7 +360,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
360
360
|
normalizeConfigModeVisibility(value) {
|
|
361
361
|
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
362
362
|
const result = {};
|
|
363
|
-
for (const mode of ['codex', 'claude', 'openclaw', 'opencode', 'kilocode']) {
|
|
363
|
+
for (const mode of ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi']) {
|
|
364
364
|
result[mode] = typeof source[mode] === 'boolean' ? source[mode] : true;
|
|
365
365
|
}
|
|
366
366
|
return result;
|
|
@@ -374,8 +374,8 @@ export function createSessionActionMethods(options = {}) {
|
|
|
374
374
|
|
|
375
375
|
setConfigModeVisibility(mode, visible) {
|
|
376
376
|
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
377
|
-
if (!normalizedMode || !['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalizedMode)) return;
|
|
378
|
-
const visibility = this.configModeVisibility || { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true };
|
|
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
379
|
const next = { ...visibility, [normalizedMode]: !!visible };
|
|
380
380
|
const visibleCount = Object.values(next).filter(Boolean).length;
|
|
381
381
|
if (visibleCount === 0) {
|
|
@@ -387,7 +387,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
387
387
|
this.persistWebUiPreferences({ configModeVisibility: next });
|
|
388
388
|
}
|
|
389
389
|
if (this.mainTab === 'config' && this.configMode === normalizedMode && !next[normalizedMode]) {
|
|
390
|
-
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].find(m => next[m] !== false);
|
|
390
|
+
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].find(m => next[m] !== false);
|
|
391
391
|
if (fallback) this.switchConfigMode(fallback);
|
|
392
392
|
}
|
|
393
393
|
},
|
|
@@ -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;
|
|
@@ -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
|
|
|
@@ -457,7 +457,8 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
457
457
|
claude: source.toolConfigPermissions.claude === true,
|
|
458
458
|
opencode: source.toolConfigPermissions.opencode === true,
|
|
459
459
|
kilocode: source.toolConfigPermissions.kilocode === true,
|
|
460
|
-
openclaw: source.toolConfigPermissions.openclaw === true
|
|
460
|
+
openclaw: source.toolConfigPermissions.openclaw === true,
|
|
461
|
+
pi: source.toolConfigPermissions.pi === true
|
|
461
462
|
};
|
|
462
463
|
}
|
|
463
464
|
if (Array.isArray(source.deletedClaudeSettingsImports)) {
|
|
@@ -13,6 +13,7 @@ const en = Object.freeze({
|
|
|
13
13
|
'common.paste': 'Paste',
|
|
14
14
|
'common.edit': 'Edit',
|
|
15
15
|
'common.install': 'Install',
|
|
16
|
+
'common.optional': 'optional',
|
|
16
17
|
'common.update': 'Update',
|
|
17
18
|
'common.uninstall': 'Uninstall',
|
|
18
19
|
'common.official': 'Official',
|
|
@@ -135,6 +136,7 @@ const en = Object.freeze({
|
|
|
135
136
|
'tab.config.claude': 'Claude',
|
|
136
137
|
'tab.config.openclaw': 'OpenClaw',
|
|
137
138
|
'tab.config.opencode': 'OpenCode',
|
|
139
|
+
'tab.config.pi': 'Pi',
|
|
138
140
|
'tab.sessions': 'Sessions',
|
|
139
141
|
'tab.usage': 'Usage',
|
|
140
142
|
'tab.market': 'Skills',
|
|
@@ -1319,6 +1321,47 @@ const en = Object.freeze({
|
|
|
1319
1321
|
'toolConfig.openclaw.confirmMessage': 'When enabled, actions in the OpenClaw tab can write openclaw.json and OpenClaw workspace files (AGENTS.md, SOUL.md, etc.).',
|
|
1320
1322
|
'toolConfig.openclaw.lockedTitle': 'OpenClaw is read-only',
|
|
1321
1323
|
'toolConfig.openclaw.lockedDesc': 'OpenClaw config is not written here. To apply configs or edit workspace files, enable write access on this tab first.',
|
|
1324
|
+
'side.config.pi': 'Pi',
|
|
1325
|
+
'side.config.pi.meta': 'Config',
|
|
1326
|
+
'toolConfig.pi.title': 'Pi config writes',
|
|
1327
|
+
'toolConfig.pi.desc': 'Read-only by default; enable to sync Pi config.',
|
|
1328
|
+
'toolConfig.pi.lockedTitle': 'Read-only',
|
|
1329
|
+
'toolConfig.pi.lockedDesc': 'Enable write access to sync Pi config.',
|
|
1330
|
+
'toolConfig.pi.confirmMessage': 'When enabled, actions in the Pi tab can write the Pi config file (~/.pi/config.json or ./.pi/config.json).',
|
|
1331
|
+
'pi.status.readOnly': 'Read-only',
|
|
1332
|
+
'pi.config.openGlobal': 'Open global config',
|
|
1333
|
+
'pi.providers.title': 'Pi Providers',
|
|
1334
|
+
'pi.providers.subtitle': 'Manage Piper local providers and models',
|
|
1335
|
+
'pi.providers.empty': 'No providers found. Click Add to start.',
|
|
1336
|
+
'pi.providers.targetFile': 'Active Pi config file: {path}',
|
|
1337
|
+
'pi.providers.editorTitle': 'Edit Provider',
|
|
1338
|
+
'pi.providers.add': 'Add Provider',
|
|
1339
|
+
'pi.providers.active': 'Active',
|
|
1340
|
+
'pi.providers.setActive': 'Set Active',
|
|
1341
|
+
'pi.providers.deleteTitle': 'Delete Pi Provider',
|
|
1342
|
+
'pi.providers.deleteMessage': 'Delete provider "{name}"?',
|
|
1343
|
+
'pi.providers.fields.name': 'Name',
|
|
1344
|
+
'pi.providers.fields.baseUrl': 'Base URL',
|
|
1345
|
+
'pi.providers.fields.api': 'API',
|
|
1346
|
+
'pi.providers.fields.apiKey': 'API Key',
|
|
1347
|
+
'pi.providers.fields.models': 'Models',
|
|
1348
|
+
'pi.providers.fields.configJson': 'Config JSON',
|
|
1349
|
+
'pi.providers.models.remove': 'Remove',
|
|
1350
|
+
'pi.providers.models.search': 'Search available models…',
|
|
1351
|
+
'pi.providers.models.remoteCount': '{count} models available, click to add',
|
|
1352
|
+
'pi.providers.models.remoteLoading': 'Fetching remote model list…',
|
|
1353
|
+
'pi.providers.models.remoteFailed': 'You can still type model IDs manually.',
|
|
1354
|
+
'pi.providers.models.noMatch': 'No matching models',
|
|
1355
|
+
'pi.providers.delete': 'Delete Provider',
|
|
1356
|
+
'pi.providers.addModal.title': 'Add Provider',
|
|
1357
|
+
'pi.providers.addModal.subtitle': 'Create a new Piper provider',
|
|
1358
|
+
'pi.providers.addModal.fields.baseUrl': 'Base URL',
|
|
1359
|
+
'pi.providers.addModal.fields.apiKey': 'API Key',
|
|
1360
|
+
'pi.providers.addModal.api.openaiCompletions': 'OpenAI Chat Completions (most compatible)',
|
|
1361
|
+
'pi.providers.addModal.api.openaiResponses': 'OpenAI Responses API',
|
|
1362
|
+
'pi.providers.addModal.api.anthropicMessages': 'Anthropic Messages API',
|
|
1363
|
+
'pi.providers.addModal.api.googleGenerativeAi': 'Google Generative AI',
|
|
1364
|
+
'pi.providers.addModal.fields.api': 'API Format',
|
|
1322
1365
|
'kilocode.providerModel.title': 'KiloCode provider / model',
|
|
1323
1366
|
'kilocode.connection.title': 'KiloCode connection',
|
|
1324
1367
|
'kilocode.targetFile': 'Active KiloCode config: {path} · {status}',
|
|
@@ -14,6 +14,7 @@ const ja = Object.freeze({
|
|
|
14
14
|
'common.paste': 'ペースト',
|
|
15
15
|
'common.edit': '編集',
|
|
16
16
|
'common.install': 'インストール',
|
|
17
|
+
'common.optional': '任意',
|
|
17
18
|
'common.update': 'アップグレード',
|
|
18
19
|
'common.uninstall': 'アンインストール',
|
|
19
20
|
'common.official': '公式',
|
|
@@ -136,6 +137,7 @@ const ja = Object.freeze({
|
|
|
136
137
|
'tab.config.claude': 'Claude',
|
|
137
138
|
'tab.config.openclaw': 'OpenClaw',
|
|
138
139
|
'tab.config.opencode': 'OpenCode',
|
|
140
|
+
'tab.config.pi': 'Pi',
|
|
139
141
|
'tab.sessions': 'セッション',
|
|
140
142
|
'tab.usage': '使用量',
|
|
141
143
|
'tab.market': 'Skills',
|
|
@@ -1320,6 +1322,47 @@ const ja = Object.freeze({
|
|
|
1320
1322
|
'toolConfig.openclaw.confirmMessage': '有効化すると、OpenClaw タブ内の操作が openclaw.json およびワークスペースファイル(AGENTS.md、SOUL.md など)に書き込みできます。',
|
|
1321
1323
|
'toolConfig.openclaw.lockedTitle': 'OpenClaw は読み取り専用です',
|
|
1322
1324
|
'toolConfig.openclaw.lockedDesc': 'ここでは OpenClaw 設定を書き込みません。設定を適用またはワークスペースファイルを編集するには、このタブの書き込みスイッチを有効化してください。',
|
|
1325
|
+
'side.config.pi': 'Pi',
|
|
1326
|
+
'side.config.pi.meta': '設定',
|
|
1327
|
+
'toolConfig.pi.title': 'Pi 設定の書き込み',
|
|
1328
|
+
'toolConfig.pi.desc': '既定は読み取り専用。有効化すると Pi 設定を同期できます。',
|
|
1329
|
+
'toolConfig.pi.lockedTitle': '読み取り専用',
|
|
1330
|
+
'toolConfig.pi.lockedDesc': '書き込み権限を有効化すると、Pi 設定を同期できます。',
|
|
1331
|
+
'toolConfig.pi.confirmMessage': '有効化すると、Pi タブ内の操作で ~/.pi/config.json または ./.pi/config.json に書き込むことができます。',
|
|
1332
|
+
'pi.status.readOnly': '読み取り専用',
|
|
1333
|
+
'pi.config.openGlobal': 'グローバル設定を開く',
|
|
1334
|
+
'pi.providers.title': 'Pi プロバイダ',
|
|
1335
|
+
'pi.providers.subtitle': 'Piper ローカルプロバイダとモデルを管理',
|
|
1336
|
+
'pi.providers.empty': 'Piper プロバイダが見つかりません。「追加」をクリックして開始してください。',
|
|
1337
|
+
'pi.providers.targetFile': 'Pi の有効な設定ファイル:{path}',
|
|
1338
|
+
'pi.providers.editorTitle': 'プロバイダを編集',
|
|
1339
|
+
'pi.providers.add': 'プロバイダを追加',
|
|
1340
|
+
'pi.providers.active': '有効',
|
|
1341
|
+
'pi.providers.setActive': '使用中に設定',
|
|
1342
|
+
'pi.providers.deleteTitle': 'Pi 設定を削除',
|
|
1343
|
+
'pi.providers.deleteMessage': 'プロバイダー「{name}」を削除しますか?',
|
|
1344
|
+
'pi.providers.fields.name': '名前',
|
|
1345
|
+
'pi.providers.fields.baseUrl': 'Base URL',
|
|
1346
|
+
'pi.providers.fields.api': 'API 形式',
|
|
1347
|
+
'pi.providers.fields.apiKey': 'API Key',
|
|
1348
|
+
'pi.providers.fields.models': 'モデル',
|
|
1349
|
+
'pi.providers.fields.configJson': '設定 JSON',
|
|
1350
|
+
'pi.providers.models.remove': '削除',
|
|
1351
|
+
'pi.providers.models.search': '利用可能なモデルを検索…',
|
|
1352
|
+
'pi.providers.models.remoteCount': '{count} 個のモデルを取得しました。クリックで追加',
|
|
1353
|
+
'pi.providers.models.remoteLoading': 'リモートモデル一覧を取得中…',
|
|
1354
|
+
'pi.providers.models.remoteFailed': 'モデル ID は手動入力できます。',
|
|
1355
|
+
'pi.providers.models.noMatch': '一致するモデルがありません',
|
|
1356
|
+
'pi.providers.delete': 'プロバイダを削除',
|
|
1357
|
+
'pi.providers.addModal.title': 'プロバイダを追加',
|
|
1358
|
+
'pi.providers.addModal.subtitle': '新しい Piper プロバイダを作成',
|
|
1359
|
+
'pi.providers.addModal.fields.baseUrl': 'Base URL',
|
|
1360
|
+
'pi.providers.addModal.fields.apiKey': 'API Key',
|
|
1361
|
+
'pi.providers.addModal.api.openaiCompletions': 'OpenAI Chat Completions(最も互換性が高い)',
|
|
1362
|
+
'pi.providers.addModal.api.openaiResponses': 'OpenAI Responses API',
|
|
1363
|
+
'pi.providers.addModal.api.anthropicMessages': 'Anthropic Messages API',
|
|
1364
|
+
'pi.providers.addModal.api.googleGenerativeAi': 'Google Generative AI',
|
|
1365
|
+
'pi.providers.addModal.fields.api': 'API 形式',
|
|
1323
1366
|
'kilocode.providerModel.title': 'KiloCode プロバイダー / モデル',
|
|
1324
1367
|
'kilocode.connection.title': 'KiloCode 接続',
|
|
1325
1368
|
'kilocode.targetFile': '有効な KiloCode 設定:{path} · {status}',
|
|
@@ -22,6 +22,7 @@ const vi = Object.freeze({
|
|
|
22
22
|
'common.paste': 'Dán',
|
|
23
23
|
'common.edit': 'Chỉnh sửa',
|
|
24
24
|
'common.install': 'Cài đặt',
|
|
25
|
+
'common.optional': 'không bắt buộc',
|
|
25
26
|
'common.update': 'Cập nhật',
|
|
26
27
|
'common.uninstall': 'Gỡ cài đặt',
|
|
27
28
|
'common.official': 'Chính thức',
|
|
@@ -95,6 +96,7 @@ const vi = Object.freeze({
|
|
|
95
96
|
'tab.docs': 'Tài liệu',
|
|
96
97
|
'tab.config': 'Cấu hình',
|
|
97
98
|
'tab.config.opencode': 'OpenCode',
|
|
99
|
+
'tab.config.pi': 'Pi',
|
|
98
100
|
'tab.sessions': 'Phiên',
|
|
99
101
|
'tab.usage': 'Sử dụng',
|
|
100
102
|
'tab.market': 'Skills',
|
|
@@ -1288,6 +1290,47 @@ const vi = Object.freeze({
|
|
|
1288
1290
|
'toolConfig.openclaw.confirmMessage': 'Khi bật, các thao tác trong tab OpenClaw có thể ghi openclaw.json và các tệp workspace (AGENTS.md, SOUL.md, v.v.).',
|
|
1289
1291
|
'toolConfig.openclaw.lockedTitle': 'OpenClaw đang ở chế độ chỉ đọc',
|
|
1290
1292
|
'toolConfig.openclaw.lockedDesc': 'Không ghi cấu hình OpenClaw ở đây. Để áp dụng cấu hình hoặc sửa tệp workspace, hãy bật quyền ghi trên tab này trước.',
|
|
1293
|
+
'side.config.pi': 'Pi',
|
|
1294
|
+
'side.config.pi.meta': 'Cấu hình',
|
|
1295
|
+
'toolConfig.pi.title': 'Ghi cấu hình Pi',
|
|
1296
|
+
'toolConfig.pi.desc': 'Mặc định chỉ đọc; bật để đồng bộ cấu hình Pi.',
|
|
1297
|
+
'toolConfig.pi.lockedTitle': 'Chế độ chỉ đọc',
|
|
1298
|
+
'toolConfig.pi.lockedDesc': 'Bật quyền ghi để đồng bộ cấu hình Pi.',
|
|
1299
|
+
'toolConfig.pi.confirmMessage': 'Khi bật, các thao tác trong tab Pi có thể ghi file cấu hình Pi (~/.pi/config.json hoặc ./.pi/config.json).',
|
|
1300
|
+
'pi.status.readOnly': 'Chỉ đọc',
|
|
1301
|
+
'pi.config.openGlobal': 'Mở cấu hình toàn cục',
|
|
1302
|
+
'pi.providers.title': 'Nhà cung cấp Pi',
|
|
1303
|
+
'pi.providers.subtitle': 'Quản lý nhà cung cấp và mô hình Piper cục bộ',
|
|
1304
|
+
'pi.providers.empty': 'Chưa tìm thấy nhà cung cấp Piper. Nhấn Thêm để bắt đầu.',
|
|
1305
|
+
'pi.providers.targetFile': 'Tệp cấu hình Pi đang dùng: {path}',
|
|
1306
|
+
'pi.providers.editorTitle': 'Chỉnh sửa nhà cung cấp',
|
|
1307
|
+
'pi.providers.add': 'Thêm nhà cung cấp',
|
|
1308
|
+
'pi.providers.active': 'Đang bật',
|
|
1309
|
+
'pi.providers.setActive': 'Đặt thành đang dùng',
|
|
1310
|
+
'pi.providers.deleteTitle': 'Xóa cấu hình Pi',
|
|
1311
|
+
'pi.providers.deleteMessage': 'Xóa nhà cung cấp "{name}"?',
|
|
1312
|
+
'pi.providers.fields.name': 'Tên',
|
|
1313
|
+
'pi.providers.fields.baseUrl': 'Base URL',
|
|
1314
|
+
'pi.providers.fields.api': 'API',
|
|
1315
|
+
'pi.providers.fields.apiKey': 'API Key',
|
|
1316
|
+
'pi.providers.fields.models': 'Mô hình',
|
|
1317
|
+
'pi.providers.fields.configJson': 'Cấu hình JSON',
|
|
1318
|
+
'pi.providers.models.remove': 'Xóa',
|
|
1319
|
+
'pi.providers.models.search': 'Tìm kiếm mô hình khả dụng…',
|
|
1320
|
+
'pi.providers.models.remoteCount': 'Đã lấy {count} mô hình, bấm để thêm',
|
|
1321
|
+
'pi.providers.models.remoteLoading': 'Đang tải danh sách mô hình từ xa…',
|
|
1322
|
+
'pi.providers.models.remoteFailed': 'Bạn vẫn có thể nhập ID mô hình thủ công.',
|
|
1323
|
+
'pi.providers.models.noMatch': 'Không có mô hình phù hợp',
|
|
1324
|
+
'pi.providers.delete': 'Xóa nhà cung cấp',
|
|
1325
|
+
'pi.providers.addModal.title': 'Thêm nhà cung cấp',
|
|
1326
|
+
'pi.providers.addModal.subtitle': 'Tạo nhà cung cấp Piper mới',
|
|
1327
|
+
'pi.providers.addModal.fields.baseUrl': 'Base URL',
|
|
1328
|
+
'pi.providers.addModal.fields.apiKey': 'API Key',
|
|
1329
|
+
'pi.providers.addModal.api.openaiCompletions': 'OpenAI Chat Completions (tương thích nhất)',
|
|
1330
|
+
'pi.providers.addModal.api.openaiResponses': 'OpenAI Responses API',
|
|
1331
|
+
'pi.providers.addModal.api.anthropicMessages': 'Anthropic Messages API',
|
|
1332
|
+
'pi.providers.addModal.api.googleGenerativeAi': 'Google Generative AI',
|
|
1333
|
+
'pi.providers.addModal.fields.api': 'Định dạng API',
|
|
1291
1334
|
'kilocode.providerModel.title': 'Nhà cung cấp / mô hình KiloCode',
|
|
1292
1335
|
'kilocode.connection.title': 'Kết nối KiloCode',
|
|
1293
1336
|
'kilocode.targetFile': 'Cấu hình KiloCode đang dùng: {path} · {status}',
|