protocol-proxy 1.0.5 → 1.1.0
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 +213 -67
- 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,41 @@ 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
|
-
p.target.models = legacyModels;
|
|
334
|
-
}
|
|
479
|
+
|
|
480
|
+
// 自动注册供应商到全局列表
|
|
481
|
+
if (t.providerUrl && !findProviderByUrl(t.providerUrl)) {
|
|
482
|
+
addProvider(getProviderDisplayName(t.providerUrl), t.providerUrl);
|
|
335
483
|
}
|
|
484
|
+
selectProvider(t.providerUrl || '');
|
|
336
485
|
selectModel(t.defaultModel || '');
|
|
337
486
|
} else {
|
|
338
487
|
document.getElementById('proxy-id').value = '';
|
|
339
488
|
document.getElementById('auth-token-group').style.display = 'none';
|
|
340
|
-
|
|
341
|
-
selectModel(
|
|
489
|
+
selectProvider('');
|
|
490
|
+
selectModel('');
|
|
342
491
|
}
|
|
343
492
|
|
|
493
|
+
updateModelAddState();
|
|
344
494
|
document.getElementById('modal').classList.add('active');
|
|
345
495
|
}
|
|
346
496
|
|
|
347
497
|
function closeModal() {
|
|
348
498
|
document.getElementById('modal').classList.remove('active');
|
|
349
499
|
document.getElementById('model-dropdown').classList.remove('open');
|
|
500
|
+
document.getElementById('provider-dropdown').classList.remove('open');
|
|
350
501
|
editingId = null;
|
|
351
502
|
}
|
|
352
503
|
|
|
353
504
|
async function handleSubmit(e) {
|
|
354
505
|
e.preventDefault();
|
|
355
506
|
|
|
507
|
+
const providerUrl = getSelectedProviderUrl();
|
|
508
|
+
if (!providerUrl) {
|
|
509
|
+
showToast('请选择供应商地址', true);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
356
513
|
const port = parseInt(document.getElementById('proxy-port').value);
|
|
357
514
|
|
|
358
515
|
// 前端端口冲突校验
|
|
@@ -363,8 +520,8 @@ async function handleSubmit(e) {
|
|
|
363
520
|
}
|
|
364
521
|
|
|
365
522
|
const target = {
|
|
366
|
-
providerUrl
|
|
367
|
-
protocol:
|
|
523
|
+
providerUrl,
|
|
524
|
+
protocol: detectProtocol(providerUrl),
|
|
368
525
|
defaultModel: document.getElementById('target-model').value.trim() || undefined,
|
|
369
526
|
models: getSelectedModels(),
|
|
370
527
|
apiKey: document.getElementById('target-key').value.trim(),
|
|
@@ -395,15 +552,6 @@ async function handleSubmit(e) {
|
|
|
395
552
|
return;
|
|
396
553
|
}
|
|
397
554
|
|
|
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
555
|
closeModal();
|
|
408
556
|
await loadProxies();
|
|
409
557
|
} catch (err) {
|
|
@@ -437,7 +585,6 @@ async function deleteProxy(id) {
|
|
|
437
585
|
if (!ok) return;
|
|
438
586
|
try {
|
|
439
587
|
await fetch(`/api/proxies/${id}`, { method: 'DELETE' });
|
|
440
|
-
localStorage.removeItem(getModelKey(id));
|
|
441
588
|
await loadProxies();
|
|
442
589
|
} catch (err) {
|
|
443
590
|
showToast('删除失败: ' + err.message, true);
|
|
@@ -449,7 +596,6 @@ async function editProxy(id) {
|
|
|
449
596
|
const res = await fetch(`/api/proxies/${id}`);
|
|
450
597
|
if (!res.ok) throw new Error('加载失败');
|
|
451
598
|
const full = await res.json();
|
|
452
|
-
// 用完整数据替换列表中的掩码数据
|
|
453
599
|
const idx = proxies.findIndex(p => p.id === id);
|
|
454
600
|
if (idx !== -1) proxies[idx] = { ...proxies[idx], ...full };
|
|
455
601
|
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>
|