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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protocol-proxy",
3
- "version": "2.0.2",
3
+ "version": "2.0.6",
4
4
  "description": "OpenAI / Anthropic 协议转换透明代理",
5
5
  "main": "server.js",
6
6
  "bin": {
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
- addKeyInput.value = '';
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
- const res = await fetch('/api/providers', {
74
- method: 'POST',
75
- headers: { 'Content-Type': 'application/json' },
76
- body: JSON.stringify({ name, url, apiKey }),
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 || '添加失败', true);
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
- dropdown.classList.remove('open');
103
+ renderProviderOptions();
87
104
  } catch (err) {
88
- showToast('添加失败: ' + err.message, true);
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
- <button type="button" class="model-option-delete" data-delete-id="${escapeHtml(p.id)}" title="删除此供应商">&times;</button>
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">&#9998;</button>
130
+ <button type="button" class="model-option-delete" data-delete-id="${escapeHtml(p.id)}" title="删除此供应商">&times;</button>
131
+ </span>
110
132
  </div>
111
133
  `).join('');
112
134
 
@@ -122,7 +144,29 @@ function renderProviderOptions() {
122
144
  });
123
145
  });
124
146
 
125
- container.querySelectorAll('.model-option-delete').forEach(btn => {
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: document.getElementById('target-model').value.trim() || '',
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
- <input type="text" id="target-protocol" readonly placeholder="自动识别" style="background:#1e293b;color:#94a3b8;cursor:not-allowed">
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);