protocol-proxy 2.0.2 → 2.0.6
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 +91 -14
- package/public/index.html +4 -2
- package/server.js +1 -1
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -40,16 +40,17 @@ function initProviderDropdown() {
|
|
|
40
40
|
const dropdown = document.getElementById('provider-dropdown');
|
|
41
41
|
const addNameInput = document.getElementById('provider-add-name');
|
|
42
42
|
const addUrlInput = document.getElementById('provider-add-url');
|
|
43
|
-
const addKeyInput = document.getElementById('provider-add-key');
|
|
44
43
|
const addBtn = document.getElementById('provider-add-btn');
|
|
45
44
|
|
|
46
45
|
trigger.addEventListener('click', (e) => {
|
|
47
46
|
e.stopPropagation();
|
|
48
47
|
dropdown.classList.toggle('open');
|
|
49
48
|
if (dropdown.classList.contains('open')) {
|
|
49
|
+
editingProviderId = null;
|
|
50
50
|
addNameInput.value = '';
|
|
51
51
|
addUrlInput.value = '';
|
|
52
|
-
|
|
52
|
+
addUrlInput.disabled = false;
|
|
53
|
+
addBtn.textContent = '添加';
|
|
53
54
|
renderProviderOptions();
|
|
54
55
|
addNameInput.focus();
|
|
55
56
|
}
|
|
@@ -64,28 +65,44 @@ function initProviderDropdown() {
|
|
|
64
65
|
addBtn.addEventListener('click', async () => {
|
|
65
66
|
const name = addNameInput.value.trim();
|
|
66
67
|
const url = addUrlInput.value.trim();
|
|
67
|
-
const apiKey = addKeyInput.value.trim();
|
|
68
68
|
if (!name || !url) {
|
|
69
69
|
showToast('请填写供应商名称和地址', true);
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
let res;
|
|
74
|
+
if (editingProviderId) {
|
|
75
|
+
// 更新模式
|
|
76
|
+
res = await fetch(`/api/providers/${editingProviderId}`, {
|
|
77
|
+
method: 'PUT',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ name }),
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
// 新增模式
|
|
83
|
+
res = await fetch('/api/providers', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({ name, url }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
78
89
|
if (!res.ok) {
|
|
79
90
|
const err = await res.json();
|
|
80
|
-
showToast(err.error || '
|
|
91
|
+
showToast(err.error || '操作失败', true);
|
|
81
92
|
return;
|
|
82
93
|
}
|
|
83
94
|
const provider = await res.json();
|
|
95
|
+
editingProviderId = null;
|
|
96
|
+
addUrlInput.disabled = false;
|
|
97
|
+
addBtn.textContent = '添加';
|
|
98
|
+
addNameInput.value = '';
|
|
99
|
+
addUrlInput.value = '';
|
|
100
|
+
addKeyInput.value = '';
|
|
84
101
|
await loadProviders();
|
|
85
102
|
selectProvider(provider.id);
|
|
86
|
-
|
|
103
|
+
renderProviderOptions();
|
|
87
104
|
} catch (err) {
|
|
88
|
-
showToast('
|
|
105
|
+
showToast('操作失败: ' + err.message, true);
|
|
89
106
|
}
|
|
90
107
|
});
|
|
91
108
|
|
|
@@ -98,6 +115,8 @@ function initProviderDropdown() {
|
|
|
98
115
|
});
|
|
99
116
|
}
|
|
100
117
|
|
|
118
|
+
let editingProviderId = null;
|
|
119
|
+
|
|
101
120
|
function renderProviderOptions() {
|
|
102
121
|
const container = document.getElementById('provider-dropdown-options');
|
|
103
122
|
const currentId = document.getElementById('provider-id').value;
|
|
@@ -106,7 +125,10 @@ function renderProviderOptions() {
|
|
|
106
125
|
<div class="model-option${p.id === currentId ? ' selected' : ''}" data-id="${escapeHtml(p.id)}">
|
|
107
126
|
<span class="model-option-name">${escapeHtml(p.name)}</span>
|
|
108
127
|
${p.name !== p.url ? `<span style="color:#64748b;font-size:12px;margin-left:4px">${escapeHtml(p.url)}</span>` : ''}
|
|
109
|
-
<
|
|
128
|
+
<span style="margin-left:auto;display:flex;gap:4px">
|
|
129
|
+
<button type="button" class="model-option-delete" data-edit-id="${escapeHtml(p.id)}" title="编辑此供应商" style="color:#60a5fa;font-size:14px">✎</button>
|
|
130
|
+
<button type="button" class="model-option-delete" data-delete-id="${escapeHtml(p.id)}" title="删除此供应商">×</button>
|
|
131
|
+
</span>
|
|
110
132
|
</div>
|
|
111
133
|
`).join('');
|
|
112
134
|
|
|
@@ -122,7 +144,29 @@ function renderProviderOptions() {
|
|
|
122
144
|
});
|
|
123
145
|
});
|
|
124
146
|
|
|
125
|
-
|
|
147
|
+
// 编辑供应商
|
|
148
|
+
container.querySelectorAll('[data-edit-id]').forEach(btn => {
|
|
149
|
+
btn.addEventListener('click', async (e) => {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
const id = btn.dataset.editId;
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch(`/api/providers/${id}`);
|
|
154
|
+
if (!res.ok) throw new Error('加载失败');
|
|
155
|
+
const p = await res.json();
|
|
156
|
+
editingProviderId = id;
|
|
157
|
+
document.getElementById('provider-add-name').value = p.name;
|
|
158
|
+
document.getElementById('provider-add-url').value = p.url;
|
|
159
|
+
document.getElementById('provider-add-key').value = p.apiKey || '';
|
|
160
|
+
document.getElementById('provider-add-url').disabled = true;
|
|
161
|
+
document.getElementById('provider-add-btn').textContent = '更新';
|
|
162
|
+
} catch (err) {
|
|
163
|
+
showToast('加载供应商失败: ' + err.message, true);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 删除供应商
|
|
169
|
+
container.querySelectorAll('[data-delete-id]').forEach(btn => {
|
|
126
170
|
btn.addEventListener('click', async (e) => {
|
|
127
171
|
e.stopPropagation();
|
|
128
172
|
const id = btn.dataset.deleteId;
|
|
@@ -157,6 +201,14 @@ function selectProvider(id) {
|
|
|
157
201
|
: '选择供应商...';
|
|
158
202
|
renderModelOptions();
|
|
159
203
|
updateModelAddState();
|
|
204
|
+
// 加载供应商的 API Key
|
|
205
|
+
if (id) {
|
|
206
|
+
fetch(`/api/providers/${id}`).then(r => r.json()).then(p => {
|
|
207
|
+
document.getElementById('target-key').value = p.apiKey || '';
|
|
208
|
+
}).catch(() => {});
|
|
209
|
+
} else {
|
|
210
|
+
document.getElementById('target-key').value = '';
|
|
211
|
+
}
|
|
160
212
|
}
|
|
161
213
|
|
|
162
214
|
// ==================== Model 下拉框 ====================
|
|
@@ -446,6 +498,12 @@ function openModal(id = null) {
|
|
|
446
498
|
document.getElementById('auth-token-group').style.display = p.requireAuth ? 'block' : 'none';
|
|
447
499
|
selectProvider(p.providerId || '');
|
|
448
500
|
selectModel(p.defaultModel || '');
|
|
501
|
+
// 加载供应商的 API Key
|
|
502
|
+
if (p.providerId) {
|
|
503
|
+
fetch(`/api/providers/${p.providerId}`).then(r => r.json()).then(provider => {
|
|
504
|
+
document.getElementById('target-key').value = provider.apiKey || '';
|
|
505
|
+
}).catch(() => {});
|
|
506
|
+
}
|
|
449
507
|
} else {
|
|
450
508
|
document.getElementById('proxy-id').value = '';
|
|
451
509
|
document.getElementById('auth-token-group').style.display = 'none';
|
|
@@ -481,13 +539,32 @@ async function handleSubmit(e) {
|
|
|
481
539
|
return;
|
|
482
540
|
}
|
|
483
541
|
|
|
542
|
+
const apiKey = document.getElementById('target-key').value.trim();
|
|
543
|
+
const protocol = document.getElementById('target-protocol').value;
|
|
544
|
+
const defaultModel = document.getElementById('target-model').value.trim() || '';
|
|
545
|
+
|
|
546
|
+
// 同步更新供应商配置
|
|
547
|
+
const providerUpdates = {};
|
|
548
|
+
if (apiKey) providerUpdates.apiKey = apiKey;
|
|
549
|
+
if (protocol) providerUpdates.protocol = protocol;
|
|
550
|
+
if (Object.keys(providerUpdates).length > 0) {
|
|
551
|
+
try {
|
|
552
|
+
await fetch(`/api/providers/${providerId}`, {
|
|
553
|
+
method: 'PUT',
|
|
554
|
+
headers: { 'Content-Type': 'application/json' },
|
|
555
|
+
body: JSON.stringify(providerUpdates),
|
|
556
|
+
});
|
|
557
|
+
await loadProviders();
|
|
558
|
+
} catch {}
|
|
559
|
+
}
|
|
560
|
+
|
|
484
561
|
const payload = {
|
|
485
562
|
name: document.getElementById('proxy-name').value.trim(),
|
|
486
563
|
port,
|
|
487
564
|
requireAuth: document.getElementById('proxy-auth').value === 'true',
|
|
488
565
|
authToken: document.getElementById('proxy-auth-token').value.trim() || null,
|
|
489
566
|
providerId,
|
|
490
|
-
defaultModel
|
|
567
|
+
defaultModel,
|
|
491
568
|
};
|
|
492
569
|
|
|
493
570
|
try {
|
package/public/index.html
CHANGED
|
@@ -85,7 +85,6 @@
|
|
|
85
85
|
<div class="model-add-section">
|
|
86
86
|
<input type="text" class="model-add-input" id="provider-add-name" placeholder="供应商名称">
|
|
87
87
|
<input type="url" class="model-add-input" id="provider-add-url" placeholder="https://api.example.com">
|
|
88
|
-
<input type="password" class="model-add-input" id="provider-add-key" placeholder="API Key">
|
|
89
88
|
<button type="button" class="btn btn-primary btn-sm" id="provider-add-btn">添加</button>
|
|
90
89
|
</div>
|
|
91
90
|
</div>
|
|
@@ -93,7 +92,10 @@
|
|
|
93
92
|
</div>
|
|
94
93
|
<div class="form-group">
|
|
95
94
|
<label>协议</label>
|
|
96
|
-
<
|
|
95
|
+
<select id="target-protocol">
|
|
96
|
+
<option value="openai">OpenAI</option>
|
|
97
|
+
<option value="anthropic">Anthropic</option>
|
|
98
|
+
</select>
|
|
97
99
|
</div>
|
|
98
100
|
</div>
|
|
99
101
|
<div class="form-row">
|
package/server.js
CHANGED
|
@@ -198,7 +198,7 @@ async function init() {
|
|
|
198
198
|
if (req.body.name !== undefined) updates.name = req.body.name;
|
|
199
199
|
if (req.body.url !== undefined) updates.url = req.body.url;
|
|
200
200
|
if (req.body.protocol !== undefined) updates.protocol = req.body.protocol;
|
|
201
|
-
if (req.body.apiKey !== undefined) updates.apiKey = req.body.apiKey;
|
|
201
|
+
if (req.body.apiKey !== undefined && req.body.apiKey !== '') updates.apiKey = req.body.apiKey;
|
|
202
202
|
if (req.body.models !== undefined) updates.models = req.body.models;
|
|
203
203
|
|
|
204
204
|
const updated = configStore.updateProvider(req.params.id, updates);
|