asemxin commited on
Commit
cc8eb0f
·
1 Parent(s): 6b10e4d

Add full model list display and cookie management UI

Browse files
Files changed (2) hide show
  1. index.html +361 -96
  2. server.py +100 -2
index.html CHANGED
@@ -1,9 +1,10 @@
1
  <!DOCTYPE html>
2
  <html lang="zh-CN">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Monica Proxy - Cookie 状态检测</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <style>
@@ -19,13 +20,13 @@
19
  --error-color: #ff453a;
20
  --warning-color: #ffd60a;
21
  }
22
-
23
  * {
24
  margin: 0;
25
  padding: 0;
26
  box-sizing: border-box;
27
  }
28
-
29
  body {
30
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
31
  background: var(--bg-primary);
@@ -35,16 +36,16 @@
35
  align-items: center;
36
  justify-content: center;
37
  padding: 20px;
38
- background-image:
39
  radial-gradient(ellipse 80% 50% at 50% -20%, rgba(102, 126, 234, 0.15), transparent),
40
  radial-gradient(ellipse 60% 40% at 100% 100%, rgba(118, 75, 162, 0.1), transparent);
41
  }
42
-
43
  .container {
44
  width: 100%;
45
- max-width: 520px;
46
  }
47
-
48
  .card {
49
  background: var(--bg-card);
50
  border: 1px solid var(--border-color);
@@ -52,19 +53,20 @@
52
  padding: 40px;
53
  backdrop-filter: blur(20px);
54
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
 
55
  }
56
-
57
  .header {
58
  text-align: center;
59
  margin-bottom: 36px;
60
  }
61
-
62
  .logo {
63
  font-size: 48px;
64
  margin-bottom: 16px;
65
  display: block;
66
  }
67
-
68
  h1 {
69
  font-size: 24px;
70
  font-weight: 600;
@@ -74,16 +76,16 @@
74
  background-clip: text;
75
  margin-bottom: 8px;
76
  }
77
-
78
  .subtitle {
79
  color: var(--text-secondary);
80
  font-size: 14px;
81
  }
82
-
83
  .form-group {
84
  margin-bottom: 20px;
85
  }
86
-
87
  label {
88
  display: block;
89
  font-size: 13px;
@@ -91,8 +93,9 @@
91
  color: var(--text-secondary);
92
  margin-bottom: 8px;
93
  }
94
-
95
- input {
 
96
  width: 100%;
97
  padding: 14px 16px;
98
  background: var(--bg-secondary);
@@ -103,18 +106,27 @@
103
  font-family: inherit;
104
  transition: all 0.2s ease;
105
  }
106
-
107
- input:focus {
 
 
 
 
 
 
 
 
108
  outline: none;
109
  border-color: #667eea;
110
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
111
  }
112
-
113
- input::placeholder {
 
114
  color: var(--text-secondary);
115
  opacity: 0.6;
116
  }
117
-
118
  .btn {
119
  width: 100%;
120
  padding: 16px;
@@ -129,22 +141,36 @@
129
  transition: all 0.2s ease;
130
  margin-top: 8px;
131
  }
132
-
133
  .btn:hover {
134
  transform: translateY(-2px);
135
  box-shadow: 0 8px 24px rgba(102, 126, 234, 0.35);
136
  }
137
-
138
  .btn:active {
139
  transform: translateY(0);
140
  }
141
-
142
  .btn:disabled {
143
  opacity: 0.6;
144
  cursor: not-allowed;
145
  transform: none;
146
  }
147
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  .status-panel {
149
  margin-top: 28px;
150
  padding: 20px;
@@ -152,20 +178,20 @@
152
  border-radius: 14px;
153
  border: 1px solid var(--border-color);
154
  }
155
-
156
  .status-header {
157
  display: flex;
158
  align-items: center;
159
  justify-content: space-between;
160
  margin-bottom: 16px;
161
  }
162
-
163
  .status-label {
164
  font-size: 13px;
165
  font-weight: 500;
166
  color: var(--text-secondary);
167
  }
168
-
169
  .status-badge {
170
  display: flex;
171
  align-items: center;
@@ -175,49 +201,56 @@
175
  font-size: 12px;
176
  font-weight: 600;
177
  }
178
-
179
  .status-badge.idle {
180
  background: rgba(142, 142, 147, 0.15);
181
  color: var(--text-secondary);
182
  }
183
-
184
  .status-badge.loading {
185
  background: rgba(255, 214, 10, 0.15);
186
  color: var(--warning-color);
187
  }
188
-
189
  .status-badge.success {
190
  background: rgba(48, 209, 88, 0.15);
191
  color: var(--success-color);
192
  }
193
-
194
  .status-badge.error {
195
  background: rgba(255, 69, 58, 0.15);
196
  color: var(--error-color);
197
  }
198
-
199
  .status-dot {
200
  width: 8px;
201
  height: 8px;
202
  border-radius: 50%;
203
  background: currentColor;
204
  }
205
-
206
  .status-badge.loading .status-dot {
207
  animation: pulse 1.5s ease-in-out infinite;
208
  }
209
-
210
  @keyframes pulse {
211
- 0%, 100% { opacity: 1; }
212
- 50% { opacity: 0.4; }
 
 
 
 
 
 
 
213
  }
214
-
215
  .status-details {
216
  font-size: 13px;
217
  color: var(--text-secondary);
218
  line-height: 1.6;
219
  }
220
-
221
  .status-details pre {
222
  background: rgba(0, 0, 0, 0.3);
223
  padding: 12px;
@@ -229,91 +262,249 @@
229
  white-space: pre-wrap;
230
  word-break: break-all;
231
  }
232
-
233
  .model-list {
234
- margin-top: 12px;
 
 
235
  }
236
-
237
  .model-item {
238
  display: flex;
239
  align-items: center;
240
- gap: 8px;
241
- padding: 8px 12px;
242
  background: rgba(255, 255, 255, 0.03);
243
- border-radius: 8px;
244
- margin-bottom: 6px;
245
- font-size: 13px;
 
246
  }
247
-
 
 
 
 
 
248
  .model-icon {
249
- font-size: 16px;
250
  }
251
-
252
  .model-name {
253
  flex: 1;
254
  font-family: 'SF Mono', 'Consolas', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  }
256
-
 
 
 
 
 
 
 
 
257
  .footer {
258
  text-align: center;
259
  margin-top: 24px;
260
  font-size: 12px;
261
  color: var(--text-secondary);
262
  }
263
-
264
  .footer a {
265
  color: #667eea;
266
  text-decoration: none;
267
  }
268
-
269
  .footer a:hover {
270
  text-decoration: underline;
271
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  </style>
273
  </head>
 
274
  <body>
275
  <div class="container">
276
  <div class="card">
277
  <div class="header">
278
  <span class="logo">🤖</span>
279
  <h1>Monica Proxy</h1>
280
- <p class="subtitle">Cookie 状态检测工具</p>
281
- </div>
282
-
283
- <div class="form-group">
284
- <label for="apiUrl">API 地址</label>
285
- <input type="text" id="apiUrl" value="https://asem12345-monica-proxy.hf.space" placeholder="输入 API 地址">
286
  </div>
287
-
288
- <div class="form-group">
289
- <label for="bearerToken">Bearer Token</label>
290
- <input type="password" id="bearerToken" placeholder="输入你的 Bearer Token">
291
  </div>
292
-
293
- <button class="btn" id="checkBtn" onclick="checkStatus()">
294
- 检测 Cookie 状态
295
- </button>
296
-
297
- <div class="status-panel">
298
- <div class="status-header">
299
- <span class="status-label">状态</span>
300
- <div class="status-badge idle" id="statusBadge">
301
- <span class="status-dot"></span>
302
- <span id="statusText">等待检测</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  </div>
305
- <div class="status-details" id="statusDetails">
306
- 点击上方按钮检测 Monica Proxy 的 Cookie 是否可用。
 
 
307
  </div>
 
 
 
 
308
  </div>
309
  </div>
310
-
311
  <div class="footer">
312
  Powered by <a href="https://github.com/ycvk/monica-proxy" target="_blank">ycvk/monica-proxy</a>
313
  </div>
314
  </div>
315
-
316
  <script>
 
 
 
 
 
 
 
 
 
 
317
  async function checkStatus() {
318
  const apiUrl = document.getElementById('apiUrl').value.trim().replace(/\/$/, '');
319
  const bearerToken = document.getElementById('bearerToken').value.trim();
@@ -321,24 +512,24 @@
321
  const statusBadge = document.getElementById('statusBadge');
322
  const statusText = document.getElementById('statusText');
323
  const statusDetails = document.getElementById('statusDetails');
324
-
325
  if (!apiUrl) {
326
  alert('请输入 API 地址');
327
  return;
328
  }
329
-
330
  if (!bearerToken) {
331
  alert('请输入 Bearer Token');
332
  return;
333
  }
334
-
335
  // Loading state
336
  btn.disabled = true;
337
  btn.textContent = '检测中...';
338
  statusBadge.className = 'status-badge loading';
339
  statusText.textContent = '检测中';
340
  statusDetails.innerHTML = '正在连接到 Monica Proxy...';
341
-
342
  try {
343
  const response = await fetch(`${apiUrl}/v1/models`, {
344
  method: 'GET',
@@ -347,23 +538,30 @@
347
  'Content-Type': 'application/json'
348
  }
349
  });
350
-
351
  const data = await response.json();
352
-
353
  if (response.ok && data.data && data.data.length > 0) {
354
  // Success
355
  statusBadge.className = 'status-badge success';
356
  statusText.textContent = 'Cookie 可用';
357
-
358
- let modelsHtml = `<p>✅ Cookie 工作正常!检测到 <strong>${data.data.length}</strong> 个可用模型:</p><div class="model-list">`;
359
- data.data.slice(0, 10).forEach(model => {
360
- modelsHtml += `<div class="model-item"><span class="model-icon">🧠</span><span class="model-name">${model.id}</span></div>`;
 
 
 
 
 
 
 
 
 
361
  });
362
- if (data.data.length > 10) {
363
- modelsHtml += `<div class="model-item" style="color: var(--text-secondary);">... 还有 ${data.data.length - 10} 个模型</div>`;
364
- }
365
- modelsHtml += '</div>';
366
- statusDetails.innerHTML = modelsHtml;
367
  } else if (response.status === 401) {
368
  // Unauthorized
369
  statusBadge.className = 'status-badge error';
@@ -373,7 +571,11 @@
373
  // Other error - possibly cookie issue
374
  statusBadge.className = 'status-badge error';
375
  statusText.textContent = 'Cookie 失效';
376
- statusDetails.innerHTML = `<p>❌ Cookie 可能已失效或存在其他问题</p><pre>${JSON.stringify(data, null, 2)}</pre>`;
 
 
 
 
377
  }
378
  } catch (error) {
379
  statusBadge.className = 'status-badge error';
@@ -384,13 +586,76 @@
384
  btn.textContent = '检测 Cookie 状态';
385
  }
386
  }
387
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  // Allow Enter key to trigger check
389
- document.getElementById('bearerToken').addEventListener('keypress', function(e) {
390
  if (e.key === 'Enter') {
391
  checkStatus();
392
  }
393
  });
394
  </script>
395
  </body>
396
- </html>
 
 
1
  <!DOCTYPE html>
2
  <html lang="zh-CN">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Monica Proxy - 控制面板</title>
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
10
  <style>
 
20
  --error-color: #ff453a;
21
  --warning-color: #ffd60a;
22
  }
23
+
24
  * {
25
  margin: 0;
26
  padding: 0;
27
  box-sizing: border-box;
28
  }
29
+
30
  body {
31
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
32
  background: var(--bg-primary);
 
36
  align-items: center;
37
  justify-content: center;
38
  padding: 20px;
39
+ background-image:
40
  radial-gradient(ellipse 80% 50% at 50% -20%, rgba(102, 126, 234, 0.15), transparent),
41
  radial-gradient(ellipse 60% 40% at 100% 100%, rgba(118, 75, 162, 0.1), transparent);
42
  }
43
+
44
  .container {
45
  width: 100%;
46
+ max-width: 600px;
47
  }
48
+
49
  .card {
50
  background: var(--bg-card);
51
  border: 1px solid var(--border-color);
 
53
  padding: 40px;
54
  backdrop-filter: blur(20px);
55
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
56
+ margin-bottom: 20px;
57
  }
58
+
59
  .header {
60
  text-align: center;
61
  margin-bottom: 36px;
62
  }
63
+
64
  .logo {
65
  font-size: 48px;
66
  margin-bottom: 16px;
67
  display: block;
68
  }
69
+
70
  h1 {
71
  font-size: 24px;
72
  font-weight: 600;
 
76
  background-clip: text;
77
  margin-bottom: 8px;
78
  }
79
+
80
  .subtitle {
81
  color: var(--text-secondary);
82
  font-size: 14px;
83
  }
84
+
85
  .form-group {
86
  margin-bottom: 20px;
87
  }
88
+
89
  label {
90
  display: block;
91
  font-size: 13px;
 
93
  color: var(--text-secondary);
94
  margin-bottom: 8px;
95
  }
96
+
97
+ input,
98
+ textarea {
99
  width: 100%;
100
  padding: 14px 16px;
101
  background: var(--bg-secondary);
 
106
  font-family: inherit;
107
  transition: all 0.2s ease;
108
  }
109
+
110
+ textarea {
111
+ resize: vertical;
112
+ min-height: 80px;
113
+ font-family: 'SF Mono', 'Consolas', monospace;
114
+ font-size: 12px;
115
+ }
116
+
117
+ input:focus,
118
+ textarea:focus {
119
  outline: none;
120
  border-color: #667eea;
121
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
122
  }
123
+
124
+ input::placeholder,
125
+ textarea::placeholder {
126
  color: var(--text-secondary);
127
  opacity: 0.6;
128
  }
129
+
130
  .btn {
131
  width: 100%;
132
  padding: 16px;
 
141
  transition: all 0.2s ease;
142
  margin-top: 8px;
143
  }
144
+
145
  .btn:hover {
146
  transform: translateY(-2px);
147
  box-shadow: 0 8px 24px rgba(102, 126, 234, 0.35);
148
  }
149
+
150
  .btn:active {
151
  transform: translateY(0);
152
  }
153
+
154
  .btn:disabled {
155
  opacity: 0.6;
156
  cursor: not-allowed;
157
  transform: none;
158
  }
159
+
160
+ .btn-secondary {
161
+ background: var(--bg-secondary);
162
+ border: 1px solid var(--border-color);
163
+ }
164
+
165
+ .btn-secondary:hover {
166
+ background: rgba(255, 255, 255, 0.1);
167
+ box-shadow: none;
168
+ }
169
+
170
+ .btn-danger {
171
+ background: linear-gradient(135deg, #ff453a 0%, #d63031 100%);
172
+ }
173
+
174
  .status-panel {
175
  margin-top: 28px;
176
  padding: 20px;
 
178
  border-radius: 14px;
179
  border: 1px solid var(--border-color);
180
  }
181
+
182
  .status-header {
183
  display: flex;
184
  align-items: center;
185
  justify-content: space-between;
186
  margin-bottom: 16px;
187
  }
188
+
189
  .status-label {
190
  font-size: 13px;
191
  font-weight: 500;
192
  color: var(--text-secondary);
193
  }
194
+
195
  .status-badge {
196
  display: flex;
197
  align-items: center;
 
201
  font-size: 12px;
202
  font-weight: 600;
203
  }
204
+
205
  .status-badge.idle {
206
  background: rgba(142, 142, 147, 0.15);
207
  color: var(--text-secondary);
208
  }
209
+
210
  .status-badge.loading {
211
  background: rgba(255, 214, 10, 0.15);
212
  color: var(--warning-color);
213
  }
214
+
215
  .status-badge.success {
216
  background: rgba(48, 209, 88, 0.15);
217
  color: var(--success-color);
218
  }
219
+
220
  .status-badge.error {
221
  background: rgba(255, 69, 58, 0.15);
222
  color: var(--error-color);
223
  }
224
+
225
  .status-dot {
226
  width: 8px;
227
  height: 8px;
228
  border-radius: 50%;
229
  background: currentColor;
230
  }
231
+
232
  .status-badge.loading .status-dot {
233
  animation: pulse 1.5s ease-in-out infinite;
234
  }
235
+
236
  @keyframes pulse {
237
+
238
+ 0%,
239
+ 100% {
240
+ opacity: 1;
241
+ }
242
+
243
+ 50% {
244
+ opacity: 0.4;
245
+ }
246
  }
247
+
248
  .status-details {
249
  font-size: 13px;
250
  color: var(--text-secondary);
251
  line-height: 1.6;
252
  }
253
+
254
  .status-details pre {
255
  background: rgba(0, 0, 0, 0.3);
256
  padding: 12px;
 
262
  white-space: pre-wrap;
263
  word-break: break-all;
264
  }
265
+
266
  .model-list {
267
+ margin-top: 16px;
268
+ max-height: 400px;
269
+ overflow-y: auto;
270
  }
271
+
272
  .model-item {
273
  display: flex;
274
  align-items: center;
275
+ gap: 10px;
276
+ padding: 10px 14px;
277
  background: rgba(255, 255, 255, 0.03);
278
+ border-radius: 10px;
279
+ margin-bottom: 8px;
280
+ border: 1px solid transparent;
281
+ transition: all 0.2s ease;
282
  }
283
+
284
+ .model-item:hover {
285
+ border-color: var(--border-color);
286
+ background: rgba(255, 255, 255, 0.05);
287
+ }
288
+
289
  .model-icon {
290
+ font-size: 18px;
291
  }
292
+
293
  .model-name {
294
  flex: 1;
295
  font-family: 'SF Mono', 'Consolas', monospace;
296
+ font-size: 13px;
297
+ }
298
+
299
+ .model-count {
300
+ background: var(--accent-gradient);
301
+ -webkit-background-clip: text;
302
+ -webkit-text-fill-color: transparent;
303
+ background-clip: text;
304
+ font-weight: 600;
305
+ font-size: 14px;
306
+ }
307
+
308
+ .section-title {
309
+ font-size: 16px;
310
+ font-weight: 600;
311
+ margin-bottom: 20px;
312
+ color: var(--text-primary);
313
+ display: flex;
314
+ align-items: center;
315
+ gap: 10px;
316
+ }
317
+
318
+ .section-title .icon {
319
+ font-size: 20px;
320
+ }
321
+
322
+ .tabs {
323
+ display: flex;
324
+ gap: 8px;
325
+ margin-bottom: 24px;
326
+ }
327
+
328
+ .tab {
329
+ flex: 1;
330
+ padding: 12px;
331
+ background: var(--bg-secondary);
332
+ border: 1px solid var(--border-color);
333
+ border-radius: 10px;
334
+ color: var(--text-secondary);
335
+ font-size: 13px;
336
+ font-weight: 500;
337
+ cursor: pointer;
338
+ transition: all 0.2s ease;
339
+ text-align: center;
340
+ }
341
+
342
+ .tab:hover {
343
+ background: rgba(255, 255, 255, 0.05);
344
+ }
345
+
346
+ .tab.active {
347
+ background: var(--accent-gradient);
348
+ color: white;
349
+ border-color: transparent;
350
  }
351
+
352
+ .tab-content {
353
+ display: none;
354
+ }
355
+
356
+ .tab-content.active {
357
+ display: block;
358
+ }
359
+
360
  .footer {
361
  text-align: center;
362
  margin-top: 24px;
363
  font-size: 12px;
364
  color: var(--text-secondary);
365
  }
366
+
367
  .footer a {
368
  color: #667eea;
369
  text-decoration: none;
370
  }
371
+
372
  .footer a:hover {
373
  text-decoration: underline;
374
  }
375
+
376
+ .alert {
377
+ padding: 14px 16px;
378
+ border-radius: 10px;
379
+ font-size: 13px;
380
+ margin-bottom: 16px;
381
+ display: none;
382
+ }
383
+
384
+ .alert.success {
385
+ background: rgba(48, 209, 88, 0.15);
386
+ border: 1px solid rgba(48, 209, 88, 0.3);
387
+ color: var(--success-color);
388
+ display: block;
389
+ }
390
+
391
+ .alert.error {
392
+ background: rgba(255, 69, 58, 0.15);
393
+ border: 1px solid rgba(255, 69, 58, 0.3);
394
+ color: var(--error-color);
395
+ display: block;
396
+ }
397
+
398
+ .info-box {
399
+ background: rgba(102, 126, 234, 0.1);
400
+ border: 1px solid rgba(102, 126, 234, 0.2);
401
+ border-radius: 10px;
402
+ padding: 14px 16px;
403
+ font-size: 12px;
404
+ color: var(--text-secondary);
405
+ margin-bottom: 20px;
406
+ }
407
+
408
+ .info-box code {
409
+ background: rgba(0, 0, 0, 0.3);
410
+ padding: 2px 6px;
411
+ border-radius: 4px;
412
+ font-family: 'SF Mono', 'Consolas', monospace;
413
+ }
414
  </style>
415
  </head>
416
+
417
  <body>
418
  <div class="container">
419
  <div class="card">
420
  <div class="header">
421
  <span class="logo">🤖</span>
422
  <h1>Monica Proxy</h1>
423
+ <p class="subtitle">控制面板</p>
 
 
 
 
 
424
  </div>
425
+
426
+ <div class="tabs">
427
+ <div class="tab active" onclick="switchTab('status')">📊 状态检测</div>
428
+ <div class="tab" onclick="switchTab('cookie')">🔑 Cookie 管理</div>
429
  </div>
430
+
431
+ <!-- Status Tab -->
432
+ <div id="status-tab" class="tab-content active">
433
+ <div class="form-group">
434
+ <label for="apiUrl">API 地址</label>
435
+ <input type="text" id="apiUrl" value="https://asem12345-monica-proxy.hf.space"
436
+ placeholder="输入 API 地址">
437
+ </div>
438
+
439
+ <div class="form-group">
440
+ <label for="bearerToken">Bearer Token</label>
441
+ <input type="password" id="bearerToken" placeholder="输入你的 Bearer Token">
442
+ </div>
443
+
444
+ <button class="btn" id="checkBtn" onclick="checkStatus()">
445
+ 检测 Cookie 状态
446
+ </button>
447
+
448
+ <div class="status-panel">
449
+ <div class="status-header">
450
+ <span class="status-label">状态</span>
451
+ <div class="status-badge idle" id="statusBadge">
452
+ <span class="status-dot"></span>
453
+ <span id="statusText">等待检测</span>
454
+ </div>
455
  </div>
456
+ <div class="status-details" id="statusDetails">
457
+ 点击上方按钮检测 Monica Proxy 的 Cookie 是否可用。
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ <!-- Cookie Management Tab -->
463
+ <div id="cookie-tab" class="tab-content">
464
+ <div class="info-box">
465
+ ⚠️ 更新 Cookie 需要提供 Bearer Token 进行身份验证。Cookie 更新后,代理服务将自动重启以应用新配置。
466
+ </div>
467
+
468
+ <div id="cookieAlert" class="alert"></div>
469
+
470
+ <div class="form-group">
471
+ <label for="cookieApiUrl">API 地址</label>
472
+ <input type="text" id="cookieApiUrl" value="https://asem12345-monica-proxy.hf.space"
473
+ placeholder="输入 API 地址">
474
+ </div>
475
+
476
+ <div class="form-group">
477
+ <label for="cookieBearerToken">Bearer Token(身份验证)</label>
478
+ <input type="password" id="cookieBearerToken" placeholder="输入你的 Bearer Token">
479
  </div>
480
+
481
+ <div class="form-group">
482
+ <label for="newCookie">新的 MONICA_COOKIE</label>
483
+ <textarea id="newCookie" placeholder="粘贴新的 Monica Cookie 值..."></textarea>
484
  </div>
485
+
486
+ <button class="btn btn-danger" id="updateCookieBtn" onclick="updateCookie()">
487
+ 🔄 更新 Cookie
488
+ </button>
489
  </div>
490
  </div>
491
+
492
  <div class="footer">
493
  Powered by <a href="https://github.com/ycvk/monica-proxy" target="_blank">ycvk/monica-proxy</a>
494
  </div>
495
  </div>
496
+
497
  <script>
498
+ function switchTab(tabName) {
499
+ // Update tab buttons
500
+ document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
501
+ event.target.classList.add('active');
502
+
503
+ // Update tab content
504
+ document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
505
+ document.getElementById(tabName + '-tab').classList.add('active');
506
+ }
507
+
508
  async function checkStatus() {
509
  const apiUrl = document.getElementById('apiUrl').value.trim().replace(/\/$/, '');
510
  const bearerToken = document.getElementById('bearerToken').value.trim();
 
512
  const statusBadge = document.getElementById('statusBadge');
513
  const statusText = document.getElementById('statusText');
514
  const statusDetails = document.getElementById('statusDetails');
515
+
516
  if (!apiUrl) {
517
  alert('请输入 API 地址');
518
  return;
519
  }
520
+
521
  if (!bearerToken) {
522
  alert('请输入 Bearer Token');
523
  return;
524
  }
525
+
526
  // Loading state
527
  btn.disabled = true;
528
  btn.textContent = '检测中...';
529
  statusBadge.className = 'status-badge loading';
530
  statusText.textContent = '检测中';
531
  statusDetails.innerHTML = '正在连接到 Monica Proxy...';
532
+
533
  try {
534
  const response = await fetch(`${apiUrl}/v1/models`, {
535
  method: 'GET',
 
538
  'Content-Type': 'application/json'
539
  }
540
  });
541
+
542
  const data = await response.json();
543
+
544
  if (response.ok && data.data && data.data.length > 0) {
545
  // Success
546
  statusBadge.className = 'status-badge success';
547
  statusText.textContent = 'Cookie 可用';
548
+
549
+ let html = `
550
+ <p>✅ Cookie 工作正常!</p>
551
+ <div style="margin-top: 16px; display: flex; align-items: center; justify-content: space-between;">
552
+ <span class="section-title" style="margin: 0;"><span class="icon">🧠</span> 可用模型</span>
553
+ <span class="model-count">${data.data.length} 个</span>
554
+ </div>
555
+ <div class="model-list">
556
+ `;
557
+
558
+ data.data.forEach(model => {
559
+ const icon = getModelIcon(model.id);
560
+ html += `<div class="model-item"><span class="model-icon">${icon}</span><span class="model-name">${model.id}</span></div>`;
561
  });
562
+
563
+ html += '</div>';
564
+ statusDetails.innerHTML = html;
 
 
565
  } else if (response.status === 401) {
566
  // Unauthorized
567
  statusBadge.className = 'status-badge error';
 
571
  // Other error - possibly cookie issue
572
  statusBadge.className = 'status-badge error';
573
  statusText.textContent = 'Cookie 失效';
574
+ statusDetails.innerHTML = `
575
+ <p>❌ Cookie 可能已失效或存在其他问题</p>
576
+ <pre>${JSON.stringify(data, null, 2)}</pre>
577
+ <p style="margin-top: 16px;">👉 请切换到 <strong>Cookie 管理</strong> 标签页更新 Cookie</p>
578
+ `;
579
  }
580
  } catch (error) {
581
  statusBadge.className = 'status-badge error';
 
586
  btn.textContent = '检测 Cookie 状态';
587
  }
588
  }
589
+
590
+ function getModelIcon(modelId) {
591
+ const id = modelId.toLowerCase();
592
+ if (id.includes('gpt')) return '🟢';
593
+ if (id.includes('claude')) return '🟠';
594
+ if (id.includes('gemini')) return '🔵';
595
+ if (id.includes('grok')) return '⚫';
596
+ if (id.includes('llama')) return '🦙';
597
+ if (id.includes('qwen')) return '🟣';
598
+ if (id.includes('deepseek')) return '🔷';
599
+ if (id.includes('sonar')) return '📡';
600
+ if (id.includes('o1') || id.includes('o3')) return '🧪';
601
+ return '🤖';
602
+ }
603
+
604
+ async function updateCookie() {
605
+ const apiUrl = document.getElementById('cookieApiUrl').value.trim().replace(/\/$/, '');
606
+ const bearerToken = document.getElementById('cookieBearerToken').value.trim();
607
+ const newCookie = document.getElementById('newCookie').value.trim();
608
+ const btn = document.getElementById('updateCookieBtn');
609
+ const alertBox = document.getElementById('cookieAlert');
610
+
611
+ if (!apiUrl || !bearerToken || !newCookie) {
612
+ showAlert('error', '请填写所有字段');
613
+ return;
614
+ }
615
+
616
+ btn.disabled = true;
617
+ btn.textContent = '更新中...';
618
+ alertBox.className = 'alert';
619
+
620
+ try {
621
+ const response = await fetch(`${apiUrl}/admin/update-cookie`, {
622
+ method: 'POST',
623
+ headers: {
624
+ 'Authorization': `Bearer ${bearerToken}`,
625
+ 'Content-Type': 'application/json'
626
+ },
627
+ body: JSON.stringify({ cookie: newCookie })
628
+ });
629
+
630
+ const data = await response.json();
631
+
632
+ if (response.ok) {
633
+ showAlert('success', '✅ Cookie 更新成功!服务将在几秒后重启。');
634
+ document.getElementById('newCookie').value = '';
635
+ } else {
636
+ showAlert('error', `❌ 更新失败: ${data.error || data.message || '未知错误'}`);
637
+ }
638
+ } catch (error) {
639
+ showAlert('error', `❌ 请求失败: ${error.message}`);
640
+ } finally {
641
+ btn.disabled = false;
642
+ btn.textContent = '🔄 更新 Cookie';
643
+ }
644
+ }
645
+
646
+ function showAlert(type, message) {
647
+ const alertBox = document.getElementById('cookieAlert');
648
+ alertBox.className = `alert ${type}`;
649
+ alertBox.textContent = message;
650
+ }
651
+
652
  // Allow Enter key to trigger check
653
+ document.getElementById('bearerToken').addEventListener('keypress', function (e) {
654
  if (e.key === 'Enter') {
655
  checkStatus();
656
  }
657
  });
658
  </script>
659
  </body>
660
+
661
+ </html>
server.py CHANGED
@@ -1,14 +1,19 @@
1
  """
2
  Simple reverse proxy + static file server for Monica Proxy
 
3
  """
4
  import asyncio
5
  import subprocess
6
  import os
 
7
  from aiohttp import web, ClientSession, ClientTimeout
8
 
9
  BACKEND_PORT = 8080
10
  FRONTEND_PORT = 7860
11
 
 
 
 
12
  async def proxy_handler(request: web.Request):
13
  """Proxy requests to monica-proxy backend"""
14
  backend_url = f"http://127.0.0.1:{BACKEND_PORT}{request.path_qs}"
@@ -43,6 +48,87 @@ async def index_handler(request: web.Request):
43
  """Serve the status page"""
44
  return web.FileResponse('index.html')
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  async def start_backend():
47
  """Start monica-proxy in background"""
48
  env = os.environ.copy()
@@ -68,16 +154,26 @@ async def start_backend():
68
  return process
69
 
70
  async def main():
 
 
71
  # Start backend
72
  print(f"Starting monica-proxy on port {BACKEND_PORT}...")
73
- backend = await start_backend()
74
 
75
  # Give backend time to start
76
  await asyncio.sleep(2)
77
 
78
  # Setup web server
79
  app = web.Application()
 
 
80
  app.router.add_get('/', index_handler)
 
 
 
 
 
 
81
  app.router.add_route('*', '/v1/{path:.*}', proxy_handler)
82
  app.router.add_route('*', '/{path:.*}', proxy_handler)
83
 
@@ -91,12 +187,14 @@ async def main():
91
  print(f"Server ready!")
92
  print(f" Status page: http://0.0.0.0:{FRONTEND_PORT}/")
93
  print(f" API: http://0.0.0.0:{FRONTEND_PORT}/v1/...")
 
94
 
95
  # Keep running
96
  try:
97
  await asyncio.Event().wait()
98
  finally:
99
- backend.terminate()
 
100
 
101
  if __name__ == '__main__':
102
  asyncio.run(main())
 
1
  """
2
  Simple reverse proxy + static file server for Monica Proxy
3
+ with admin endpoints for cookie management
4
  """
5
  import asyncio
6
  import subprocess
7
  import os
8
+ import signal
9
  from aiohttp import web, ClientSession, ClientTimeout
10
 
11
  BACKEND_PORT = 8080
12
  FRONTEND_PORT = 7860
13
 
14
+ # Global reference to backend process
15
+ backend_process = None
16
+
17
  async def proxy_handler(request: web.Request):
18
  """Proxy requests to monica-proxy backend"""
19
  backend_url = f"http://127.0.0.1:{BACKEND_PORT}{request.path_qs}"
 
48
  """Serve the status page"""
49
  return web.FileResponse('index.html')
50
 
51
+ async def update_cookie_handler(request: web.Request):
52
+ """Update MONICA_COOKIE and restart backend"""
53
+ global backend_process
54
+
55
+ # Verify authorization
56
+ auth_header = request.headers.get('Authorization', '')
57
+ expected_token = os.environ.get('BEARER_TOKEN', '')
58
+
59
+ if not expected_token:
60
+ return web.json_response(
61
+ {"error": "BEARER_TOKEN not configured on server"},
62
+ status=500
63
+ )
64
+
65
+ if not auth_header.startswith('Bearer ') or auth_header[7:] != expected_token:
66
+ return web.json_response(
67
+ {"error": "Invalid authorization"},
68
+ status=401
69
+ )
70
+
71
+ try:
72
+ data = await request.json()
73
+ new_cookie = data.get('cookie', '').strip()
74
+
75
+ if not new_cookie:
76
+ return web.json_response(
77
+ {"error": "Cookie value is required"},
78
+ status=400
79
+ )
80
+
81
+ # Update environment variable
82
+ os.environ['MONICA_COOKIE'] = new_cookie
83
+ print(f"[admin] MONICA_COOKIE updated, length: {len(new_cookie)}")
84
+
85
+ # Restart backend
86
+ if backend_process:
87
+ print("[admin] Restarting backend...")
88
+ backend_process.terminate()
89
+ await asyncio.sleep(1)
90
+ backend_process = await start_backend()
91
+ await asyncio.sleep(2)
92
+ print("[admin] Backend restarted")
93
+
94
+ return web.json_response({
95
+ "success": True,
96
+ "message": "Cookie updated and backend restarted"
97
+ })
98
+
99
+ except Exception as e:
100
+ return web.json_response(
101
+ {"error": str(e)},
102
+ status=500
103
+ )
104
+
105
+ async def get_cookie_status_handler(request: web.Request):
106
+ """Get current cookie status (masked)"""
107
+ auth_header = request.headers.get('Authorization', '')
108
+ expected_token = os.environ.get('BEARER_TOKEN', '')
109
+
110
+ if not auth_header.startswith('Bearer ') or auth_header[7:] != expected_token:
111
+ return web.json_response(
112
+ {"error": "Invalid authorization"},
113
+ status=401
114
+ )
115
+
116
+ cookie = os.environ.get('MONICA_COOKIE', '')
117
+ if cookie:
118
+ # Mask the cookie, show only first and last 10 chars
119
+ if len(cookie) > 30:
120
+ masked = cookie[:10] + '...' + cookie[-10:]
121
+ else:
122
+ masked = cookie[:5] + '...'
123
+ else:
124
+ masked = '(not set)'
125
+
126
+ return web.json_response({
127
+ "cookie_set": bool(cookie),
128
+ "cookie_length": len(cookie),
129
+ "cookie_preview": masked
130
+ })
131
+
132
  async def start_backend():
133
  """Start monica-proxy in background"""
134
  env = os.environ.copy()
 
154
  return process
155
 
156
  async def main():
157
+ global backend_process
158
+
159
  # Start backend
160
  print(f"Starting monica-proxy on port {BACKEND_PORT}...")
161
+ backend_process = await start_backend()
162
 
163
  # Give backend time to start
164
  await asyncio.sleep(2)
165
 
166
  # Setup web server
167
  app = web.Application()
168
+
169
+ # Static routes
170
  app.router.add_get('/', index_handler)
171
+
172
+ # Admin routes
173
+ app.router.add_post('/admin/update-cookie', update_cookie_handler)
174
+ app.router.add_get('/admin/cookie-status', get_cookie_status_handler)
175
+
176
+ # Proxy routes (must be last due to catch-all)
177
  app.router.add_route('*', '/v1/{path:.*}', proxy_handler)
178
  app.router.add_route('*', '/{path:.*}', proxy_handler)
179
 
 
187
  print(f"Server ready!")
188
  print(f" Status page: http://0.0.0.0:{FRONTEND_PORT}/")
189
  print(f" API: http://0.0.0.0:{FRONTEND_PORT}/v1/...")
190
+ print(f" Admin: http://0.0.0.0:{FRONTEND_PORT}/admin/...")
191
 
192
  # Keep running
193
  try:
194
  await asyncio.Event().wait()
195
  finally:
196
+ if backend_process:
197
+ backend_process.terminate()
198
 
199
  if __name__ == '__main__':
200
  asyncio.run(main())