protocol-proxy 1.0.5 → 1.1.1
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/package.json +1 -1
- package/public/app.js +233 -66
- package/public/index.html +17 -6
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1,63 +1,188 @@
|
|
|
1
1
|
let proxies = [];
|
|
2
2
|
let editingId = null;
|
|
3
3
|
|
|
4
|
-
// ====================
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
// ==================== 供应商管理(全局共享) ====================
|
|
5
|
+
const PROVIDERS_KEY = 'protocol-proxy-providers';
|
|
6
|
+
|
|
7
|
+
function loadProviders() {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(localStorage.getItem(PROVIDERS_KEY)) || [];
|
|
10
|
+
} catch { return []; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function saveProviders(providers) {
|
|
14
|
+
localStorage.setItem(PROVIDERS_KEY, JSON.stringify(providers));
|
|
7
15
|
}
|
|
8
16
|
|
|
9
|
-
function
|
|
10
|
-
|
|
17
|
+
function addProvider(name, url) {
|
|
18
|
+
const providers = loadProviders();
|
|
19
|
+
if (providers.some(p => p.url === url)) return;
|
|
20
|
+
providers.push({ id: 'p-' + Date.now(), name, url, protocol: detectProtocol(url) });
|
|
21
|
+
saveProviders(providers);
|
|
11
22
|
}
|
|
12
23
|
|
|
13
|
-
function
|
|
14
|
-
|
|
24
|
+
function findProviderByUrl(url) {
|
|
25
|
+
return loadProviders().find(p => p.url === url);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getProviderDisplayName(url) {
|
|
29
|
+
const p = findProviderByUrl(url);
|
|
30
|
+
return p ? p.name : url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function detectProtocol(url) {
|
|
34
|
+
return /anthropic/i.test(url) ? 'anthropic' : 'openai';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ==================== Model 管理(按供应商 URL) ====================
|
|
38
|
+
function getModelKey(providerUrl) {
|
|
39
|
+
return providerUrl ? `protocol-proxy-models-${providerUrl}` : 'protocol-proxy-models-__new';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadModelsByProvider(providerUrl) {
|
|
43
|
+
const saved = localStorage.getItem(getModelKey(providerUrl));
|
|
15
44
|
if (saved) {
|
|
16
45
|
try { return JSON.parse(saved); } catch { /* fall through */ }
|
|
17
46
|
}
|
|
18
47
|
return [];
|
|
19
48
|
}
|
|
20
49
|
|
|
21
|
-
function
|
|
22
|
-
const proxy = proxyId ? proxies.find(p => p.id === proxyId) : null;
|
|
23
|
-
const serverModels = proxy?.target?.models;
|
|
24
|
-
if (Array.isArray(serverModels)) {
|
|
25
|
-
return serverModels.filter(Boolean);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return loadLegacyModels(proxyId);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function saveModels(proxyId, models) {
|
|
50
|
+
function saveModelsByProvider(providerUrl, models) {
|
|
32
51
|
const normalized = Array.from(new Set((models || []).map(m => m.trim()).filter(Boolean)));
|
|
33
|
-
|
|
34
|
-
const proxy = proxies.find(p => p.id === proxyId);
|
|
35
|
-
if (proxy) {
|
|
36
|
-
proxy.target = proxy.target || {};
|
|
37
|
-
proxy.target.models = normalized;
|
|
38
|
-
}
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
localStorage.setItem(getLegacyModelKey(proxyId), JSON.stringify(normalized));
|
|
52
|
+
localStorage.setItem(getModelKey(providerUrl), JSON.stringify(normalized));
|
|
42
53
|
}
|
|
43
54
|
|
|
44
|
-
function addModel(
|
|
45
|
-
|
|
55
|
+
function addModel(providerUrl, name) {
|
|
56
|
+
if (!providerUrl) return;
|
|
57
|
+
const models = loadModelsByProvider(providerUrl);
|
|
46
58
|
if (!models.includes(name)) {
|
|
47
59
|
models.push(name);
|
|
48
|
-
|
|
60
|
+
saveModelsByProvider(providerUrl, models);
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
function removeModel(
|
|
53
|
-
const models =
|
|
54
|
-
|
|
64
|
+
function removeModel(providerUrl, name) {
|
|
65
|
+
const models = loadModelsByProvider(providerUrl).filter(m => m !== name);
|
|
66
|
+
saveModelsByProvider(providerUrl, models);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
function getCurrentProxyId() {
|
|
58
70
|
return document.getElementById('modal').dataset.proxyId || null;
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
function getSelectedProviderUrl() {
|
|
74
|
+
return document.getElementById('target-url').value || '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ==================== 供应商下拉框 ====================
|
|
78
|
+
function initProviderDropdown() {
|
|
79
|
+
const trigger = document.getElementById('provider-dropdown-trigger');
|
|
80
|
+
const dropdown = document.getElementById('provider-dropdown');
|
|
81
|
+
const addNameInput = document.getElementById('provider-add-name');
|
|
82
|
+
const addUrlInput = document.getElementById('provider-add-url');
|
|
83
|
+
const addBtn = document.getElementById('provider-add-btn');
|
|
84
|
+
|
|
85
|
+
trigger.addEventListener('click', (e) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
dropdown.classList.toggle('open');
|
|
88
|
+
if (dropdown.classList.contains('open')) {
|
|
89
|
+
addNameInput.value = '';
|
|
90
|
+
addUrlInput.value = '';
|
|
91
|
+
renderProviderOptions();
|
|
92
|
+
addNameInput.focus();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
document.addEventListener('click', (e) => {
|
|
97
|
+
if (!dropdown.contains(e.target)) {
|
|
98
|
+
dropdown.classList.remove('open');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
addBtn.addEventListener('click', () => {
|
|
103
|
+
const name = addNameInput.value.trim();
|
|
104
|
+
const url = addUrlInput.value.trim();
|
|
105
|
+
if (!name || !url) {
|
|
106
|
+
showToast('请填写供应商名称和地址', true);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
addProvider(name, url);
|
|
110
|
+
selectProvider(url);
|
|
111
|
+
dropdown.classList.remove('open');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
addUrlInput.addEventListener('keydown', (e) => {
|
|
115
|
+
if (e.key === 'Enter') {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
addBtn.click();
|
|
118
|
+
}
|
|
119
|
+
if (e.key === 'Escape') {
|
|
120
|
+
dropdown.classList.remove('open');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
addNameInput.addEventListener('keydown', (e) => {
|
|
125
|
+
if (e.key === 'Escape') {
|
|
126
|
+
dropdown.classList.remove('open');
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function renderProviderOptions() {
|
|
132
|
+
const container = document.getElementById('provider-dropdown-options');
|
|
133
|
+
const providers = loadProviders();
|
|
134
|
+
const currentUrl = getSelectedProviderUrl();
|
|
135
|
+
|
|
136
|
+
container.innerHTML = providers.map(p => `
|
|
137
|
+
<div class="model-option${p.url === currentUrl ? ' selected' : ''}" data-url="${escapeHtml(p.url)}">
|
|
138
|
+
<span class="model-option-name">${escapeHtml(p.name)}</span>
|
|
139
|
+
<span style="color:#64748b;font-size:12px;margin-left:4px">${escapeHtml(p.url)}</span>
|
|
140
|
+
<button type="button" class="model-option-delete" data-delete-url="${escapeHtml(p.url)}" title="删除此供应商">×</button>
|
|
141
|
+
</div>
|
|
142
|
+
`).join('');
|
|
143
|
+
|
|
144
|
+
if (providers.length === 0) {
|
|
145
|
+
container.innerHTML = '<div style="padding:8px 12px;color:#64748b;font-size:13px">暂无供应商,请在下方添加</div>';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
container.querySelectorAll('.model-option').forEach(opt => {
|
|
149
|
+
opt.addEventListener('click', (e) => {
|
|
150
|
+
if (e.target.closest('.model-option-delete')) return;
|
|
151
|
+
selectProvider(opt.dataset.url);
|
|
152
|
+
document.getElementById('provider-dropdown').classList.remove('open');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
container.querySelectorAll('.model-option-delete').forEach(btn => {
|
|
157
|
+
btn.addEventListener('click', async (e) => {
|
|
158
|
+
e.stopPropagation();
|
|
159
|
+
const url = btn.dataset.deleteUrl;
|
|
160
|
+
const p = findProviderByUrl(url);
|
|
161
|
+
const ok = await showConfirm(`确定要删除供应商 <strong>${escapeHtml(p?.name || url)}</strong> 吗?`);
|
|
162
|
+
if (!ok) return;
|
|
163
|
+
const providers = loadProviders().filter(pr => pr.url !== url);
|
|
164
|
+
saveProviders(providers);
|
|
165
|
+
if (getSelectedProviderUrl() === url) {
|
|
166
|
+
selectProvider('');
|
|
167
|
+
}
|
|
168
|
+
renderProviderOptions();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function selectProvider(url) {
|
|
174
|
+
document.getElementById('target-url').value = url || '';
|
|
175
|
+
const provider = findProviderByUrl(url);
|
|
176
|
+
document.getElementById('target-protocol').value = url ? (provider?.protocol || detectProtocol(url)) : '';
|
|
177
|
+
document.getElementById('provider-dropdown-value').textContent = url
|
|
178
|
+
? (provider ? `${provider.name} - ${url}` : url)
|
|
179
|
+
: '选择供应商...';
|
|
180
|
+
// 切换供应商后刷新模型列表
|
|
181
|
+
renderModelOptions();
|
|
182
|
+
updateModelAddState();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ==================== Model 下拉框 ====================
|
|
61
186
|
function initModelDropdown() {
|
|
62
187
|
const trigger = document.getElementById('model-dropdown-trigger');
|
|
63
188
|
const dropdown = document.getElementById('model-dropdown');
|
|
@@ -80,10 +205,14 @@ function initModelDropdown() {
|
|
|
80
205
|
});
|
|
81
206
|
|
|
82
207
|
addBtn.addEventListener('click', () => {
|
|
208
|
+
const providerUrl = getSelectedProviderUrl();
|
|
209
|
+
if (!providerUrl) {
|
|
210
|
+
showToast('请先选择供应商地址', true);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
83
213
|
const name = addInput.value.trim();
|
|
84
214
|
if (!name) return;
|
|
85
|
-
|
|
86
|
-
addModel(proxyId, name);
|
|
215
|
+
addModel(providerUrl, name);
|
|
87
216
|
selectModel(name);
|
|
88
217
|
renderModelOptions();
|
|
89
218
|
addInput.value = '';
|
|
@@ -103,8 +232,8 @@ function initModelDropdown() {
|
|
|
103
232
|
|
|
104
233
|
function renderModelOptions() {
|
|
105
234
|
const container = document.getElementById('model-dropdown-options');
|
|
106
|
-
const
|
|
107
|
-
const models =
|
|
235
|
+
const providerUrl = getSelectedProviderUrl();
|
|
236
|
+
const models = providerUrl ? loadModelsByProvider(providerUrl) : [];
|
|
108
237
|
const current = document.getElementById('target-model').value;
|
|
109
238
|
|
|
110
239
|
container.innerHTML = models.map(m => `
|
|
@@ -114,6 +243,12 @@ function renderModelOptions() {
|
|
|
114
243
|
</div>
|
|
115
244
|
`).join('');
|
|
116
245
|
|
|
246
|
+
if (!providerUrl) {
|
|
247
|
+
container.innerHTML = '<div style="padding:8px 12px;color:#64748b;font-size:13px">请先选择供应商</div>';
|
|
248
|
+
} else if (models.length === 0) {
|
|
249
|
+
container.innerHTML = '<div style="padding:8px 12px;color:#64748b;font-size:13px">暂无模型,请在下方添加</div>';
|
|
250
|
+
}
|
|
251
|
+
|
|
117
252
|
container.querySelectorAll('.model-option').forEach(opt => {
|
|
118
253
|
opt.addEventListener('click', (e) => {
|
|
119
254
|
if (e.target.closest('.model-option-delete')) return;
|
|
@@ -128,8 +263,7 @@ function renderModelOptions() {
|
|
|
128
263
|
const name = btn.dataset.delete;
|
|
129
264
|
const ok = await showConfirm(`确定要删除模型 <strong>${escapeHtml(name)}</strong> 吗?`);
|
|
130
265
|
if (!ok) return;
|
|
131
|
-
|
|
132
|
-
removeModel(pid, name);
|
|
266
|
+
removeModel(getSelectedProviderUrl(), name);
|
|
133
267
|
if (document.getElementById('target-model').value === name) {
|
|
134
268
|
selectModel('');
|
|
135
269
|
}
|
|
@@ -144,9 +278,25 @@ function selectModel(value) {
|
|
|
144
278
|
renderModelOptions();
|
|
145
279
|
}
|
|
146
280
|
|
|
281
|
+
function updateModelAddState() {
|
|
282
|
+
const section = document.getElementById('model-add-section');
|
|
283
|
+
const providerUrl = getSelectedProviderUrl();
|
|
284
|
+
const addInput = document.getElementById('model-add-input');
|
|
285
|
+
const addBtn = document.getElementById('model-add-btn');
|
|
286
|
+
if (providerUrl) {
|
|
287
|
+
addInput.disabled = false;
|
|
288
|
+
addBtn.disabled = false;
|
|
289
|
+
addInput.placeholder = '输入模型名称';
|
|
290
|
+
} else {
|
|
291
|
+
addInput.disabled = true;
|
|
292
|
+
addBtn.disabled = true;
|
|
293
|
+
addInput.placeholder = '请先选择供应商';
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
147
297
|
function getSelectedModels() {
|
|
148
|
-
const
|
|
149
|
-
const models = [...
|
|
298
|
+
const providerUrl = getSelectedProviderUrl();
|
|
299
|
+
const models = providerUrl ? [...loadModelsByProvider(providerUrl)] : [];
|
|
150
300
|
const current = document.getElementById('target-model').value.trim();
|
|
151
301
|
if (current && !models.includes(current)) {
|
|
152
302
|
models.unshift(current);
|
|
@@ -163,6 +313,7 @@ function generateToken() {
|
|
|
163
313
|
|
|
164
314
|
async function init() {
|
|
165
315
|
await loadProxies();
|
|
316
|
+
initProviderDropdown();
|
|
166
317
|
initModelDropdown();
|
|
167
318
|
document.getElementById('proxy-auth').addEventListener('change', (e) => {
|
|
168
319
|
const enabled = e.target.value === 'true';
|
|
@@ -253,6 +404,7 @@ function renderProxies() {
|
|
|
253
404
|
|
|
254
405
|
container.innerHTML = proxies.map(p => {
|
|
255
406
|
const t = p.target || {};
|
|
407
|
+
const providerName = getProviderDisplayName(t.providerUrl || '');
|
|
256
408
|
return `
|
|
257
409
|
<div class="proxy-item">
|
|
258
410
|
<div class="proxy-header">
|
|
@@ -277,17 +429,17 @@ function renderProxies() {
|
|
|
277
429
|
<table class="target-table">
|
|
278
430
|
<thead>
|
|
279
431
|
<tr>
|
|
280
|
-
<th
|
|
432
|
+
<th>供应商</th>
|
|
281
433
|
<th>协议</th>
|
|
282
434
|
<th>默认 Model</th>
|
|
283
435
|
</tr>
|
|
284
436
|
</thead>
|
|
285
437
|
<tbody>
|
|
286
438
|
<tr>
|
|
287
|
-
<td>${escapeHtml(t.providerUrl)}</td>
|
|
439
|
+
<td>${escapeHtml(providerName)}${providerName !== t.providerUrl ? ` <span style="color:#64748b;font-size:12px">(${escapeHtml(t.providerUrl)})</span>` : ''}</td>
|
|
288
440
|
<td>
|
|
289
441
|
<span class="badge" style="background:${t.protocol==='openai'?'#0c4a6e':'#581c87'};color:${t.protocol==='openai'?'#7dd3fc':'#e9d5ff'}">
|
|
290
|
-
${t.protocol}
|
|
442
|
+
${t.protocol || '-'}
|
|
291
443
|
</span>
|
|
292
444
|
</td>
|
|
293
445
|
<td><code>${escapeHtml(t.defaultModel) || '-'}</code></td>
|
|
@@ -323,36 +475,62 @@ function openModal(id = null) {
|
|
|
323
475
|
document.getElementById('proxy-auth').value = p.requireAuth ? 'true' : 'false';
|
|
324
476
|
document.getElementById('proxy-auth-token').value = p.authToken || '';
|
|
325
477
|
document.getElementById('auth-token-group').style.display = p.requireAuth ? 'block' : 'none';
|
|
326
|
-
document.getElementById('target-url').value = t.providerUrl || '';
|
|
327
|
-
document.getElementById('target-protocol').value = t.protocol || 'openai';
|
|
328
478
|
document.getElementById('target-key').value = t.apiKey || '';
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
479
|
+
|
|
480
|
+
// 自动注册供应商到全局列表
|
|
481
|
+
if (t.providerUrl && !findProviderByUrl(t.providerUrl)) {
|
|
482
|
+
addProvider(getProviderDisplayName(t.providerUrl), t.providerUrl);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 迁移旧模型数据:从按代理ID存储 → 按供应商URL存储
|
|
486
|
+
if (t.providerUrl) {
|
|
487
|
+
const existingByProvider = loadModelsByProvider(t.providerUrl);
|
|
488
|
+
if (existingByProvider.length === 0) {
|
|
489
|
+
// 优先用服务端的模型列表
|
|
490
|
+
let modelsToMigrate = Array.isArray(t.models) ? t.models.filter(Boolean) : [];
|
|
491
|
+
// 兼容旧版 localStorage(按代理ID存储)
|
|
492
|
+
if (modelsToMigrate.length === 0) {
|
|
493
|
+
const legacyKey = `protocol-proxy-models-${id}`;
|
|
494
|
+
try {
|
|
495
|
+
const legacy = JSON.parse(localStorage.getItem(legacyKey));
|
|
496
|
+
if (Array.isArray(legacy)) modelsToMigrate = legacy;
|
|
497
|
+
} catch {}
|
|
498
|
+
}
|
|
499
|
+
if (modelsToMigrate.length > 0) {
|
|
500
|
+
saveModelsByProvider(t.providerUrl, modelsToMigrate);
|
|
501
|
+
}
|
|
334
502
|
}
|
|
335
503
|
}
|
|
504
|
+
|
|
505
|
+
selectProvider(t.providerUrl || '');
|
|
336
506
|
selectModel(t.defaultModel || '');
|
|
337
507
|
} else {
|
|
338
508
|
document.getElementById('proxy-id').value = '';
|
|
339
509
|
document.getElementById('auth-token-group').style.display = 'none';
|
|
340
|
-
|
|
341
|
-
selectModel(
|
|
510
|
+
selectProvider('');
|
|
511
|
+
selectModel('');
|
|
342
512
|
}
|
|
343
513
|
|
|
514
|
+
updateModelAddState();
|
|
344
515
|
document.getElementById('modal').classList.add('active');
|
|
345
516
|
}
|
|
346
517
|
|
|
347
518
|
function closeModal() {
|
|
348
519
|
document.getElementById('modal').classList.remove('active');
|
|
349
520
|
document.getElementById('model-dropdown').classList.remove('open');
|
|
521
|
+
document.getElementById('provider-dropdown').classList.remove('open');
|
|
350
522
|
editingId = null;
|
|
351
523
|
}
|
|
352
524
|
|
|
353
525
|
async function handleSubmit(e) {
|
|
354
526
|
e.preventDefault();
|
|
355
527
|
|
|
528
|
+
const providerUrl = getSelectedProviderUrl();
|
|
529
|
+
if (!providerUrl) {
|
|
530
|
+
showToast('请选择供应商地址', true);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
356
534
|
const port = parseInt(document.getElementById('proxy-port').value);
|
|
357
535
|
|
|
358
536
|
// 前端端口冲突校验
|
|
@@ -363,8 +541,8 @@ async function handleSubmit(e) {
|
|
|
363
541
|
}
|
|
364
542
|
|
|
365
543
|
const target = {
|
|
366
|
-
providerUrl
|
|
367
|
-
protocol:
|
|
544
|
+
providerUrl,
|
|
545
|
+
protocol: detectProtocol(providerUrl),
|
|
368
546
|
defaultModel: document.getElementById('target-model').value.trim() || undefined,
|
|
369
547
|
models: getSelectedModels(),
|
|
370
548
|
apiKey: document.getElementById('target-key').value.trim(),
|
|
@@ -395,15 +573,6 @@ async function handleSubmit(e) {
|
|
|
395
573
|
return;
|
|
396
574
|
}
|
|
397
575
|
|
|
398
|
-
// 新建代理后,将临时模型列表迁移到真实 ID 下
|
|
399
|
-
if (!editingId && result.id) {
|
|
400
|
-
const tempModels = loadModels(null);
|
|
401
|
-
if (tempModels.length > 0) {
|
|
402
|
-
saveModels(result.id, tempModels);
|
|
403
|
-
}
|
|
404
|
-
localStorage.removeItem(getModelKey(null));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
576
|
closeModal();
|
|
408
577
|
await loadProxies();
|
|
409
578
|
} catch (err) {
|
|
@@ -437,7 +606,6 @@ async function deleteProxy(id) {
|
|
|
437
606
|
if (!ok) return;
|
|
438
607
|
try {
|
|
439
608
|
await fetch(`/api/proxies/${id}`, { method: 'DELETE' });
|
|
440
|
-
localStorage.removeItem(getModelKey(id));
|
|
441
609
|
await loadProxies();
|
|
442
610
|
} catch (err) {
|
|
443
611
|
showToast('删除失败: ' + err.message, true);
|
|
@@ -449,7 +617,6 @@ async function editProxy(id) {
|
|
|
449
617
|
const res = await fetch(`/api/proxies/${id}`);
|
|
450
618
|
if (!res.ok) throw new Error('加载失败');
|
|
451
619
|
const full = await res.json();
|
|
452
|
-
// 用完整数据替换列表中的掩码数据
|
|
453
620
|
const idx = proxies.findIndex(p => p.id === id);
|
|
454
621
|
if (idx !== -1) proxies[idx] = { ...proxies[idx], ...full };
|
|
455
622
|
openModal(id);
|
package/public/index.html
CHANGED
|
@@ -74,14 +74,25 @@
|
|
|
74
74
|
<div class="form-row">
|
|
75
75
|
<div class="form-group">
|
|
76
76
|
<label>供应商地址</label>
|
|
77
|
-
<input type="
|
|
77
|
+
<input type="hidden" id="target-url">
|
|
78
|
+
<div class="model-dropdown" id="provider-dropdown">
|
|
79
|
+
<div class="model-dropdown-trigger" id="provider-dropdown-trigger">
|
|
80
|
+
<span id="provider-dropdown-value">选择供应商...</span>
|
|
81
|
+
<span class="model-dropdown-arrow">▾</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="model-dropdown-menu" id="provider-dropdown-menu">
|
|
84
|
+
<div class="model-dropdown-options" id="provider-dropdown-options"></div>
|
|
85
|
+
<div class="model-add-section">
|
|
86
|
+
<input type="text" class="model-add-input" id="provider-add-name" placeholder="供应商名称">
|
|
87
|
+
<input type="url" class="model-add-input" id="provider-add-url" placeholder="https://api.example.com">
|
|
88
|
+
<button type="button" class="btn btn-primary btn-sm" id="provider-add-btn">添加</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
78
92
|
</div>
|
|
79
93
|
<div class="form-group">
|
|
80
94
|
<label>目标协议</label>
|
|
81
|
-
<
|
|
82
|
-
<option value="openai">OpenAI</option>
|
|
83
|
-
<option value="anthropic">Anthropic</option>
|
|
84
|
-
</select>
|
|
95
|
+
<input type="text" id="target-protocol" readonly placeholder="自动识别" style="background:#1e293b;color:#94a3b8;cursor:not-allowed">
|
|
85
96
|
</div>
|
|
86
97
|
</div>
|
|
87
98
|
<div class="form-row">
|
|
@@ -95,7 +106,7 @@
|
|
|
95
106
|
</div>
|
|
96
107
|
<div class="model-dropdown-menu" id="model-dropdown-menu">
|
|
97
108
|
<div class="model-dropdown-options" id="model-dropdown-options"></div>
|
|
98
|
-
<div class="model-add-section">
|
|
109
|
+
<div class="model-add-section" id="model-add-section">
|
|
99
110
|
<input type="text" class="model-add-input" id="model-add-input" placeholder="输入模型名称">
|
|
100
111
|
<button type="button" class="btn btn-primary btn-sm" id="model-add-btn">添加</button>
|
|
101
112
|
</div>
|