codexmate 0.1.17 → 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.js +130 -3
- package/package.json +1 -1
- package/web-ui/app.js +5 -10
- package/web-ui/index.html +1 -0
- package/web-ui/modules/app.methods.pi-config.mjs +208 -120
- package/web-ui/modules/i18n/locales/en.mjs +13 -0
- package/web-ui/modules/i18n/locales/ja.mjs +13 -0
- package/web-ui/modules/i18n/locales/vi.mjs +13 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +13 -0
- package/web-ui/modules/i18n/locales/zh.mjs +13 -0
- package/web-ui/modules/pi-provider-templates.mjs +26 -0
- package/web-ui/partials/index/modals-pi-provider.html +96 -0
- package/web-ui/partials/index/panel-config-pi.html +0 -122
- package/web-ui/res/web-ui-render.precompiled.js +323 -348
- 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 || '';
|
|
@@ -1374,6 +1374,19 @@ const en = Object.freeze({
|
|
|
1374
1374
|
'pi.providers.models.remoteLoading': 'Fetching remote model list…',
|
|
1375
1375
|
'pi.providers.models.remoteFailed': 'You can still type model IDs manually.',
|
|
1376
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',
|
|
1377
1390
|
'pi.providers.delete': 'Delete Provider',
|
|
1378
1391
|
'pi.providers.addModal.title': 'Add Provider',
|
|
1379
1392
|
'pi.providers.addModal.subtitle': 'Create a new Piper provider',
|
|
@@ -1375,6 +1375,19 @@ const ja = Object.freeze({
|
|
|
1375
1375
|
'pi.providers.models.remoteLoading': 'リモートモデル一覧を取得中…',
|
|
1376
1376
|
'pi.providers.models.remoteFailed': 'モデル ID は手動入力できます。',
|
|
1377
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': 'テンプレート選択に戻る',
|
|
1378
1391
|
'pi.providers.delete': 'プロバイダを削除',
|
|
1379
1392
|
'pi.providers.addModal.title': 'プロバイダを追加',
|
|
1380
1393
|
'pi.providers.addModal.subtitle': '新しい Piper プロバイダを作成',
|
|
@@ -1343,6 +1343,19 @@ const vi = Object.freeze({
|
|
|
1343
1343
|
'pi.providers.models.remoteLoading': 'Đang tải danh sách mô hình từ xa…',
|
|
1344
1344
|
'pi.providers.models.remoteFailed': 'Bạn vẫn có thể nhập ID mô hình thủ công.',
|
|
1345
1345
|
'pi.providers.models.noMatch': 'Không có mô hình phù hợp',
|
|
1346
|
+
'pi.providers.models.importTitle': 'Nhập mô hình từ endpoint',
|
|
1347
|
+
'pi.providers.models.addSelected': 'Thêm đã chọn ({count})',
|
|
1348
|
+
'pi.providers.models.name': 'Tên',
|
|
1349
|
+
'pi.providers.models.reasoning': 'Hỗ trợ suy luận',
|
|
1350
|
+
'pi.providers.models.contextWindow': 'Cửa sổ ngữ cảnh',
|
|
1351
|
+
'pi.providers.models.maxTokens': 'Đầu ra tối đa',
|
|
1352
|
+
'pi.providers.models.catalogFill': 'Tự động điền',
|
|
1353
|
+
'pi.providers.models.catalogFilling': 'Đang điền…',
|
|
1354
|
+
'pi.picker.searchPlaceholder': 'Tìm kiếm nhà cung cấp…',
|
|
1355
|
+
'pi.picker.group.custom': 'Endpoint tùy chỉnh',
|
|
1356
|
+
'pi.picker.group.apiKey': 'Nhà cung cấp API key',
|
|
1357
|
+
'pi.picker.empty': 'Không có mẫu nhà cung cấp phù hợp',
|
|
1358
|
+
'pi.picker.back': 'Quay lại chọn mẫu',
|
|
1346
1359
|
'pi.providers.delete': 'Xóa nhà cung cấp',
|
|
1347
1360
|
'pi.providers.addModal.title': 'Thêm nhà cung cấp',
|
|
1348
1361
|
'pi.providers.addModal.subtitle': 'Tạo nhà cung cấp Piper mới',
|
|
@@ -1372,6 +1372,19 @@ const zhTw = Object.freeze({
|
|
|
1372
1372
|
'pi.providers.models.remoteLoading': '正在取得遠端模型清單…',
|
|
1373
1373
|
'pi.providers.models.remoteFailed': '可手動輸入模型 ID。',
|
|
1374
1374
|
'pi.providers.models.noMatch': '無符合模型',
|
|
1375
|
+
'pi.providers.models.importTitle': '從端點匯入模型',
|
|
1376
|
+
'pi.providers.models.addSelected': '新增選取({count})',
|
|
1377
|
+
'pi.providers.models.name': '名稱',
|
|
1378
|
+
'pi.providers.models.reasoning': '支援推理',
|
|
1379
|
+
'pi.providers.models.contextWindow': '上下文視窗',
|
|
1380
|
+
'pi.providers.models.maxTokens': '最大輸出',
|
|
1381
|
+
'pi.providers.models.catalogFill': '自動填寫',
|
|
1382
|
+
'pi.providers.models.catalogFilling': '填寫中…',
|
|
1383
|
+
'pi.picker.searchPlaceholder': '搜尋供應商…',
|
|
1384
|
+
'pi.picker.group.custom': '自訂端點',
|
|
1385
|
+
'pi.picker.group.apiKey': 'API Key 服務商',
|
|
1386
|
+
'pi.picker.empty': '沒有符合的供應商範本',
|
|
1387
|
+
'pi.picker.back': '返回選擇範本',
|
|
1375
1388
|
'pi.providers.delete': '刪除供應商',
|
|
1376
1389
|
'pi.providers.addModal.title': '新增供應商',
|
|
1377
1390
|
'pi.providers.addModal.subtitle': '建立新的 Piper 供應商',
|
|
@@ -1372,6 +1372,19 @@ const zh = Object.freeze({
|
|
|
1372
1372
|
'pi.providers.models.remoteLoading': '正在获取远端模型列表…',
|
|
1373
1373
|
'pi.providers.models.remoteFailed': '可手动输入模型 ID。',
|
|
1374
1374
|
'pi.providers.models.noMatch': '无匹配模型',
|
|
1375
|
+
'pi.providers.models.importTitle': '从端点导入模型',
|
|
1376
|
+
'pi.providers.models.addSelected': '添加选中({count})',
|
|
1377
|
+
'pi.providers.models.name': '名称',
|
|
1378
|
+
'pi.providers.models.reasoning': '支持推理',
|
|
1379
|
+
'pi.providers.models.contextWindow': '上下文窗口',
|
|
1380
|
+
'pi.providers.models.maxTokens': '最大输出',
|
|
1381
|
+
'pi.providers.models.catalogFill': '自动填充',
|
|
1382
|
+
'pi.providers.models.catalogFilling': '填充中…',
|
|
1383
|
+
'pi.picker.searchPlaceholder': '搜索供应商…',
|
|
1384
|
+
'pi.picker.group.custom': '自定义端点',
|
|
1385
|
+
'pi.picker.group.apiKey': 'API Key 服务商',
|
|
1386
|
+
'pi.picker.empty': '没有匹配的供应商模板',
|
|
1387
|
+
'pi.picker.back': '返回选择模板',
|
|
1375
1388
|
'pi.providers.delete': '删除供应商',
|
|
1376
1389
|
'pi.providers.addModal.title': '添加供应商',
|
|
1377
1390
|
'pi.providers.addModal.subtitle': '创建新的 Piper 供应商',
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const PI_PROVIDER_TEMPLATE_GROUPS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'custom',
|
|
4
|
+
i18nKey: 'pi.picker.group.custom',
|
|
5
|
+
templates: [
|
|
6
|
+
{ id: 'custom-openai', name: 'OpenAI Compatible', baseUrl: '', api: 'openai-completions' },
|
|
7
|
+
{ id: 'custom-anthropic', name: 'Anthropic Compatible', baseUrl: '', api: 'anthropic-messages' }
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: 'apiKey',
|
|
12
|
+
i18nKey: 'pi.picker.group.apiKey',
|
|
13
|
+
templates: [
|
|
14
|
+
{ id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1', api: 'openai-responses' },
|
|
15
|
+
{ id: 'anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com', api: 'anthropic-messages' },
|
|
16
|
+
{ id: 'google-gemini', name: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta', api: 'google-generative-ai' },
|
|
17
|
+
{ id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com', api: 'openai-completions' },
|
|
18
|
+
{ id: 'moonshot-kimi', name: 'Moonshot Kimi', baseUrl: 'https://api.moonshot.cn/v1', api: 'openai-completions' },
|
|
19
|
+
{ id: 'zhipu-glm', name: 'Zhipu GLM', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', api: 'openai-completions' },
|
|
20
|
+
{ id: 'openrouter', name: 'OpenRouter', baseUrl: 'https://openrouter.ai/api/v1', api: 'openai-completions' },
|
|
21
|
+
{ id: 'xai-grok', name: 'xAI Grok', baseUrl: 'https://api.x.ai/v1', api: 'openai-completions' },
|
|
22
|
+
{ id: 'groq', name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', api: 'openai-completions' },
|
|
23
|
+
{ id: 'mistral', name: 'Mistral', baseUrl: 'https://api.mistral.ai/v1', api: 'openai-completions' }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
];
|