protocol-proxy 2.1.4 → 2.1.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.
@@ -122,14 +122,13 @@ function convertMessage(msg, idMap) {
122
122
  }
123
123
 
124
124
  // user 消息含 tool_result → 拆分为多个 tool 消息
125
+ // OpenAI 要求 tool 消息紧跟 assistant tool_calls,user text 放在 tool 之后
125
126
  if (msg.role === 'user' && toolResults.length > 0) {
126
127
  const result = [];
127
- // OpenAI 要求 tool 消息紧跟 assistant,user text 如果有的话应该放在 tool 之前
128
- // 但通常 tool_result 消息不会有额外的 user text
128
+ result.push(...toolResults);
129
129
  if (textParts.length > 0) {
130
130
  result.push({ role: 'user', content: textParts.join('') });
131
131
  }
132
- result.push(...toolResults);
133
132
  return result;
134
133
  }
135
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protocol-proxy",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "OpenAI / Anthropic 协议转换透明代理",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -27,7 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "cors": "^2.8.5",
30
- "express": "^4.19.2"
30
+ "express": "^4.19.2",
31
+ "puppeteer-core": "^24.43.0"
31
32
  },
32
33
  "devDependencies": {
33
34
  "pkg": "^5.8.1"
@@ -43,9 +44,7 @@
43
44
  "node20-win-x64"
44
45
  ],
45
46
  "outputPath": "dist",
46
- "options": [
47
-
48
- ]
47
+ "options": []
49
48
  },
50
49
  "engines": {
51
50
  "node": ">=20.0.0"
package/public/app.js CHANGED
@@ -193,7 +193,17 @@ function renderProviderOptions() {
193
193
  function selectProvider(id) {
194
194
  const provider = providers.find(p => p.id === id);
195
195
  document.getElementById('provider-id').value = id || '';
196
- document.getElementById('target-protocol').value = provider ? provider.protocol : '';
196
+ const protocol = provider ? provider.protocol : '';
197
+ document.getElementById('target-protocol').value = protocol;
198
+ // 同步协议自定义下拉框
199
+ document.querySelectorAll('#protocol-dropdown .model-option').forEach(o => o.classList.remove('selected'));
200
+ const protoOpt = document.querySelector(`#protocol-dropdown .model-option[data-value="${protocol}"]`);
201
+ if (protoOpt) {
202
+ protoOpt.classList.add('selected');
203
+ document.getElementById('protocol-dropdown-value').textContent = protoOpt.querySelector('.model-option-name').textContent;
204
+ } else {
205
+ document.getElementById('protocol-dropdown-value').textContent = '选择协议...';
206
+ }
197
207
  document.getElementById('provider-dropdown-value').textContent = provider
198
208
  ? (provider.name !== provider.url ? `${provider.name} - ${provider.url}` : provider.url)
199
209
  : '选择供应商...';
@@ -343,17 +353,49 @@ function generateToken() {
343
353
  return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');
344
354
  }
345
355
 
356
+ function initSimpleDropdown(dropdownId, onChange) {
357
+ const dropdown = document.getElementById(dropdownId);
358
+ const trigger = dropdown.querySelector('.model-dropdown-trigger');
359
+ const valueEl = dropdown.querySelector('[id$="-dropdown-value"]');
360
+ const hiddenInput = document.getElementById(dropdownId.replace('-dropdown', ''));
361
+ const opts = dropdown.querySelectorAll('.model-option');
362
+
363
+ trigger.addEventListener('click', (e) => {
364
+ e.stopPropagation();
365
+ dropdown.classList.toggle('open');
366
+ });
367
+
368
+ document.addEventListener('click', (e) => {
369
+ if (!dropdown.contains(e.target)) {
370
+ dropdown.classList.remove('open');
371
+ }
372
+ });
373
+
374
+ opts.forEach(opt => {
375
+ opt.addEventListener('click', () => {
376
+ opts.forEach(o => o.classList.remove('selected'));
377
+ opt.classList.add('selected');
378
+ const val = opt.dataset.value;
379
+ if (hiddenInput) hiddenInput.value = val;
380
+ valueEl.textContent = opt.querySelector('.model-option-name').textContent;
381
+ onChange?.(val);
382
+ dropdown.classList.remove('open');
383
+ });
384
+ });
385
+ }
386
+
346
387
  async function init() {
347
388
  await Promise.all([loadProxies(), loadProviders()]);
348
389
  initProviderDropdown();
349
390
  initModelDropdown();
350
- document.getElementById('proxy-auth').addEventListener('change', (e) => {
351
- const enabled = e.target.value === 'true';
391
+ initSimpleDropdown('auth-dropdown', (val) => {
392
+ const enabled = val === 'true';
352
393
  document.getElementById('auth-token-group').style.display = enabled ? 'block' : 'none';
353
394
  if (enabled && !document.getElementById('proxy-auth-token').value) {
354
395
  document.getElementById('proxy-auth-token').value = generateToken();
355
396
  }
356
397
  });
398
+ initSimpleDropdown('protocol-dropdown');
357
399
  }
358
400
 
359
401
  // ==================== 代理地址复制 ====================
@@ -485,7 +527,15 @@ function openModal(id = null) {
485
527
  document.getElementById('proxy-id').value = p.id;
486
528
  document.getElementById('proxy-name').value = p.name;
487
529
  document.getElementById('proxy-port').value = p.port;
488
- document.getElementById('proxy-auth').value = p.requireAuth ? 'true' : 'false';
530
+ // 同步认证下拉框
531
+ const authVal = p.requireAuth ? 'true' : 'false';
532
+ document.getElementById('proxy-auth').value = authVal;
533
+ document.querySelectorAll('#auth-dropdown .model-option').forEach(o => o.classList.remove('selected'));
534
+ const authOpt = document.querySelector(`#auth-dropdown .model-option[data-value="${authVal}"]`);
535
+ if (authOpt) {
536
+ authOpt.classList.add('selected');
537
+ document.getElementById('auth-dropdown-value').textContent = authOpt.querySelector('.model-option-name').textContent;
538
+ }
489
539
  document.getElementById('proxy-auth-token').value = p.authToken || '';
490
540
  document.getElementById('auth-token-group').style.display = p.requireAuth ? 'block' : 'none';
491
541
  selectProvider(p.providerId || '');
@@ -493,6 +543,11 @@ function openModal(id = null) {
493
543
  document.getElementById('target-key').placeholder = p.hasApiKey ? '已设置(留空则不修改)' : 'sk-...';
494
544
  } else {
495
545
  document.getElementById('proxy-id').value = '';
546
+ // 重置认证下拉框
547
+ document.getElementById('proxy-auth').value = 'false';
548
+ document.querySelectorAll('#auth-dropdown .model-option').forEach(o => o.classList.remove('selected'));
549
+ document.querySelector('#auth-dropdown .model-option[data-value="false"]').classList.add('selected');
550
+ document.getElementById('auth-dropdown-value').textContent = '不启用';
496
551
  document.getElementById('auth-token-group').style.display = 'none';
497
552
  selectProvider('');
498
553
  selectModel('');
@@ -507,6 +562,8 @@ function closeModal() {
507
562
  document.getElementById('modal').classList.remove('active');
508
563
  document.getElementById('model-dropdown').classList.remove('open');
509
564
  document.getElementById('provider-dropdown').classList.remove('open');
565
+ document.getElementById('auth-dropdown').classList.remove('open');
566
+ document.getElementById('protocol-dropdown').classList.remove('open');
510
567
  editingId = null;
511
568
  editingProviderId = null;
512
569
  }
package/public/index.html CHANGED
@@ -56,10 +56,19 @@
56
56
  </div>
57
57
  <div class="form-group">
58
58
  <label>Agent 认证</label>
59
- <select id="proxy-auth">
60
- <option value="false">不启用</option>
61
- <option value="true">启用</option>
62
- </select>
59
+ <input type="hidden" id="proxy-auth" value="false">
60
+ <div class="model-dropdown" id="auth-dropdown">
61
+ <div class="model-dropdown-trigger" id="auth-dropdown-trigger">
62
+ <span id="auth-dropdown-value">不启用</span>
63
+ <span class="model-dropdown-arrow">&#9662;</span>
64
+ </div>
65
+ <div class="model-dropdown-menu" id="auth-dropdown-menu">
66
+ <div class="model-dropdown-options" id="auth-dropdown-options">
67
+ <div class="model-option selected" data-value="false"><span class="model-option-name">不启用</span></div>
68
+ <div class="model-option" data-value="true"><span class="model-option-name">启用</span></div>
69
+ </div>
70
+ </div>
71
+ </div>
63
72
  </div>
64
73
  </div>
65
74
 
@@ -92,10 +101,19 @@
92
101
  </div>
93
102
  <div class="form-group">
94
103
  <label>协议</label>
95
- <select id="target-protocol">
96
- <option value="openai">OpenAI</option>
97
- <option value="anthropic">Anthropic</option>
98
- </select>
104
+ <input type="hidden" id="target-protocol" value="openai">
105
+ <div class="model-dropdown" id="protocol-dropdown">
106
+ <div class="model-dropdown-trigger" id="protocol-dropdown-trigger">
107
+ <span id="protocol-dropdown-value">OpenAI</span>
108
+ <span class="model-dropdown-arrow">&#9662;</span>
109
+ </div>
110
+ <div class="model-dropdown-menu" id="protocol-dropdown-menu">
111
+ <div class="model-dropdown-options" id="protocol-dropdown-options">
112
+ <div class="model-option selected" data-value="openai"><span class="model-option-name">OpenAI</span></div>
113
+ <div class="model-option" data-value="anthropic"><span class="model-option-name">Anthropic</span></div>
114
+ </div>
115
+ </div>
116
+ </div>
99
117
  </div>
100
118
  </div>
101
119
  <div class="form-row">
package/public/style.css CHANGED
@@ -1,163 +1,256 @@
1
1
  * { box-sizing: border-box; margin: 0; padding: 0; }
2
2
 
3
3
  body {
4
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5
- background: #0f172a;
4
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
5
+ background: #030712;
6
6
  color: #e2e8f0;
7
7
  line-height: 1.6;
8
+ min-height: 100vh;
9
+ }
10
+
11
+ /* 科技网格背景 */
12
+ body::before {
13
+ content: '';
14
+ position: fixed;
15
+ top: 0; left: 0; right: 0; bottom: 0;
16
+ background-image:
17
+ linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px),
18
+ linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px);
19
+ background-size: 60px 60px;
20
+ pointer-events: none;
21
+ z-index: 0;
8
22
  }
9
23
 
10
24
  .container {
25
+ position: relative;
26
+ z-index: 1;
11
27
  max-width: 1200px;
12
28
  margin: 0 auto;
13
29
  padding: 40px 20px;
14
30
  }
15
31
 
32
+ /* Header */
16
33
  header {
17
34
  text-align: center;
18
- margin-bottom: 32px;
35
+ margin-bottom: 40px;
19
36
  }
20
37
 
21
38
  header h1 {
22
- font-size: 2.5rem;
23
- font-weight: 700;
24
- background: linear-gradient(90deg, #60a5fa, #a78bfa);
39
+ font-size: 2.8rem;
40
+ font-weight: 800;
41
+ letter-spacing: -0.02em;
42
+ background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #22d3ee 100%);
25
43
  -webkit-background-clip: text;
26
44
  -webkit-text-fill-color: transparent;
27
- margin-bottom: 8px;
45
+ background-clip: text;
46
+ margin-bottom: 10px;
47
+ text-shadow: 0 0 40px rgba(96, 165, 250, 0.15);
28
48
  }
29
49
 
30
50
  header p {
31
- color: #94a3b8;
32
- font-size: 1.1rem;
51
+ color: #64748b;
52
+ font-size: 1.05rem;
53
+ letter-spacing: 0.02em;
33
54
  }
34
55
 
56
+ /* Stats */
35
57
  .stats {
36
58
  display: flex;
37
- gap: 24px;
59
+ gap: 20px;
38
60
  justify-content: center;
39
- margin-bottom: 32px;
61
+ margin-bottom: 40px;
40
62
  }
41
63
 
42
64
  .stat-item {
43
- background: #1e293b;
44
- border: 1px solid #334155;
45
- border-radius: 12px;
46
- padding: 20px 40px;
65
+ background: rgba(15, 23, 42, 0.6);
66
+ backdrop-filter: blur(12px);
67
+ border: 1px solid rgba(51, 65, 85, 0.5);
68
+ border-radius: 16px;
69
+ padding: 24px 48px;
47
70
  text-align: center;
48
- min-width: 140px;
71
+ min-width: 160px;
72
+ position: relative;
73
+ overflow: hidden;
74
+ transition: all 0.3s ease;
75
+ }
76
+
77
+ .stat-item::before {
78
+ content: '';
79
+ position: absolute;
80
+ top: 0; left: 0; right: 0;
81
+ height: 2px;
82
+ background: linear-gradient(90deg, transparent, #3b82f6, transparent);
83
+ opacity: 0.5;
84
+ }
85
+
86
+ .stat-item:hover {
87
+ border-color: rgba(59, 130, 246, 0.3);
88
+ box-shadow: 0 0 30px rgba(59, 130, 246, 0.08);
89
+ transform: translateY(-2px);
49
90
  }
50
91
 
51
92
  .stat-value {
52
93
  display: block;
53
- font-size: 2rem;
94
+ font-size: 2.2rem;
54
95
  font-weight: 700;
55
96
  color: #60a5fa;
97
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
56
98
  }
57
99
 
58
100
  .stat-label {
59
- color: #94a3b8;
60
- font-size: 0.9rem;
101
+ color: #64748b;
102
+ font-size: 0.85rem;
103
+ text-transform: uppercase;
104
+ letter-spacing: 0.08em;
105
+ margin-top: 4px;
61
106
  }
62
107
 
108
+ /* Card */
63
109
  .card {
64
- background: #1e293b;
65
- border: 1px solid #334155;
66
- border-radius: 16px;
110
+ background: rgba(15, 23, 42, 0.5);
111
+ backdrop-filter: blur(16px);
112
+ border: 1px solid rgba(51, 65, 85, 0.4);
113
+ border-radius: 20px;
67
114
  overflow: hidden;
115
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
68
116
  }
69
117
 
70
118
  .card-header {
71
119
  display: flex;
72
120
  justify-content: space-between;
73
121
  align-items: center;
74
- padding: 24px;
75
- border-bottom: 1px solid #334155;
122
+ padding: 24px 28px;
123
+ border-bottom: 1px solid rgba(51, 65, 85, 0.4);
76
124
  }
77
125
 
78
126
  .card-header h2 {
79
- font-size: 1.25rem;
127
+ font-size: 1.15rem;
80
128
  font-weight: 600;
129
+ color: #f1f5f9;
130
+ letter-spacing: -0.01em;
81
131
  }
82
132
 
133
+ /* Buttons */
83
134
  .btn {
84
- padding: 8px 16px;
85
- border-radius: 8px;
86
- border: 1px solid #475569;
87
- background: #334155;
88
- color: #e2e8f0;
135
+ padding: 9px 18px;
136
+ border-radius: 10px;
137
+ border: 1px solid rgba(71, 85, 105, 0.6);
138
+ background: rgba(51, 65, 85, 0.4);
139
+ color: #cbd5e1;
89
140
  cursor: pointer;
90
141
  font-size: 0.9rem;
91
- transition: all 0.2s;
142
+ font-weight: 500;
143
+ transition: all 0.25s ease;
144
+ backdrop-filter: blur(8px);
92
145
  }
93
146
 
94
147
  .btn:hover {
95
- background: #475569;
148
+ background: rgba(71, 85, 105, 0.5);
149
+ border-color: rgba(100, 116, 139, 0.8);
150
+ transform: translateY(-1px);
151
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
96
152
  }
97
153
 
98
154
  .btn-primary {
99
- background: #3b82f6;
100
- border-color: #3b82f6;
155
+ background: linear-gradient(135deg, #3b82f6, #2563eb);
156
+ border-color: transparent;
101
157
  color: white;
158
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.25);
102
159
  }
103
160
 
104
161
  .btn-primary:hover {
105
- background: #2563eb;
162
+ background: linear-gradient(135deg, #4b92ff, #3573fb);
163
+ box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4);
164
+ transform: translateY(-1px);
106
165
  }
107
166
 
108
167
  .btn-sm {
109
- padding: 4px 12px;
168
+ padding: 5px 14px;
110
169
  font-size: 0.85rem;
111
170
  }
112
171
 
113
172
  .btn-danger {
114
- background: #ef4444;
115
- border-color: #ef4444;
173
+ background: linear-gradient(135deg, #ef4444, #dc2626);
174
+ border-color: transparent;
116
175
  color: white;
176
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.2);
117
177
  }
118
178
 
119
179
  .btn-danger:hover {
120
- background: #dc2626;
180
+ background: linear-gradient(135deg, #ff5555, #ec3636);
181
+ box-shadow: 0 4px 16px rgba(239, 68, 68, 0.35);
121
182
  }
122
183
 
123
184
  .btn-success {
124
- background: #22c55e;
125
- border-color: #22c55e;
185
+ background: linear-gradient(135deg, #22c55e, #16a34a);
186
+ border-color: transparent;
126
187
  color: white;
188
+ box-shadow: 0 2px 8px rgba(34, 197, 94, 0.2);
127
189
  }
128
190
 
129
191
  .btn-success:hover {
130
- background: #16a34a;
192
+ background: linear-gradient(135deg, #32d56e, #26b34a);
193
+ box-shadow: 0 4px 16px rgba(34, 197, 94, 0.35);
131
194
  }
132
195
 
196
+ /* Proxy list */
133
197
  .proxy-list {
134
- padding: 16px;
198
+ padding: 20px;
135
199
  }
136
200
 
137
201
  .empty {
138
202
  text-align: center;
139
- padding: 60px 20px;
140
- color: #64748b;
203
+ padding: 80px 20px;
204
+ color: #475569;
205
+ font-size: 0.95rem;
206
+ }
207
+
208
+ .empty::before {
209
+ content: '◈';
210
+ display: block;
211
+ font-size: 3rem;
212
+ color: #334155;
213
+ margin-bottom: 16px;
141
214
  }
142
215
 
216
+ /* Proxy Item */
143
217
  .proxy-item {
144
- background: #0f172a;
145
- border: 1px solid #334155;
146
- border-radius: 12px;
147
- padding: 20px;
148
- margin-bottom: 12px;
149
- transition: border-color 0.2s;
218
+ background: rgba(6, 8, 15, 0.6);
219
+ backdrop-filter: blur(12px);
220
+ border: 1px solid rgba(51, 65, 85, 0.35);
221
+ border-radius: 16px;
222
+ padding: 24px;
223
+ margin-bottom: 16px;
224
+ position: relative;
225
+ overflow: hidden;
226
+ transition: all 0.3s ease;
227
+ }
228
+
229
+ .proxy-item::before {
230
+ content: '';
231
+ position: absolute;
232
+ left: 0; top: 0; bottom: 0;
233
+ width: 3px;
234
+ background: #475569;
235
+ transition: background 0.3s ease;
150
236
  }
151
237
 
152
238
  .proxy-item:hover {
153
- border-color: #475569;
239
+ border-color: rgba(71, 85, 105, 0.6);
240
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
241
+ transform: translateY(-2px);
242
+ }
243
+
244
+ .proxy-item:hover::before {
245
+ background: #3b82f6;
246
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.5);
154
247
  }
155
248
 
156
249
  .proxy-header {
157
250
  display: flex;
158
251
  justify-content: space-between;
159
252
  align-items: flex-start;
160
- margin-bottom: 12px;
253
+ margin-bottom: 14px;
161
254
  }
162
255
 
163
256
  .proxy-title {
@@ -167,31 +260,38 @@ header p {
167
260
  }
168
261
 
169
262
  .proxy-title h3 {
170
- font-size: 1.1rem;
263
+ font-size: 1.15rem;
171
264
  font-weight: 600;
265
+ color: #f8fafc;
172
266
  }
173
267
 
268
+ /* Badge */
174
269
  .badge {
175
- padding: 2px 10px;
270
+ padding: 3px 12px;
176
271
  border-radius: 20px;
177
272
  font-size: 0.75rem;
178
- font-weight: 500;
273
+ font-weight: 600;
274
+ letter-spacing: 0.03em;
275
+ backdrop-filter: blur(8px);
179
276
  }
180
277
 
181
278
  .badge-running {
182
- background: #064e3b;
279
+ background: rgba(6, 78, 59, 0.4);
183
280
  color: #34d399;
281
+ border: 1px solid rgba(52, 211, 153, 0.2);
282
+ box-shadow: 0 0 8px rgba(52, 211, 153, 0.1);
184
283
  }
185
284
 
186
285
  .badge-stopped {
187
- background: #451a03;
286
+ background: rgba(69, 26, 3, 0.4);
188
287
  color: #fbbf24;
288
+ border: 1px solid rgba(251, 191, 36, 0.2);
189
289
  }
190
290
 
191
291
  .proxy-meta {
192
292
  display: flex;
193
- gap: 20px;
194
- color: #94a3b8;
293
+ gap: 24px;
294
+ color: #64748b;
195
295
  font-size: 0.9rem;
196
296
  margin-bottom: 16px;
197
297
  }
@@ -202,11 +302,15 @@ header p {
202
302
  gap: 6px;
203
303
  }
204
304
 
305
+ /* Target Table */
205
306
  .target-table {
206
307
  width: 100%;
207
- border-collapse: collapse;
308
+ border-collapse: separate;
309
+ border-spacing: 0;
208
310
  font-size: 0.85rem;
209
311
  table-layout: fixed;
312
+ border-radius: 10px;
313
+ overflow: hidden;
210
314
  }
211
315
 
212
316
  .target-table th:nth-child(1),
@@ -221,29 +325,34 @@ header p {
221
325
  .target-table th,
222
326
  .target-table td {
223
327
  text-align: left;
224
- padding: 8px 12px;
225
- border-bottom: 1px solid #1e293b;
328
+ padding: 10px 14px;
329
+ border-bottom: 1px solid rgba(30, 41, 59, 0.6);
226
330
  }
227
331
 
228
332
  .target-table th {
229
- color: #64748b;
230
- font-weight: 500;
333
+ color: #475569;
334
+ font-weight: 600;
231
335
  text-transform: uppercase;
232
- font-size: 0.75rem;
233
- letter-spacing: 0.05em;
336
+ font-size: 0.7rem;
337
+ letter-spacing: 0.08em;
338
+ background: rgba(15, 23, 42, 0.4);
234
339
  }
235
340
 
236
341
  .target-table td {
237
- color: #cbd5e1;
342
+ color: #94a3b8;
238
343
  word-break: break-all;
239
344
  }
240
345
 
346
+ .target-table tbody tr:last-child td {
347
+ border-bottom: none;
348
+ }
349
+
241
350
  .proxy-actions {
242
351
  display: flex;
243
- gap: 8px;
244
- margin-top: 16px;
245
- padding-top: 16px;
246
- border-top: 1px solid #1e293b;
352
+ gap: 10px;
353
+ margin-top: 18px;
354
+ padding-top: 18px;
355
+ border-top: 1px solid rgba(30, 41, 59, 0.5);
247
356
  }
248
357
 
249
358
  /* Modal */
@@ -254,7 +363,8 @@ header p {
254
363
  left: 0;
255
364
  width: 100%;
256
365
  height: 100%;
257
- background: rgba(0,0,0,0.7);
366
+ background: rgba(3, 7, 18, 0.85);
367
+ backdrop-filter: blur(8px);
258
368
  z-index: 1000;
259
369
  justify-content: center;
260
370
  align-items: center;
@@ -267,104 +377,159 @@ header p {
267
377
  }
268
378
 
269
379
  .modal-content {
270
- background: #1e293b;
271
- border: 1px solid #334155;
272
- border-radius: 16px;
380
+ background: rgba(15, 23, 42, 0.7);
381
+ backdrop-filter: blur(24px);
382
+ border: 1px solid rgba(71, 85, 105, 0.4);
383
+ border-radius: 20px;
273
384
  width: 100%;
274
385
  max-width: 700px;
386
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(59, 130, 246, 0.05);
387
+ animation: modal-in 0.3s ease;
388
+ }
389
+
390
+ @keyframes modal-in {
391
+ from { opacity: 0; transform: translateY(20px) scale(0.97); }
392
+ to { opacity: 1; transform: translateY(0) scale(1); }
275
393
  }
276
394
 
277
395
  .modal-header {
278
396
  display: flex;
279
397
  justify-content: space-between;
280
398
  align-items: center;
281
- padding: 20px 24px;
282
- border-bottom: 1px solid #334155;
399
+ padding: 22px 28px;
400
+ border-bottom: 1px solid rgba(51, 65, 85, 0.4);
283
401
  }
284
402
 
285
403
  .modal-header h3 {
286
404
  font-size: 1.2rem;
405
+ font-weight: 600;
406
+ color: #f8fafc;
287
407
  }
288
408
 
289
409
  .btn-close {
290
410
  background: none;
291
411
  border: none;
292
- color: #94a3b8;
412
+ color: #64748b;
293
413
  font-size: 1.5rem;
294
414
  cursor: pointer;
295
415
  line-height: 1;
416
+ width: 36px;
417
+ height: 36px;
418
+ border-radius: 10px;
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ transition: all 0.2s;
296
423
  }
297
424
 
298
425
  .btn-close:hover {
299
426
  color: #e2e8f0;
427
+ background: rgba(71, 85, 105, 0.3);
300
428
  }
301
429
 
430
+ /* Form */
302
431
  form {
303
- padding: 24px;
432
+ padding: 24px 28px;
304
433
  }
305
434
 
306
435
  .form-group {
307
- margin-bottom: 16px;
436
+ margin-bottom: 18px;
308
437
  }
309
438
 
310
439
  .form-group label {
311
440
  display: block;
312
- margin-bottom: 6px;
441
+ margin-bottom: 8px;
313
442
  color: #94a3b8;
314
- font-size: 0.9rem;
443
+ font-size: 0.85rem;
444
+ font-weight: 500;
445
+ letter-spacing: 0.02em;
315
446
  }
316
447
 
317
448
  .form-group input,
318
449
  .form-group select,
319
450
  .form-group textarea {
320
451
  width: 100%;
321
- padding: 10px 14px;
322
- background: #0f172a;
323
- border: 1px solid #334155;
324
- border-radius: 8px;
325
- color: #e2e8f0;
452
+ padding: 11px 16px;
453
+ background: rgba(6, 8, 15, 0.6);
454
+ border: 1px solid rgba(51, 65, 85, 0.5);
455
+ border-radius: 10px;
456
+ color: #f1f5f9;
326
457
  font-size: 0.95rem;
458
+ transition: all 0.25s ease;
459
+ appearance: none;
460
+ -webkit-appearance: none;
461
+ }
462
+
463
+ .form-group input::placeholder,
464
+ .form-group select::placeholder {
465
+ color: #475569;
327
466
  }
328
467
 
329
468
  .form-group input:focus,
330
469
  .form-group select:focus {
331
470
  outline: none;
332
- border-color: #3b82f6;
471
+ border-color: rgba(59, 130, 246, 0.5);
472
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 0 20px rgba(59, 130, 246, 0.08);
473
+ background: rgba(6, 8, 15, 0.8);
474
+ }
475
+
476
+ /* Select 下拉框 - 科技感 */
477
+ .form-group select {
478
+ cursor: pointer;
479
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
480
+ background-repeat: no-repeat;
481
+ background-position: right 14px center;
482
+ padding-right: 42px;
483
+ }
484
+
485
+ .form-group select:hover {
486
+ border-color: rgba(100, 116, 139, 0.7);
487
+ }
488
+
489
+ .form-group select option {
490
+ background: #0f172a;
491
+ color: #e2e8f0;
492
+ padding: 10px;
333
493
  }
334
494
 
495
+ /* Form row */
335
496
  .form-row {
336
497
  display: grid;
337
498
  grid-template-columns: 1fr 1fr;
338
- gap: 16px;
499
+ gap: 18px;
339
500
  }
340
501
 
502
+ /* Target section */
341
503
  .target-section {
342
- margin-top: 20px;
343
- padding-top: 20px;
344
- border-top: 1px solid #334155;
504
+ margin-top: 24px;
505
+ padding-top: 24px;
506
+ border-top: 1px solid rgba(51, 65, 85, 0.4);
345
507
  }
346
508
 
347
- .target-header {
348
- display: flex;
349
- justify-content: space-between;
350
- align-items: center;
509
+ .target-section h4 {
510
+ font-size: 0.9rem;
511
+ font-weight: 600;
512
+ color: #64748b;
513
+ text-transform: uppercase;
514
+ letter-spacing: 0.08em;
351
515
  margin-bottom: 16px;
352
516
  }
353
517
 
354
- .target-header h4 {
355
- font-size: 1rem;
356
- }
357
-
358
518
  .target-item {
359
- background: #0f172a;
360
- border: 1px solid #334155;
361
- border-radius: 10px;
362
- padding: 16px;
519
+ background: rgba(6, 8, 15, 0.5);
520
+ border: 1px solid rgba(51, 65, 85, 0.35);
521
+ border-radius: 14px;
522
+ padding: 20px;
363
523
  margin-bottom: 12px;
524
+ transition: border-color 0.25s;
525
+ }
526
+
527
+ .target-item:hover {
528
+ border-color: rgba(71, 85, 105, 0.5);
364
529
  }
365
530
 
366
531
  .target-item .form-row {
367
- margin-bottom: 12px;
532
+ margin-bottom: 14px;
368
533
  }
369
534
 
370
535
  .target-item .form-row:last-child {
@@ -382,24 +547,30 @@ form {
382
547
  gap: 12px;
383
548
  padding-top: 20px;
384
549
  margin-top: 20px;
385
- border-top: 1px solid #334155;
550
+ border-top: 1px solid rgba(51, 65, 85, 0.4);
386
551
  }
387
552
 
388
553
  /* Proxy address */
389
554
  .proxy-address {
390
555
  display: flex;
391
556
  align-items: center;
392
- gap: 8px;
393
- margin-top: 12px;
394
- padding: 10px 14px;
395
- background: #1e293b;
396
- border: 1px solid #334155;
397
- border-radius: 8px;
557
+ gap: 10px;
558
+ margin-top: 14px;
559
+ padding: 12px 16px;
560
+ background: rgba(15, 23, 42, 0.5);
561
+ border: 1px solid rgba(51, 65, 85, 0.35);
562
+ border-radius: 12px;
563
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
564
+ transition: all 0.25s;
565
+ }
566
+
567
+ .proxy-address:hover {
568
+ border-color: rgba(59, 130, 246, 0.3);
569
+ box-shadow: 0 0 16px rgba(59, 130, 246, 0.06);
398
570
  }
399
571
 
400
572
  .proxy-address code {
401
573
  flex: 1;
402
- font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
403
574
  font-size: 0.85rem;
404
575
  color: #7dd3fc;
405
576
  word-break: break-all;
@@ -409,20 +580,23 @@ form {
409
580
  flex-shrink: 0;
410
581
  display: flex;
411
582
  align-items: center;
412
- gap: 4px;
413
- padding: 4px 10px;
414
- background: #334155;
415
- border: 1px solid #475569;
416
- border-radius: 6px;
583
+ gap: 6px;
584
+ padding: 6px 12px;
585
+ background: rgba(51, 65, 85, 0.4);
586
+ border: 1px solid rgba(71, 85, 105, 0.5);
587
+ border-radius: 8px;
417
588
  color: #94a3b8;
418
589
  font-size: 0.8rem;
419
590
  cursor: pointer;
420
- transition: all 0.2s;
591
+ transition: all 0.25s;
592
+ backdrop-filter: blur(8px);
421
593
  }
422
594
 
423
595
  .copy-btn:hover {
424
- background: #475569;
425
- color: #e2e8f0;
596
+ background: rgba(59, 130, 246, 0.15);
597
+ border-color: rgba(59, 130, 246, 0.4);
598
+ color: #60a5fa;
599
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.15);
426
600
  }
427
601
 
428
602
  .copy-btn svg {
@@ -433,21 +607,23 @@ form {
433
607
  /* Toast */
434
608
  .toast {
435
609
  position: fixed;
436
- bottom: 24px;
437
- right: 24px;
438
- padding: 10px 20px;
439
- background: #22c55e;
610
+ bottom: 28px;
611
+ right: 28px;
612
+ padding: 12px 24px;
613
+ background: linear-gradient(135deg, #22c55e, #16a34a);
440
614
  color: white;
441
- border-radius: 8px;
615
+ border-radius: 12px;
442
616
  font-size: 0.9rem;
617
+ font-weight: 500;
443
618
  z-index: 9999;
444
- animation: toast-in 0.3s ease, toast-out 0.3s ease 1.7s forwards;
445
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
619
+ animation: toast-in 0.35s ease, toast-out 0.35s ease 2.3s forwards;
620
+ box-shadow: 0 8px 24px rgba(34, 197, 94, 0.3);
621
+ backdrop-filter: blur(12px);
446
622
  }
447
623
 
448
624
  @keyframes toast-in {
449
- from { opacity: 0; transform: translateY(10px); }
450
- to { opacity: 1; transform: translateY(0); }
625
+ from { opacity: 0; transform: translateY(16px) scale(0.95); }
626
+ to { opacity: 1; transform: translateY(0) scale(1); }
451
627
  }
452
628
 
453
629
  @keyframes toast-out {
@@ -466,29 +642,30 @@ form {
466
642
  align-items: center;
467
643
  justify-content: space-between;
468
644
  width: 100%;
469
- padding: 10px 14px;
470
- background: #0f172a;
471
- border: 1px solid #334155;
472
- border-radius: 8px;
473
- color: #e2e8f0;
645
+ padding: 11px 16px;
646
+ background: rgba(6, 8, 15, 0.6);
647
+ border: 1px solid rgba(51, 65, 85, 0.5);
648
+ border-radius: 10px;
649
+ color: #f1f5f9;
474
650
  font-size: 0.95rem;
475
651
  cursor: pointer;
476
- transition: border-color 0.2s;
652
+ transition: all 0.25s ease;
477
653
  user-select: none;
478
654
  }
479
655
 
480
656
  .model-dropdown-trigger:hover {
481
- border-color: #475569;
657
+ border-color: rgba(100, 116, 139, 0.6);
482
658
  }
483
659
 
484
660
  .model-dropdown.open .model-dropdown-trigger {
485
- border-color: #3b82f6;
661
+ border-color: rgba(59, 130, 246, 0.5);
662
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 0 20px rgba(59, 130, 246, 0.08);
486
663
  }
487
664
 
488
665
  .model-dropdown-arrow {
489
666
  font-size: 0.7rem;
490
667
  color: #64748b;
491
- transition: transform 0.2s;
668
+ transition: transform 0.25s ease;
492
669
  }
493
670
 
494
671
  .model-dropdown.open .model-dropdown-arrow {
@@ -498,15 +675,22 @@ form {
498
675
  .model-dropdown-menu {
499
676
  display: none;
500
677
  position: absolute;
501
- top: calc(100% + 4px);
678
+ top: calc(100% + 6px);
502
679
  left: 0;
503
680
  right: 0;
504
- background: #1e293b;
505
- border: 1px solid #475569;
506
- border-radius: 10px;
681
+ background: rgba(15, 23, 42, 0.95);
682
+ backdrop-filter: blur(24px);
683
+ border: 1px solid rgba(71, 85, 105, 0.5);
684
+ border-radius: 12px;
507
685
  z-index: 100;
508
686
  overflow: hidden;
509
- box-shadow: 0 8px 24px rgba(0,0,0,0.4);
687
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(59, 130, 246, 0.05);
688
+ animation: dropdown-in 0.2s ease;
689
+ }
690
+
691
+ @keyframes dropdown-in {
692
+ from { opacity: 0; transform: translateY(-6px); }
693
+ to { opacity: 1; transform: translateY(0); }
510
694
  }
511
695
 
512
696
  .model-dropdown.open .model-dropdown-menu {
@@ -514,26 +698,42 @@ form {
514
698
  }
515
699
 
516
700
  .model-dropdown-options {
517
- max-height: 200px;
701
+ max-height: 220px;
518
702
  overflow-y: auto;
519
703
  }
520
704
 
705
+ .model-dropdown-options::-webkit-scrollbar {
706
+ width: 6px;
707
+ }
708
+
709
+ .model-dropdown-options::-webkit-scrollbar-track {
710
+ background: transparent;
711
+ }
712
+
713
+ .model-dropdown-options::-webkit-scrollbar-thumb {
714
+ background: rgba(71, 85, 105, 0.5);
715
+ border-radius: 3px;
716
+ }
717
+
521
718
  .model-option {
522
719
  display: flex;
523
720
  align-items: center;
524
721
  justify-content: space-between;
525
- padding: 8px 14px;
722
+ padding: 10px 16px;
526
723
  cursor: pointer;
527
- transition: background 0.15s;
724
+ transition: all 0.15s ease;
725
+ border-left: 2px solid transparent;
528
726
  }
529
727
 
530
728
  .model-option:hover {
531
- background: #334155;
729
+ background: rgba(59, 130, 246, 0.08);
730
+ border-left-color: rgba(59, 130, 246, 0.4);
532
731
  }
533
732
 
534
733
  .model-option.selected {
535
- background: #1e3a5f;
734
+ background: rgba(59, 130, 246, 0.12);
536
735
  color: #7dd3fc;
736
+ border-left-color: #3b82f6;
537
737
  }
538
738
 
539
739
  .model-option-name {
@@ -545,9 +745,9 @@ form {
545
745
  display: flex;
546
746
  align-items: center;
547
747
  justify-content: center;
548
- width: 22px;
549
- height: 22px;
550
- border-radius: 50%;
748
+ width: 24px;
749
+ height: 24px;
750
+ border-radius: 6px;
551
751
  border: none;
552
752
  background: transparent;
553
753
  color: #64748b;
@@ -559,69 +759,74 @@ form {
559
759
  }
560
760
 
561
761
  .model-option-delete:hover {
562
- background: #ef4444;
563
- color: white;
762
+ background: rgba(239, 68, 68, 0.15);
763
+ color: #ef4444;
564
764
  }
565
765
 
566
766
  .model-add-section {
567
767
  display: flex;
568
- gap: 6px;
569
- padding: 8px 14px;
570
- border-top: 1px solid #334155;
768
+ gap: 8px;
769
+ padding: 10px 16px;
770
+ border-top: 1px solid rgba(51, 65, 85, 0.4);
771
+ background: rgba(6, 8, 15, 0.3);
571
772
  }
572
773
 
573
774
  .model-add-input {
574
775
  flex: 1;
575
- padding: 6px 10px;
576
- background: #0f172a;
577
- border: 1px solid #334155;
578
- border-radius: 6px;
776
+ padding: 7px 12px;
777
+ background: rgba(6, 8, 15, 0.5);
778
+ border: 1px solid rgba(51, 65, 85, 0.5);
779
+ border-radius: 8px;
579
780
  color: #e2e8f0;
580
781
  font-size: 0.85rem;
782
+ transition: all 0.2s;
581
783
  }
582
784
 
583
785
  .model-add-input:focus {
584
786
  outline: none;
585
- border-color: #3b82f6;
787
+ border-color: rgba(59, 130, 246, 0.4);
788
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.08);
586
789
  }
587
790
 
588
791
  /* Confirm modal */
589
792
  .confirm-modal .confirm-box {
590
- background: #1e293b;
591
- border: 1px solid #334155;
592
- border-radius: 14px;
593
- padding: 28px 32px;
594
- max-width: 360px;
793
+ background: rgba(15, 23, 42, 0.8);
794
+ backdrop-filter: blur(24px);
795
+ border: 1px solid rgba(71, 85, 105, 0.4);
796
+ border-radius: 20px;
797
+ padding: 32px 36px;
798
+ max-width: 380px;
595
799
  width: 90%;
596
800
  text-align: center;
597
- box-shadow: 0 12px 40px rgba(0,0,0,0.5);
598
- animation: confirm-pop 0.2s ease;
801
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(239, 68, 68, 0.05);
802
+ animation: confirm-pop 0.25s ease;
599
803
  }
600
804
 
601
805
  @keyframes confirm-pop {
602
- from { opacity: 0; transform: scale(0.92); }
603
- to { opacity: 1; transform: scale(1); }
806
+ from { opacity: 0; transform: scale(0.92) translateY(10px); }
807
+ to { opacity: 1; transform: scale(1) translateY(0); }
604
808
  }
605
809
 
606
810
  .confirm-icon {
607
811
  display: inline-flex;
608
812
  align-items: center;
609
813
  justify-content: center;
610
- width: 48px;
611
- height: 48px;
612
- border-radius: 50%;
613
- background: #451a03;
814
+ width: 56px;
815
+ height: 56px;
816
+ border-radius: 16px;
817
+ background: rgba(69, 26, 3, 0.4);
614
818
  color: #fbbf24;
615
- font-size: 1.4rem;
819
+ font-size: 1.6rem;
616
820
  font-weight: 700;
617
- margin-bottom: 16px;
821
+ margin-bottom: 20px;
822
+ border: 1px solid rgba(251, 191, 36, 0.15);
618
823
  }
619
824
 
620
825
  .confirm-text {
621
- color: #cbd5e1;
826
+ color: #94a3b8;
622
827
  font-size: 0.95rem;
623
- line-height: 1.5;
624
- margin-bottom: 24px;
828
+ line-height: 1.6;
829
+ margin-bottom: 28px;
625
830
  }
626
831
 
627
832
  .confirm-text strong {
@@ -635,5 +840,26 @@ form {
635
840
  }
636
841
 
637
842
  .confirm-actions .btn {
638
- min-width: 80px;
843
+ min-width: 90px;
844
+ }
845
+
846
+ /* Responsive */
847
+ @media (max-width: 640px) {
848
+ .form-row {
849
+ grid-template-columns: 1fr;
850
+ }
851
+
852
+ .stats {
853
+ flex-direction: column;
854
+ align-items: center;
855
+ }
856
+
857
+ .proxy-meta {
858
+ flex-direction: column;
859
+ gap: 8px;
860
+ }
861
+
862
+ header h1 {
863
+ font-size: 2rem;
864
+ }
639
865
  }