codexmate 0.1.16 → 0.1.18
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 -0
- package/README.vi.md +1 -0
- package/README.zh.md +1 -0
- package/cli/skills.js +18 -1
- package/cli.js +176 -4
- package/package.json +1 -1
- package/plugins/prompt-templates/computed.mjs +5 -0
- package/web-ui/app.js +12 -10
- package/web-ui/index.html +1 -0
- package/web-ui/modules/app.computed.session.mjs +83 -12
- package/web-ui/modules/app.methods.navigation.mjs +1 -1
- package/web-ui/modules/app.methods.pi-config.mjs +288 -120
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +3 -3
- package/web-ui/modules/i18n/locales/en.mjs +23 -0
- package/web-ui/modules/i18n/locales/ja.mjs +23 -0
- package/web-ui/modules/i18n/locales/vi.mjs +23 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +23 -0
- package/web-ui/modules/i18n/locales/zh.mjs +23 -0
- package/web-ui/modules/pi-provider-templates.mjs +26 -0
- package/web-ui/modules/skills.computed.mjs +6 -2
- package/web-ui/modules/skills.methods.mjs +1 -1
- package/web-ui/partials/index/modal-skills.html +8 -0
- package/web-ui/partials/index/modals-pi-provider.html +96 -0
- package/web-ui/partials/index/panel-config-pi.html +44 -122
- package/web-ui/partials/index/panel-docs.html +8 -22
- package/web-ui/partials/index/panel-market.html +8 -0
- package/web-ui/partials/index/panel-plugins.html +14 -13
- package/web-ui/partials/index/panel-usage.html +27 -25
- package/web-ui/res/web-ui-render.precompiled.js +795 -619
- package/web-ui/styles/docs-panel.css +9 -56
- package/web-ui/styles/plugins-panel.css +18 -18
- package/web-ui/styles/sessions-usage.css +104 -91
- package/web-ui/styles/settings-panel.css +12 -0
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
import { api } from './api.mjs';
|
|
2
|
+
import { PI_PROVIDER_TEMPLATE_GROUPS } from './pi-provider-templates.mjs';
|
|
2
3
|
|
|
3
4
|
function isPiPlainObject(value) {
|
|
4
5
|
return value && typeof value === 'object' && !Array.isArray(value);
|
|
5
6
|
}
|
|
6
7
|
|
|
8
|
+
function normalizePiModelInput(value) {
|
|
9
|
+
if (!Array.isArray(value)) return [];
|
|
10
|
+
return value.filter((item) => item === 'text' || item === 'image');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizePiModelNumber(value) {
|
|
14
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : '';
|
|
15
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
16
|
+
const num = Number(value);
|
|
17
|
+
return Number.isFinite(num) ? num : '';
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizePiModelRecord(model) {
|
|
23
|
+
const record = model && typeof model === 'object' ? model : {};
|
|
24
|
+
const contextWindow = record.contextWindow !== undefined ? record.contextWindow : record.context_window;
|
|
25
|
+
const maxTokens = record.maxTokens !== undefined ? record.maxTokens : record.max_tokens;
|
|
26
|
+
return {
|
|
27
|
+
id: record.id == null ? '' : String(record.id),
|
|
28
|
+
name: record.name == null ? '' : String(record.name),
|
|
29
|
+
reasoning: !!record.reasoning,
|
|
30
|
+
contextWindow: normalizePiModelNumber(contextWindow),
|
|
31
|
+
maxTokens: normalizePiModelNumber(maxTokens),
|
|
32
|
+
input: normalizePiModelInput(record.input)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
7
35
|
export function createPiConfigMethods({ api: apiClient }) {
|
|
8
36
|
return {
|
|
9
37
|
async loadPiProviders() {
|
|
@@ -79,9 +107,18 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
79
107
|
}
|
|
80
108
|
},
|
|
81
109
|
async savePiProvider() {
|
|
110
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
111
|
+
const editor = this.editingPiProvider;
|
|
112
|
+
if (!editor || !editor.form) return;
|
|
113
|
+
const isNew = !!editor.isNew;
|
|
114
|
+
const baseUrl = (editor.form.baseUrl || '').trim();
|
|
115
|
+
if (isNew && !baseUrl) {
|
|
116
|
+
this.message = 'Base URL 不能为空';
|
|
117
|
+
this.messageType = 'error';
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
82
120
|
this.piSaving = true;
|
|
83
121
|
try {
|
|
84
|
-
const providerId = this.editingPiProvider.id;
|
|
85
122
|
const { merged, ok, error } = this.piBuildMergedEditorRecord();
|
|
86
123
|
if (!ok || !merged) {
|
|
87
124
|
this.piEditorJsonError = error || '配置 JSON 不合法';
|
|
@@ -89,12 +126,23 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
89
126
|
this.messageType = 'error';
|
|
90
127
|
return;
|
|
91
128
|
}
|
|
92
|
-
|
|
129
|
+
const providerId = isNew ? this.derivePiProviderId(baseUrl) : editor.id;
|
|
130
|
+
if (isNew) {
|
|
131
|
+
merged.id = providerId;
|
|
132
|
+
if (merged.name === undefined) merged.name = providerId;
|
|
133
|
+
}
|
|
93
134
|
await this.persistPiProvider(providerId, merged);
|
|
94
135
|
this.piProviders = { ...this.piProviders, [providerId]: merged };
|
|
95
136
|
this.piProviderIds = Object.keys(this.piProviders).sort();
|
|
96
137
|
this.resetPiProviderEditing();
|
|
97
|
-
|
|
138
|
+
if (isNew) {
|
|
139
|
+
this.showAddPiProviderModal = false;
|
|
140
|
+
this.piProviderPickerQuery = '';
|
|
141
|
+
this.piSelectedProviderTemplate = '';
|
|
142
|
+
this.message = '供应商已添加';
|
|
143
|
+
} else {
|
|
144
|
+
this.message = '配置已保存';
|
|
145
|
+
}
|
|
98
146
|
this.messageType = 'success';
|
|
99
147
|
} catch (e) {
|
|
100
148
|
this.message = e && e.message ? e.message : '保存 Pi 配置失败';
|
|
@@ -103,45 +151,6 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
103
151
|
this.piSaving = false;
|
|
104
152
|
}
|
|
105
153
|
},
|
|
106
|
-
async addPiProviderFromModal() {
|
|
107
|
-
let createdId = null;
|
|
108
|
-
this.piSaving = true;
|
|
109
|
-
try {
|
|
110
|
-
const baseUrl = (this.addingPiProviderBaseUrl || '').trim();
|
|
111
|
-
if (!baseUrl) throw new Error('Base URL 不能为空');
|
|
112
|
-
const providerId = this.derivePiProviderId(baseUrl);
|
|
113
|
-
const modelId = (this.addingPiProviderModel || '').trim();
|
|
114
|
-
const values = {
|
|
115
|
-
id: providerId,
|
|
116
|
-
name: providerId,
|
|
117
|
-
baseUrl,
|
|
118
|
-
api: this.addingPiProviderApi || 'openai-completions',
|
|
119
|
-
apiKey: (this.addingPiProviderApiKey || '').trim(),
|
|
120
|
-
models: modelId ? [{
|
|
121
|
-
id: modelId,
|
|
122
|
-
name: modelId,
|
|
123
|
-
reasoning: false,
|
|
124
|
-
contextWindow: 128000,
|
|
125
|
-
maxTokens: 32000,
|
|
126
|
-
input: ''
|
|
127
|
-
}] : []
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
await this.persistPiProvider(providerId, values);
|
|
131
|
-
this.piProviders = { ...this.piProviders, [providerId]: values };
|
|
132
|
-
this.piProviderIds = Object.keys(this.piProviders).sort();
|
|
133
|
-
this.cancelAddPiProviderModal();
|
|
134
|
-
this.message = '供应商已添加';
|
|
135
|
-
this.messageType = 'success';
|
|
136
|
-
createdId = providerId;
|
|
137
|
-
} catch (e) {
|
|
138
|
-
this.message = e && e.message ? e.message : '添加 Pi 供应商失败';
|
|
139
|
-
this.messageType = 'error';
|
|
140
|
-
} finally {
|
|
141
|
-
this.piSaving = false;
|
|
142
|
-
if (createdId) this.openEditPiProvider(createdId);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
154
|
derivePiProviderId(baseUrl) {
|
|
146
155
|
let candidate = String(Date.now());
|
|
147
156
|
while (this.piProviders[candidate]) candidate = String(Number(candidate) + 1);
|
|
@@ -150,79 +159,156 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
150
159
|
piProviderUrlTitle() {
|
|
151
160
|
return '';
|
|
152
161
|
},
|
|
162
|
+
resetPiEditorTransientState() {
|
|
163
|
+
this.piRemoteModels = [];
|
|
164
|
+
this.piRemoteModelError = '';
|
|
165
|
+
this.piRemoteChecked = {};
|
|
166
|
+
this.piCatalogFillError = '';
|
|
167
|
+
this.piCatalogFillIndex = -1;
|
|
168
|
+
this.piModelSearch = '';
|
|
169
|
+
this.piEditorJsonError = '';
|
|
170
|
+
},
|
|
153
171
|
startAddPiProvider() {
|
|
154
172
|
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
155
|
-
this.addingPiProviderId = '';
|
|
156
|
-
this.addingPiProviderName = '';
|
|
157
|
-
this.addingPiProviderBaseUrl = '';
|
|
158
|
-
this.addingPiProviderApi = 'openai-completions';
|
|
159
|
-
this.addingPiProviderApiKey = '';
|
|
160
|
-
this.addingPiProviderModel = '';
|
|
161
|
-
this.addingPiRemoteModels = [];
|
|
162
|
-
this.addingPiRemoteLoading = false;
|
|
163
|
-
this.addingPiRemoteError = '';
|
|
164
|
-
this.showAddPiProviderModal = true;
|
|
165
173
|
this.editingPiProvider = null;
|
|
174
|
+
this.showAddPiProviderModal = true;
|
|
175
|
+
this.piProviderPickerQuery = '';
|
|
176
|
+
this.piSelectedProviderTemplate = '';
|
|
177
|
+
this.resetPiEditorTransientState();
|
|
166
178
|
},
|
|
167
179
|
cancelAddPiProviderModal() {
|
|
168
180
|
this.showAddPiProviderModal = false;
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
this.piProviderPickerQuery = '';
|
|
182
|
+
this.piSelectedProviderTemplate = '';
|
|
183
|
+
if (this.editingPiProvider && this.editingPiProvider.isNew) this.editingPiProvider = null;
|
|
184
|
+
this.resetPiEditorTransientState();
|
|
185
|
+
},
|
|
186
|
+
piProviderTemplateGroups() {
|
|
187
|
+
const query = (this.piProviderPickerQuery || '').trim().toLowerCase();
|
|
188
|
+
if (!query) return PI_PROVIDER_TEMPLATE_GROUPS;
|
|
189
|
+
return PI_PROVIDER_TEMPLATE_GROUPS
|
|
190
|
+
.map((group) => ({
|
|
191
|
+
id: group.id,
|
|
192
|
+
i18nKey: group.i18nKey,
|
|
193
|
+
templates: group.templates.filter((tpl) =>
|
|
194
|
+
tpl.name.toLowerCase().includes(query) || tpl.id.toLowerCase().includes(query))
|
|
195
|
+
}))
|
|
196
|
+
.filter((group) => group.templates.length > 0);
|
|
197
|
+
},
|
|
198
|
+
selectPiProviderTemplate(tplId) {
|
|
199
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
200
|
+
const tpl = PI_PROVIDER_TEMPLATE_GROUPS
|
|
201
|
+
.flatMap((group) => group.templates)
|
|
202
|
+
.find((item) => item.id === tplId);
|
|
203
|
+
if (!tpl) return;
|
|
204
|
+
this.piSelectedProviderTemplate = tpl.id;
|
|
205
|
+
this.startPiProviderDraft(tpl);
|
|
206
|
+
},
|
|
207
|
+
startPiProviderDraft(tpl) {
|
|
208
|
+
const template = isPiPlainObject(tpl) ? tpl : {};
|
|
209
|
+
this.resetPiEditorTransientState();
|
|
210
|
+
this.editingPiProvider = {
|
|
211
|
+
id: '',
|
|
212
|
+
isNew: true,
|
|
213
|
+
form: {
|
|
214
|
+
id: '',
|
|
215
|
+
baseUrl: template.baseUrl || '',
|
|
216
|
+
api: template.api || 'openai-completions',
|
|
217
|
+
apiKey: '',
|
|
218
|
+
configJsonDraft: '',
|
|
219
|
+
models: []
|
|
220
|
+
},
|
|
221
|
+
extras: {},
|
|
222
|
+
original: null
|
|
223
|
+
};
|
|
224
|
+
this.fetchPiRemoteModels();
|
|
225
|
+
},
|
|
226
|
+
resetPiProviderTemplateSelection() {
|
|
227
|
+
this.piSelectedProviderTemplate = '';
|
|
228
|
+
if (this.editingPiProvider && this.editingPiProvider.isNew) this.editingPiProvider = null;
|
|
229
|
+
},
|
|
230
|
+
piRemoteSelectableModels() {
|
|
231
|
+
const base = Array.isArray(this.piRemoteModels) ? this.piRemoteModels : [];
|
|
232
|
+
const form = this.editingPiProvider && this.editingPiProvider.form;
|
|
233
|
+
const existing = Array.isArray(form && form.models) ? form.models : [];
|
|
234
|
+
const added = new Set(existing.map((m) => String(m && m.id || '').trim().toLowerCase()));
|
|
235
|
+
const q = (this.piModelSearch || '').trim().toLowerCase();
|
|
236
|
+
return base
|
|
237
|
+
.filter((id) => {
|
|
238
|
+
if (added.has(String(id).trim().toLowerCase())) return false;
|
|
239
|
+
return !q || String(id).toLowerCase().includes(q);
|
|
240
|
+
})
|
|
241
|
+
.slice(0, 50);
|
|
242
|
+
},
|
|
243
|
+
isPiRemoteChecked(id) {
|
|
244
|
+
return !!this.piRemoteChecked[String(id)];
|
|
245
|
+
},
|
|
246
|
+
togglePiRemoteChecked(id) {
|
|
247
|
+
const key = String(id);
|
|
248
|
+
const next = { ...this.piRemoteChecked };
|
|
249
|
+
if (next[key]) delete next[key];
|
|
250
|
+
else next[key] = true;
|
|
251
|
+
this.piRemoteChecked = next;
|
|
252
|
+
},
|
|
253
|
+
piCheckedRemoteCount() {
|
|
254
|
+
return this.piRemoteSelectableModels().filter((id) => this.isPiRemoteChecked(id)).length;
|
|
255
|
+
},
|
|
256
|
+
piCreateModelRecord(id) {
|
|
257
|
+
const t = String(id || '').trim();
|
|
258
|
+
return {
|
|
259
|
+
id: t,
|
|
260
|
+
name: t,
|
|
261
|
+
reasoning: false,
|
|
262
|
+
contextWindow: 128000,
|
|
263
|
+
maxTokens: 32000,
|
|
264
|
+
input: []
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
addSelectedPiRemoteModels() {
|
|
268
|
+
if (!this.isToolConfigWriteAllowed('pi') || !this.editingPiProvider) return;
|
|
269
|
+
const form = this.editingPiProvider.form;
|
|
270
|
+
if (!form || !Array.isArray(form.models)) return;
|
|
271
|
+
const existing = new Set(form.models.map((m) => String(m && m.id || '').trim().toLowerCase()));
|
|
272
|
+
for (const id of this.piRemoteSelectableModels()) {
|
|
273
|
+
if (!this.isPiRemoteChecked(id)) continue;
|
|
274
|
+
const key = String(id).trim().toLowerCase();
|
|
275
|
+
if (existing.has(key)) continue;
|
|
276
|
+
form.models.push(this.piCreateModelRecord(id));
|
|
277
|
+
existing.add(key);
|
|
185
278
|
}
|
|
186
|
-
this.
|
|
187
|
-
this.
|
|
279
|
+
this.piRemoteChecked = {};
|
|
280
|
+
this.piModelSearch = '';
|
|
281
|
+
},
|
|
282
|
+
async fillPiModelFromCatalog(index) {
|
|
283
|
+
if (!this.isToolConfigWriteAllowed('pi') || !this.editingPiProvider) return;
|
|
284
|
+
const models = this.editingPiProvider.form && this.editingPiProvider.form.models;
|
|
285
|
+
const model = Array.isArray(models) ? models[index] : null;
|
|
286
|
+
if (!model || !String(model.id || '').trim()) return;
|
|
287
|
+
if (this.piCatalogFillIndex !== -1) return;
|
|
288
|
+
this.piCatalogFillIndex = index;
|
|
289
|
+
this.piCatalogFillError = '';
|
|
188
290
|
try {
|
|
189
|
-
const result = await apiClient('
|
|
190
|
-
|
|
191
|
-
|
|
291
|
+
const result = await apiClient('pi-models-catalog', {
|
|
292
|
+
modelId: String(model.id).trim(),
|
|
293
|
+
providerId: this.editingPiProvider.id,
|
|
294
|
+
baseUrl: this.editingPiProvider.form.baseUrl || ''
|
|
192
295
|
});
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
if (result && result.ok && isPiPlainObject(result.model)) {
|
|
297
|
+
const info = result.model;
|
|
298
|
+
if (model.name === '' && typeof info.name === 'string') model.name = info.name;
|
|
299
|
+
if (model.contextWindow === '' && Number.isFinite(info.contextWindow)) model.contextWindow = info.contextWindow;
|
|
300
|
+
if (model.maxTokens === '' && Number.isFinite(info.maxTokens)) model.maxTokens = info.maxTokens;
|
|
301
|
+
model.reasoning = !!info.reasoning;
|
|
196
302
|
} else {
|
|
197
|
-
this.
|
|
198
|
-
this.addingPiRemoteError = (result && result.error) || '未获取到可用模型';
|
|
303
|
+
this.piCatalogFillError = (result && result.error) || 'models.dev 目录中未找到该模型';
|
|
199
304
|
}
|
|
200
|
-
} catch (e) {
|
|
201
|
-
if (!this.showAddPiProviderModal) return;
|
|
202
|
-
this.addingPiRemoteModels = [];
|
|
203
|
-
this.addingPiRemoteError = e && e.message ? e.message : '模型列表获取失败';
|
|
204
305
|
} finally {
|
|
205
|
-
|
|
306
|
+
this.piCatalogFillIndex = -1;
|
|
206
307
|
}
|
|
207
308
|
},
|
|
208
|
-
onAddingPiEndpointChange() {
|
|
209
|
-
if (this.addingPiRemoteDebounce) clearTimeout(this.addingPiRemoteDebounce);
|
|
210
|
-
this.addingPiRemoteDebounce = setTimeout(() => {
|
|
211
|
-
this.addingPiRemoteDebounce = null;
|
|
212
|
-
this.fetchAddingPiRemoteModels();
|
|
213
|
-
}, 600);
|
|
214
|
-
},
|
|
215
|
-
piFilteredAddingRemoteModels() {
|
|
216
|
-
const query = (this.addingPiProviderModel || '').trim().toLowerCase();
|
|
217
|
-
if (!query) return [];
|
|
218
|
-
return this.addingPiRemoteModels.filter((id) => id.toLowerCase().includes(query)).slice(0, 50);
|
|
219
|
-
},
|
|
220
309
|
openEditPiProvider(providerId) {
|
|
221
310
|
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving) return;
|
|
222
|
-
this.
|
|
223
|
-
this.piRemoteModelError = '';
|
|
224
|
-
this.piModelSearch = '';
|
|
225
|
-
this.piEditorJsonError = '';
|
|
311
|
+
this.resetPiEditorTransientState();
|
|
226
312
|
const provider = this.piProviders[providerId] || {};
|
|
227
313
|
const knownKeys = ['id', 'name', 'title', 'baseUrl', 'api', 'apiKey', 'models', 'headers', 'configJson'];
|
|
228
314
|
const extras = {};
|
|
@@ -236,14 +322,7 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
236
322
|
apiKey: provider.apiKey || '',
|
|
237
323
|
configJsonDraft: JSON.stringify(extras, null, 2),
|
|
238
324
|
models: Array.isArray(provider.models) && provider.models.length
|
|
239
|
-
? provider.models.map((model) => ({
|
|
240
|
-
id: model?.id || '',
|
|
241
|
-
name: model?.name || '',
|
|
242
|
-
reasoning: model?.reasoning || '',
|
|
243
|
-
contextWindow: model?.contextWindow ?? model?.context_window ?? '',
|
|
244
|
-
maxTokens: model?.maxTokens ?? model?.max_tokens ?? '',
|
|
245
|
-
input: model?.input || ''
|
|
246
|
-
}))
|
|
325
|
+
? provider.models.map((model) => ({ ...model, ...normalizePiModelRecord(model) }))
|
|
247
326
|
: []
|
|
248
327
|
};
|
|
249
328
|
this.editingPiProvider = {
|
|
@@ -259,9 +338,19 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
259
338
|
this.editingPiProvider = null;
|
|
260
339
|
this.piRemoteModels = [];
|
|
261
340
|
this.piRemoteModelError = '';
|
|
341
|
+
this.piRemoteChecked = {};
|
|
342
|
+
this.piCatalogFillError = '';
|
|
343
|
+
this.piCatalogFillIndex = -1;
|
|
262
344
|
this.piModelSearch = '';
|
|
263
345
|
this.piEditorJsonError = '';
|
|
264
346
|
},
|
|
347
|
+
closePiProviderEditor() {
|
|
348
|
+
const editor = this.editingPiProvider;
|
|
349
|
+
if (!editor) return;
|
|
350
|
+
const wasNew = !!editor.isNew;
|
|
351
|
+
this.resetPiProviderEditing();
|
|
352
|
+
if (wasNew) this.piSelectedProviderTemplate = '';
|
|
353
|
+
},
|
|
265
354
|
removePiProviderModel(index) {
|
|
266
355
|
if (!this.editingPiProvider) return;
|
|
267
356
|
const list = this.editingPiProvider.form.models;
|
|
@@ -280,13 +369,16 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
280
369
|
if (this.editingPiProvider !== provider) return;
|
|
281
370
|
if (result && Array.isArray(result.models) && result.models.length > 0) {
|
|
282
371
|
this.piRemoteModels = result.models;
|
|
372
|
+
this.piRemoteChecked = {};
|
|
283
373
|
} else {
|
|
284
374
|
this.piRemoteModels = [];
|
|
375
|
+
this.piRemoteChecked = {};
|
|
285
376
|
this.piRemoteModelError = (result && result.error) || '未获取到可用模型';
|
|
286
377
|
}
|
|
287
378
|
} catch (e) {
|
|
288
379
|
if (this.editingPiProvider !== provider) return;
|
|
289
380
|
this.piRemoteModels = [];
|
|
381
|
+
this.piRemoteChecked = {};
|
|
290
382
|
this.piRemoteModelError = e && e.message ? e.message : '模型列表获取失败';
|
|
291
383
|
} finally {
|
|
292
384
|
if (this.editingPiProvider === provider) this.piRemoteModelsLoading = false;
|
|
@@ -303,14 +395,10 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
303
395
|
if (!trimmed) return;
|
|
304
396
|
const list = this.editingPiProvider.form.models;
|
|
305
397
|
if (!Array.isArray(list)) return;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
contextWindow: 128000,
|
|
311
|
-
maxTokens: 32000,
|
|
312
|
-
input: ''
|
|
313
|
-
});
|
|
398
|
+
const key = trimmed.toLowerCase();
|
|
399
|
+
const duplicated = list.some((m) => String(m && m.id || '').trim().toLowerCase() === key);
|
|
400
|
+
if (duplicated) return;
|
|
401
|
+
list.push(this.piCreateModelRecord(trimmed));
|
|
314
402
|
this.piModelSearch = '';
|
|
315
403
|
},
|
|
316
404
|
async confirmDeletePiProvider() {
|
|
@@ -447,11 +535,11 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
447
535
|
const values = provider.form || {};
|
|
448
536
|
const extras = draft.extras || {};
|
|
449
537
|
const orig = this.piProviders[provider.id] || {};
|
|
450
|
-
const mapModels = (xs) => (Array.isArray(xs) ? xs : []).map((model) =>
|
|
451
|
-
...model,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
})
|
|
538
|
+
const mapModels = (xs) => (Array.isArray(xs) ? xs : []).map((model) => {
|
|
539
|
+
const record = { ...(model && typeof model === 'object' ? model : {}), ...normalizePiModelRecord(model) };
|
|
540
|
+
if (!record.name) record.name = record.id;
|
|
541
|
+
return record;
|
|
542
|
+
});
|
|
455
543
|
const merged = { ...extras };
|
|
456
544
|
if (merged.baseUrl === undefined) merged.baseUrl = values.baseUrl || '';
|
|
457
545
|
if (merged.api === undefined) merged.api = values.api || '';
|
|
@@ -475,6 +563,86 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
475
563
|
piEditorJsonDraftInput() {
|
|
476
564
|
const draft = this.piParseEditorJsonDraft();
|
|
477
565
|
this.piEditorJsonError = draft.ok ? '' : draft.error;
|
|
566
|
+
},
|
|
567
|
+
piHistoryBucketFor(target) {
|
|
568
|
+
return target === 'settings' ? 'pi-settings' : 'pi-models';
|
|
569
|
+
},
|
|
570
|
+
async openPiConfigHistory(target) {
|
|
571
|
+
if (this.piHistoryLoading) return;
|
|
572
|
+
const tgt = target === 'settings' ? 'settings' : (target === 'models' ? 'models' : '');
|
|
573
|
+
if (!tgt) return;
|
|
574
|
+
this.piHistoryTarget = tgt;
|
|
575
|
+
this.piHistoryItems = [];
|
|
576
|
+
this.piHistoryPreviewId = '';
|
|
577
|
+
this.piHistoryPreviewContent = '';
|
|
578
|
+
this.piHistoryError = '';
|
|
579
|
+
this.piHistoryLoading = true;
|
|
580
|
+
try {
|
|
581
|
+
const res = await api('list-prompt-history', { bucket: this.piHistoryBucketFor(tgt) });
|
|
582
|
+
if (res && res.error) {
|
|
583
|
+
this.piHistoryError = res.error;
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
this.piHistoryItems = Array.isArray(res) ? res : [];
|
|
587
|
+
} catch (e) {
|
|
588
|
+
this.piHistoryError = this.t('toast.load.fail');
|
|
589
|
+
} finally {
|
|
590
|
+
this.piHistoryLoading = false;
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
closePiConfigHistory() {
|
|
594
|
+
this.piHistoryTarget = '';
|
|
595
|
+
this.piHistoryItems = [];
|
|
596
|
+
this.piHistoryPreviewId = '';
|
|
597
|
+
this.piHistoryPreviewContent = '';
|
|
598
|
+
this.piHistoryError = '';
|
|
599
|
+
this.piHistoryLoading = false;
|
|
600
|
+
},
|
|
601
|
+
async viewPiConfigHistoryItem(item) {
|
|
602
|
+
if (!item || !item.id) return;
|
|
603
|
+
if (this.piHistoryLoading) return;
|
|
604
|
+
if (this.piHistoryPreviewId === item.id && this.piHistoryPreviewContent) return;
|
|
605
|
+
this.piHistoryPreviewId = item.id;
|
|
606
|
+
this.piHistoryPreviewContent = '';
|
|
607
|
+
try {
|
|
608
|
+
const res = await api('get-prompt-history', { bucket: this.piHistoryBucketFor(this.piHistoryTarget), id: item.id });
|
|
609
|
+
if (res && res.error) {
|
|
610
|
+
this.piHistoryError = res.error;
|
|
611
|
+
this.piHistoryPreviewId = '';
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
this.piHistoryPreviewContent = typeof res.content === 'string' ? res.content : '';
|
|
615
|
+
} catch (e) {
|
|
616
|
+
this.piHistoryError = this.t('toast.load.fail');
|
|
617
|
+
this.piHistoryPreviewId = '';
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
async applyPiConfigHistory() {
|
|
621
|
+
if (this.piHistoryApplying || this.piHistoryLoading) return;
|
|
622
|
+
if (!this.piHistoryTarget || !this.piHistoryPreviewId) return;
|
|
623
|
+
const confirmed = await this.requestConfirmDialog({
|
|
624
|
+
title: this.t('common.history'),
|
|
625
|
+
message: this.t('pi.history.confirm'),
|
|
626
|
+
confirmText: this.t('confirm.ok'),
|
|
627
|
+
cancelText: this.t('confirm.cancel'),
|
|
628
|
+
danger: true
|
|
629
|
+
});
|
|
630
|
+
if (!confirmed) return;
|
|
631
|
+
this.piHistoryApplying = true;
|
|
632
|
+
try {
|
|
633
|
+
const res = await api('apply-pi-config-history', { target: this.piHistoryTarget, id: this.piHistoryPreviewId });
|
|
634
|
+
if (res && res.error) {
|
|
635
|
+
this.showMessage(res.error, 'error');
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.showMessage(this.t('pi.history.applied'), 'success');
|
|
639
|
+
this.closePiConfigHistory();
|
|
640
|
+
await this.loadPiSources();
|
|
641
|
+
} catch (e) {
|
|
642
|
+
this.showMessage(this.t('pi.history.applyFailed'), 'error');
|
|
643
|
+
} finally {
|
|
644
|
+
this.piHistoryApplying = false;
|
|
645
|
+
}
|
|
478
646
|
}
|
|
479
647
|
};
|
|
480
648
|
}
|
|
@@ -102,13 +102,13 @@ function normalizeSessionSortMode(value) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function normalizeNavigationSnapshot(vm, source = {}) {
|
|
105
|
-
const currentSkillsTargetApp = vm.skillsTargetApp === 'claude'
|
|
105
|
+
const currentSkillsTargetApp = vm.skillsTargetApp === 'claude' || vm.skillsTargetApp === 'pi' ? vm.skillsTargetApp : 'codex';
|
|
106
106
|
const currentPromptTemplatesMode = vm.promptTemplatesMode === 'manage' ? 'manage' : 'compose';
|
|
107
107
|
return {
|
|
108
108
|
mainTab: typeof source.mainTab === 'string' ? source.mainTab : vm.mainTab,
|
|
109
109
|
configMode: typeof source.configMode === 'string' ? source.configMode : vm.configMode,
|
|
110
110
|
settingsTab: typeof source.settingsTab === 'string' ? source.settingsTab : vm.settingsTab,
|
|
111
|
-
skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex'
|
|
111
|
+
skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex' || source.skillsTargetApp === 'pi'
|
|
112
112
|
? source.skillsTargetApp
|
|
113
113
|
: currentSkillsTargetApp,
|
|
114
114
|
promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
|
|
@@ -477,7 +477,7 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
477
477
|
this.mainTab = nav.mainTab;
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
|
-
if (nav.skillsTargetApp === 'codex' || nav.skillsTargetApp === 'claude') this.skillsTargetApp = nav.skillsTargetApp;
|
|
480
|
+
if (nav.skillsTargetApp === 'codex' || nav.skillsTargetApp === 'claude' || nav.skillsTargetApp === 'pi') this.skillsTargetApp = nav.skillsTargetApp;
|
|
481
481
|
if (nav.promptTemplatesMode === 'compose' || nav.promptTemplatesMode === 'manage') this.promptTemplatesMode = nav.promptTemplatesMode;
|
|
482
482
|
if (typeof this.saveNavState === 'function') this.saveNavState();
|
|
483
483
|
}
|
|
@@ -827,6 +827,12 @@ const en = Object.freeze({
|
|
|
827
827
|
'usage.summary.avgMessagesPerSession': 'Avg messages/session',
|
|
828
828
|
'usage.summary.busiestDay': 'Busiest day',
|
|
829
829
|
'usage.summary.busiestHour': 'Peak hour',
|
|
830
|
+
'usage.kpi.tokens': 'Total tokens',
|
|
831
|
+
'usage.kpi.sessions': 'Sessions',
|
|
832
|
+
'usage.kpi.messages': 'Messages',
|
|
833
|
+
'usage.kpi.dailyAvg': 'Avg tokens / day',
|
|
834
|
+
'usage.kpi.busiestDay': 'Busiest day',
|
|
835
|
+
'usage.wave.summary': '{total} total · peak {peakDay} ({peakValue})',
|
|
830
836
|
'usage.currentSession.title': 'Current session',
|
|
831
837
|
'usage.currentSession.apiDuration': 'API duration',
|
|
832
838
|
'usage.currentSession.totalDuration': 'Total duration',
|
|
@@ -1358,12 +1364,29 @@ const en = Object.freeze({
|
|
|
1358
1364
|
'pi.files.desc': 'View and edit Pi settings.json and models.json in full; JSON validity is checked before saving and invalid content is never written.',
|
|
1359
1365
|
'pi.files.settingsTitle': 'settings.json (active settings)',
|
|
1360
1366
|
'pi.files.modelsTitle': 'models.json (providers and models)',
|
|
1367
|
+
'pi.history.apply': 'Apply this record',
|
|
1368
|
+
'pi.history.applied': 'History record applied',
|
|
1369
|
+
'pi.history.applyFailed': 'Failed to apply history record',
|
|
1370
|
+
'pi.history.confirm': 'Apply this history record to the file? The current file content will be snapshotted first.',
|
|
1361
1371
|
'pi.providers.models.remove': 'Remove',
|
|
1362
1372
|
'pi.providers.models.search': 'Search available models…',
|
|
1363
1373
|
'pi.providers.models.remoteCount': '{count} models available, click to add',
|
|
1364
1374
|
'pi.providers.models.remoteLoading': 'Fetching remote model list…',
|
|
1365
1375
|
'pi.providers.models.remoteFailed': 'You can still type model IDs manually.',
|
|
1366
1376
|
'pi.providers.models.noMatch': 'No matching models',
|
|
1377
|
+
'pi.providers.models.importTitle': 'Import models from endpoint',
|
|
1378
|
+
'pi.providers.models.addSelected': 'Add selected ({count})',
|
|
1379
|
+
'pi.providers.models.name': 'Name',
|
|
1380
|
+
'pi.providers.models.reasoning': 'Supports reasoning',
|
|
1381
|
+
'pi.providers.models.contextWindow': 'Context window',
|
|
1382
|
+
'pi.providers.models.maxTokens': 'Max output',
|
|
1383
|
+
'pi.providers.models.catalogFill': 'Auto-fill',
|
|
1384
|
+
'pi.providers.models.catalogFilling': 'Filling…',
|
|
1385
|
+
'pi.picker.searchPlaceholder': 'Search providers…',
|
|
1386
|
+
'pi.picker.group.custom': 'Custom endpoint',
|
|
1387
|
+
'pi.picker.group.apiKey': 'API key providers',
|
|
1388
|
+
'pi.picker.empty': 'No matching provider templates',
|
|
1389
|
+
'pi.picker.back': 'Back to template selection',
|
|
1367
1390
|
'pi.providers.delete': 'Delete Provider',
|
|
1368
1391
|
'pi.providers.addModal.title': 'Add Provider',
|
|
1369
1392
|
'pi.providers.addModal.subtitle': 'Create a new Piper provider',
|
|
@@ -830,6 +830,12 @@ const ja = Object.freeze({
|
|
|
830
830
|
'usage.summary.avgMessagesPerSession': 'セッションあたり平均メッセージ',
|
|
831
831
|
'usage.summary.busiestDay': '最も忙しい日',
|
|
832
832
|
'usage.summary.busiestHour': 'ピーク時間帯',
|
|
833
|
+
'usage.kpi.tokens': '合計 Token',
|
|
834
|
+
'usage.kpi.sessions': 'セッション数',
|
|
835
|
+
'usage.kpi.messages': 'メッセージ数',
|
|
836
|
+
'usage.kpi.dailyAvg': '1 日あたり Token',
|
|
837
|
+
'usage.kpi.busiestDay': '最も忙しい日',
|
|
838
|
+
'usage.wave.summary': '合計 {total} · ピーク {peakDay}({peakValue})',
|
|
833
839
|
'usage.currentSession.title': '現在のセッション',
|
|
834
840
|
'usage.currentSession.apiDuration': 'API 時間',
|
|
835
841
|
'usage.currentSession.totalDuration': '総時間',
|
|
@@ -1359,12 +1365,29 @@ const ja = Object.freeze({
|
|
|
1359
1365
|
'pi.files.desc': 'Pi の settings.json と models.json を全文表示して直接編集します。保存前に JSON の正当性を検証し、不正な内容は書き込みません。',
|
|
1360
1366
|
'pi.files.settingsTitle': 'settings.json(有効な設定)',
|
|
1361
1367
|
'pi.files.modelsTitle': 'models.json(プロバイダーとモデル)',
|
|
1368
|
+
'pi.history.apply': 'この履歴を適用',
|
|
1369
|
+
'pi.history.applied': '履歴を適用しました',
|
|
1370
|
+
'pi.history.applyFailed': '履歴の適用に失敗しました',
|
|
1371
|
+
'pi.history.confirm': 'この履歴をファイルに適用しますか?現在の内容は先にスナップショット保存されます。',
|
|
1362
1372
|
'pi.providers.models.remove': '削除',
|
|
1363
1373
|
'pi.providers.models.search': '利用可能なモデルを検索…',
|
|
1364
1374
|
'pi.providers.models.remoteCount': '{count} 個のモデルを取得しました。クリックで追加',
|
|
1365
1375
|
'pi.providers.models.remoteLoading': 'リモートモデル一覧を取得中…',
|
|
1366
1376
|
'pi.providers.models.remoteFailed': 'モデル ID は手動入力できます。',
|
|
1367
1377
|
'pi.providers.models.noMatch': '一致するモデルがありません',
|
|
1378
|
+
'pi.providers.models.importTitle': 'エンドポイントからモデルをインポート',
|
|
1379
|
+
'pi.providers.models.addSelected': '選択済みを追加({count})',
|
|
1380
|
+
'pi.providers.models.name': '名前',
|
|
1381
|
+
'pi.providers.models.reasoning': '推論をサポート',
|
|
1382
|
+
'pi.providers.models.contextWindow': 'コンテキストウィンドウ',
|
|
1383
|
+
'pi.providers.models.maxTokens': '最大出力',
|
|
1384
|
+
'pi.providers.models.catalogFill': '自動入力',
|
|
1385
|
+
'pi.providers.models.catalogFilling': '入力中…',
|
|
1386
|
+
'pi.picker.searchPlaceholder': 'プロバイダーを検索…',
|
|
1387
|
+
'pi.picker.group.custom': 'カスタムエンドポイント',
|
|
1388
|
+
'pi.picker.group.apiKey': 'API キープロバイダー',
|
|
1389
|
+
'pi.picker.empty': '一致するプロバイダーテンプレートがありません',
|
|
1390
|
+
'pi.picker.back': 'テンプレート選択に戻る',
|
|
1368
1391
|
'pi.providers.delete': 'プロバイダを削除',
|
|
1369
1392
|
'pi.providers.addModal.title': 'プロバイダを追加',
|
|
1370
1393
|
'pi.providers.addModal.subtitle': '新しい Piper プロバイダを作成',
|