waroca commited on
Commit
fed48fd
·
verified ·
1 Parent(s): f08bbbb

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +2 -0
  2. components/subscriptions.py +175 -109
app.py CHANGED
@@ -55,6 +55,8 @@ def main():
55
  margin: 0 0 1rem 0 !important;
56
  font-weight: 400 !important;
57
  text-align: center !important;
 
 
58
  }
59
 
60
  /* Dataset card styling */
 
55
  margin: 0 0 1rem 0 !important;
56
  font-weight: 400 !important;
57
  text-align: center !important;
58
+ width: 100% !important;
59
+ display: block !important;
60
  }
61
 
62
  /* Dataset card styling */
components/subscriptions.py CHANGED
@@ -2,6 +2,7 @@ from datetime import datetime
2
  import json
3
  import os
4
  import html
 
5
 
6
  # Get MCP server URL from environment
7
  MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "http://localhost:8000")
@@ -19,7 +20,9 @@ def create_subscriptions_html(subscriptions):
19
  """
20
 
21
  cards_html = ""
22
- modals_html = ""
 
 
23
 
24
  for idx, sub in enumerate(subscriptions):
25
  # Determine subscription status
@@ -52,13 +55,13 @@ def create_subscriptions_html(subscriptions):
52
 
53
  dataset_id = sub.get('dataset_id', '')
54
  access_token = sub.get('access_token', '')
55
- modal_id = f"modal-{idx}"
56
 
57
  # Connection details button for active subscriptions
58
  connection_btn_html = ""
59
  if is_active and access_token:
60
  connection_btn_html = f'''
61
- <button class="btn-connect" onclick="document.getElementById('{modal_id}').classList.add('show')">
62
  Connect
63
  </button>
64
  '''
@@ -78,25 +81,28 @@ def create_subscriptions_html(subscriptions):
78
  }
79
  mcp_config_json = json.dumps(mcp_config, indent=2)
80
  mcp_config_escaped = html.escape(mcp_config_json)
81
- mcp_config_for_js = json.dumps(mcp_config_json)
82
 
83
  # Example prompt
84
  example_prompt = f"Query the dataset {dataset_id} and show me a summary of the data. What columns are available and how many rows are there?"
85
  example_prompt_escaped = html.escape(example_prompt)
86
- example_prompt_for_js = json.dumps(example_prompt)
87
-
88
- # Modal HTML with collapsible sections
89
- modals_html += f'''
90
- <div id="{modal_id}" class="modal-overlay" onclick="if(event.target===this) this.classList.remove('show')">
91
- <div class="modal-content">
92
- <div class="modal-header">
93
- <div class="modal-header-info">
94
- <h3 class="modal-title">{html.escape(dataset_id)}</h3>
95
- <span class="modal-status {status_class}">{status_text}</span>
 
 
 
 
96
  </div>
97
- <button class="modal-close" onclick="document.getElementById('{modal_id}').classList.remove('show')">&times;</button>
98
  </div>
99
- <div class="modal-body">
100
  <!-- MCP Config - expanded by default -->
101
  <details class="config-accordion" open>
102
  <summary class="config-summary">
@@ -108,7 +114,7 @@ def create_subscriptions_html(subscriptions):
108
  <p class="config-desc">Add to your Claude Desktop config:</p>
109
  <div class="code-wrapper">
110
  <pre class="code-block">{mcp_config_escaped}</pre>
111
- <button class="btn-copy" onclick="copyToClipboard({mcp_config_for_js}, this)">Copy</button>
112
  </div>
113
  </div>
114
  </details>
@@ -123,7 +129,7 @@ def create_subscriptions_html(subscriptions):
123
  <div class="config-content">
124
  <div class="code-wrapper">
125
  <pre class="code-block prompt-block">{example_prompt_escaped}</pre>
126
- <button class="btn-copy" onclick="copyToClipboard({example_prompt_for_js}, this)">Copy</button>
127
  </div>
128
  </div>
129
  </details>
@@ -138,7 +144,7 @@ def create_subscriptions_html(subscriptions):
138
  <div class="config-content">
139
  <div class="code-wrapper">
140
  <code class="token-block">{html.escape(access_token)}</code>
141
- <button class="btn-copy" onclick="copyToClipboard('{access_token}', this)">Copy</button>
142
  </div>
143
  </div>
144
  </details>
@@ -151,14 +157,8 @@ def create_subscriptions_html(subscriptions):
151
  </div>
152
  '''
153
 
154
- # View on HF button for active subscriptions
155
  view_btn_html = ""
156
- if is_active:
157
- view_btn_html = f'''
158
- <a href="https://huggingface.co/datasets/{dataset_id}" target="_blank" class="btn-link">
159
- View on HF
160
- </a>
161
- '''
162
 
163
  # Determine plan display name
164
  plan_id = sub.get('plan_id', 'trial')
@@ -185,32 +185,91 @@ def create_subscriptions_html(subscriptions):
185
  </div>
186
  """
187
 
 
 
 
188
  result_html = f"""
189
  <div class="subscriptions-container">
190
  <div class="subscriptions-list">
191
  {cards_html}
192
  </div>
193
- {modals_html}
194
  </div>
195
 
196
  <script>
197
- function copyToClipboard(text, btn) {{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  navigator.clipboard.writeText(text).then(function() {{
199
- const originalText = btn.textContent;
200
  btn.textContent = 'Copied!';
201
  btn.classList.add('copied');
202
  setTimeout(function() {{
203
  btn.textContent = originalText;
204
  btn.classList.remove('copied');
205
  }}, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }});
207
  }}
208
 
 
 
 
 
 
 
 
209
  document.addEventListener('keydown', function(e) {{
210
  if (e.key === 'Escape') {{
211
- document.querySelectorAll('.modal-overlay.show').forEach(function(modal) {{
212
- modal.classList.remove('show');
213
  }});
 
214
  }}
215
  }});
216
  </script>
@@ -302,71 +361,75 @@ def create_subscriptions_html(subscriptions):
302
  .btn-connect:hover {{
303
  background: #0056b3;
304
  }}
305
- .btn-link {{
306
- padding: 0.375rem 0.75rem;
307
- border-radius: 6px;
308
- font-size: 0.75rem;
309
- font-weight: 500;
310
- text-decoration: none;
311
- color: var(--text-secondary, #a1a1a6);
312
- background: var(--bg-tertiary, #2c2c2e);
313
- transition: all 0.15s ease;
 
 
 
 
 
 
314
  }}
315
- .btn-link:hover {{
316
- background: var(--bg-hover, #3a3a3c);
317
- color: var(--text-primary, #f5f5f7);
318
  }}
319
-
320
- /* Modal - use !important to override Gradio styles */
321
- .modal-overlay {{
322
- display: none !important;
323
- position: fixed !important;
324
- top: 0 !important;
325
- left: 0 !important;
326
- right: 0 !important;
327
- bottom: 0 !important;
328
- width: 100vw !important;
329
- height: 100vh !important;
330
- background: rgba(0, 0, 0, 0.8) !important;
331
- backdrop-filter: blur(8px) !important;
332
- z-index: 99999 !important;
333
- justify-content: center !important;
334
- align-items: center !important;
335
- padding: 1rem !important;
336
- margin: 0 !important;
337
- }}
338
- .modal-overlay.show {{
339
- display: flex !important;
340
- }}
341
- .modal-content {{
342
- background: var(--bg-primary, #000) !important;
343
- border: 1px solid var(--border-color, rgba(255,255,255,0.08)) !important;
344
- border-radius: 14px !important;
345
- max-width: 480px !important;
346
- width: 100% !important;
347
- max-height: 85vh !important;
348
- overflow-y: auto !important;
349
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;
350
- margin: auto !important;
351
- }}
352
- .modal-header {{
353
  display: flex;
354
  justify-content: space-between;
355
  align-items: center;
356
- padding: 1rem 1.25rem;
357
  border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.08));
358
  gap: 1rem;
 
359
  }}
360
- .modal-header-info {{
361
  display: flex;
362
  align-items: center;
363
  gap: 0.625rem;
364
  min-width: 0;
365
  flex: 1;
366
  }}
367
- .modal-title {{
368
  margin: 0;
369
- font-size: 0.875rem;
370
  font-weight: 600;
371
  color: var(--text-primary, #f5f5f7);
372
  font-family: 'SF Mono', Monaco, monospace;
@@ -374,7 +437,7 @@ def create_subscriptions_html(subscriptions):
374
  overflow: hidden;
375
  text-overflow: ellipsis;
376
  }}
377
- .modal-status {{
378
  padding: 0.125rem 0.375rem;
379
  border-radius: 4px;
380
  font-size: 0.5625rem;
@@ -382,14 +445,14 @@ def create_subscriptions_html(subscriptions):
382
  text-transform: uppercase;
383
  flex-shrink: 0;
384
  }}
385
- .modal-status.active {{
386
  background: rgba(52, 199, 89, 0.15);
387
  color: #34c759;
388
  }}
389
- .modal-close {{
390
  background: none;
391
  border: none;
392
- font-size: 1.25rem;
393
  color: var(--text-tertiary, #6e6e73);
394
  cursor: pointer;
395
  padding: 0;
@@ -397,25 +460,27 @@ def create_subscriptions_html(subscriptions):
397
  transition: color 0.15s;
398
  flex-shrink: 0;
399
  }}
400
- .modal-close:hover {{
401
  color: var(--text-primary, #f5f5f7);
402
  }}
403
- .modal-body {{
404
- padding: 1rem 1.25rem;
 
 
405
  }}
406
 
407
  /* Accordion sections */
408
  .config-accordion {{
409
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
410
  border-radius: 8px;
411
- margin-bottom: 0.625rem;
412
  overflow: hidden;
413
  }}
414
  .config-summary {{
415
  display: flex;
416
  align-items: center;
417
  gap: 0.5rem;
418
- padding: 0.75rem 1rem;
419
  cursor: pointer;
420
  user-select: none;
421
  background: var(--bg-secondary, #1c1c1e);
@@ -429,10 +494,10 @@ def create_subscriptions_html(subscriptions):
429
  background: var(--bg-tertiary, #2c2c2e);
430
  }}
431
  .config-icon {{
432
- font-size: 0.875rem;
433
  }}
434
  .config-label {{
435
- font-size: 0.8125rem;
436
  font-weight: 500;
437
  color: var(--text-primary, #f5f5f7);
438
  flex: 1;
@@ -449,25 +514,26 @@ def create_subscriptions_html(subscriptions):
449
  transform: rotate(180deg);
450
  }}
451
  .config-content {{
452
- padding: 0.75rem 1rem;
453
  border-top: 1px solid var(--border-color, rgba(255,255,255,0.08));
454
  background: var(--bg-primary, #000);
455
  }}
456
  .config-desc {{
457
- font-size: 0.75rem;
458
  color: var(--text-tertiary, #6e6e73);
459
- margin: 0 0 0.5rem 0;
460
  }}
461
  .code-wrapper {{
462
  position: relative;
463
  }}
464
  .code-block {{
465
- padding: 0.75rem;
 
466
  background: var(--bg-secondary, #1c1c1e);
467
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
468
  border-radius: 6px;
469
  font-family: 'SF Mono', Monaco, monospace;
470
- font-size: 0.6875rem;
471
  color: var(--text-primary, #f5f5f7);
472
  overflow-x: auto;
473
  white-space: pre;
@@ -480,12 +546,13 @@ def create_subscriptions_html(subscriptions):
480
  }}
481
  .token-block {{
482
  display: block;
483
- padding: 0.5rem 0.75rem;
 
484
  background: var(--bg-secondary, #1c1c1e);
485
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
486
  border-radius: 6px;
487
  font-family: 'SF Mono', Monaco, monospace;
488
- font-size: 0.6875rem;
489
  color: var(--text-primary, #f5f5f7);
490
  word-break: break-all;
491
  }}
@@ -493,12 +560,12 @@ def create_subscriptions_html(subscriptions):
493
  position: absolute;
494
  top: 0.5rem;
495
  right: 0.5rem;
496
- padding: 0.25rem 0.5rem;
497
  background: var(--bg-tertiary, #2c2c2e);
498
  color: var(--text-secondary, #a1a1a6);
499
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
500
  border-radius: 4px;
501
- font-size: 0.625rem;
502
  font-weight: 500;
503
  cursor: pointer;
504
  transition: all 0.15s;
@@ -517,18 +584,18 @@ def create_subscriptions_html(subscriptions):
517
  display: flex;
518
  align-items: center;
519
  gap: 0.375rem;
520
- padding: 0.625rem 0.75rem;
521
  background: rgba(0, 122, 255, 0.08);
522
  border-radius: 6px;
523
- font-size: 0.6875rem;
524
  color: var(--text-secondary, #a1a1a6);
525
- margin-top: 0.25rem;
526
  }}
527
  .config-tip code {{
528
  background: var(--bg-secondary, #1c1c1e);
529
- padding: 0.0625rem 0.25rem;
530
  border-radius: 3px;
531
- font-size: 0.625rem;
532
  color: var(--text-primary, #f5f5f7);
533
  }}
534
 
@@ -540,9 +607,8 @@ def create_subscriptions_html(subscriptions):
540
  .subscription-actions {{
541
  justify-content: flex-start;
542
  }}
543
- .modal-content {{
544
- margin: 0.5rem;
545
- max-width: none;
546
  }}
547
  }}
548
  </style>
 
2
  import json
3
  import os
4
  import html
5
+ import base64
6
 
7
  # Get MCP server URL from environment
8
  MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "http://localhost:8000")
 
20
  """
21
 
22
  cards_html = ""
23
+ drawers_html = ""
24
+ # Store data for copy functionality
25
+ copy_data = {}
26
 
27
  for idx, sub in enumerate(subscriptions):
28
  # Determine subscription status
 
55
 
56
  dataset_id = sub.get('dataset_id', '')
57
  access_token = sub.get('access_token', '')
58
+ drawer_id = f"drawer-{idx}"
59
 
60
  # Connection details button for active subscriptions
61
  connection_btn_html = ""
62
  if is_active and access_token:
63
  connection_btn_html = f'''
64
+ <button class="btn-connect" onclick="openDrawer('{drawer_id}')">
65
  Connect
66
  </button>
67
  '''
 
81
  }
82
  mcp_config_json = json.dumps(mcp_config, indent=2)
83
  mcp_config_escaped = html.escape(mcp_config_json)
 
84
 
85
  # Example prompt
86
  example_prompt = f"Query the dataset {dataset_id} and show me a summary of the data. What columns are available and how many rows are there?"
87
  example_prompt_escaped = html.escape(example_prompt)
88
+
89
+ # Store data for copy buttons using base64 encoding to avoid JS escaping issues
90
+ copy_data[f"{drawer_id}-mcp"] = base64.b64encode(mcp_config_json.encode()).decode()
91
+ copy_data[f"{drawer_id}-prompt"] = base64.b64encode(example_prompt.encode()).decode()
92
+ copy_data[f"{drawer_id}-token"] = base64.b64encode(access_token.encode()).decode()
93
+
94
+ # Drawer HTML (slides in from right)
95
+ drawers_html += f'''
96
+ <div id="{drawer_id}" class="drawer-overlay" onclick="if(event.target===this) closeDrawer('{drawer_id}')">
97
+ <div class="drawer-panel">
98
+ <div class="drawer-header">
99
+ <div class="drawer-header-info">
100
+ <h3 class="drawer-title">{html.escape(dataset_id)}</h3>
101
+ <span class="drawer-status {status_class}">{status_text}</span>
102
  </div>
103
+ <button class="drawer-close" onclick="closeDrawer('{drawer_id}')">&times;</button>
104
  </div>
105
+ <div class="drawer-body">
106
  <!-- MCP Config - expanded by default -->
107
  <details class="config-accordion" open>
108
  <summary class="config-summary">
 
114
  <p class="config-desc">Add to your Claude Desktop config:</p>
115
  <div class="code-wrapper">
116
  <pre class="code-block">{mcp_config_escaped}</pre>
117
+ <button class="btn-copy" data-copy-id="{drawer_id}-mcp">Copy</button>
118
  </div>
119
  </div>
120
  </details>
 
129
  <div class="config-content">
130
  <div class="code-wrapper">
131
  <pre class="code-block prompt-block">{example_prompt_escaped}</pre>
132
+ <button class="btn-copy" data-copy-id="{drawer_id}-prompt">Copy</button>
133
  </div>
134
  </div>
135
  </details>
 
144
  <div class="config-content">
145
  <div class="code-wrapper">
146
  <code class="token-block">{html.escape(access_token)}</code>
147
+ <button class="btn-copy" data-copy-id="{drawer_id}-token">Copy</button>
148
  </div>
149
  </div>
150
  </details>
 
157
  </div>
158
  '''
159
 
160
+ # View on HF button removed - access only through MCP
161
  view_btn_html = ""
 
 
 
 
 
 
162
 
163
  # Determine plan display name
164
  plan_id = sub.get('plan_id', 'trial')
 
185
  </div>
186
  """
187
 
188
+ # Convert copy_data to JSON for embedding in script
189
+ copy_data_json = json.dumps(copy_data)
190
+
191
  result_html = f"""
192
  <div class="subscriptions-container">
193
  <div class="subscriptions-list">
194
  {cards_html}
195
  </div>
196
+ {drawers_html}
197
  </div>
198
 
199
  <script>
200
+ // Store copy data
201
+ var copyDataStore = {copy_data_json};
202
+
203
+ function openDrawer(id) {{
204
+ var drawer = document.getElementById(id);
205
+ if (drawer) {{
206
+ drawer.classList.add('show');
207
+ document.body.style.overflow = 'hidden';
208
+ }}
209
+ }}
210
+
211
+ function closeDrawer(id) {{
212
+ var drawer = document.getElementById(id);
213
+ if (drawer) {{
214
+ drawer.classList.remove('show');
215
+ document.body.style.overflow = '';
216
+ }}
217
+ }}
218
+
219
+ function copyFromDataAttr(btn) {{
220
+ var copyId = btn.getAttribute('data-copy-id');
221
+ if (!copyId || !copyDataStore[copyId]) {{
222
+ console.error('Copy data not found for:', copyId);
223
+ return;
224
+ }}
225
+ // Decode base64
226
+ var text = atob(copyDataStore[copyId]);
227
  navigator.clipboard.writeText(text).then(function() {{
228
+ var originalText = btn.textContent;
229
  btn.textContent = 'Copied!';
230
  btn.classList.add('copied');
231
  setTimeout(function() {{
232
  btn.textContent = originalText;
233
  btn.classList.remove('copied');
234
  }}, 2000);
235
+ }}).catch(function(err) {{
236
+ console.error('Failed to copy:', err);
237
+ // Fallback for older browsers
238
+ var textarea = document.createElement('textarea');
239
+ textarea.value = text;
240
+ textarea.style.position = 'fixed';
241
+ textarea.style.opacity = '0';
242
+ document.body.appendChild(textarea);
243
+ textarea.select();
244
+ try {{
245
+ document.execCommand('copy');
246
+ var originalText = btn.textContent;
247
+ btn.textContent = 'Copied!';
248
+ btn.classList.add('copied');
249
+ setTimeout(function() {{
250
+ btn.textContent = originalText;
251
+ btn.classList.remove('copied');
252
+ }}, 2000);
253
+ }} catch (e) {{
254
+ console.error('Fallback copy failed:', e);
255
+ }}
256
+ document.body.removeChild(textarea);
257
  }});
258
  }}
259
 
260
+ // Attach click handlers to copy buttons
261
+ document.addEventListener('click', function(e) {{
262
+ if (e.target.classList.contains('btn-copy')) {{
263
+ copyFromDataAttr(e.target);
264
+ }}
265
+ }});
266
+
267
  document.addEventListener('keydown', function(e) {{
268
  if (e.key === 'Escape') {{
269
+ document.querySelectorAll('.drawer-overlay.show').forEach(function(drawer) {{
270
+ drawer.classList.remove('show');
271
  }});
272
+ document.body.style.overflow = '';
273
  }}
274
  }});
275
  </script>
 
361
  .btn-connect:hover {{
362
  background: #0056b3;
363
  }}
364
+
365
+ /* Drawer - slides from right */
366
+ .drawer-overlay {{
367
+ display: none;
368
+ position: fixed;
369
+ top: 0;
370
+ left: 0;
371
+ right: 0;
372
+ bottom: 0;
373
+ width: 100vw;
374
+ height: 100vh;
375
+ background: rgba(0, 0, 0, 0.6);
376
+ backdrop-filter: blur(4px);
377
+ z-index: 99999;
378
+ margin: 0;
379
  }}
380
+ .drawer-overlay.show {{
381
+ display: block;
 
382
  }}
383
+ .drawer-panel {{
384
+ position: fixed;
385
+ top: 0;
386
+ right: 0;
387
+ width: 100%;
388
+ max-width: 420px;
389
+ height: 100vh;
390
+ background: var(--bg-primary, #000);
391
+ border-left: 1px solid var(--border-color, rgba(255,255,255,0.08));
392
+ box-shadow: -10px 0 40px rgba(0, 0, 0, 0.5);
393
+ display: flex;
394
+ flex-direction: column;
395
+ transform: translateX(100%);
396
+ animation: slideIn 0.25s ease forwards;
397
+ }}
398
+ @keyframes slideIn {{
399
+ to {{
400
+ transform: translateX(0);
401
+ }}
402
+ }}
403
+ .drawer-overlay:not(.show) .drawer-panel {{
404
+ animation: slideOut 0.2s ease forwards;
405
+ }}
406
+ @keyframes slideOut {{
407
+ from {{
408
+ transform: translateX(0);
409
+ }}
410
+ to {{
411
+ transform: translateX(100%);
412
+ }}
413
+ }}
414
+ .drawer-header {{
 
 
415
  display: flex;
416
  justify-content: space-between;
417
  align-items: center;
418
+ padding: 1.25rem 1.5rem;
419
  border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.08));
420
  gap: 1rem;
421
+ flex-shrink: 0;
422
  }}
423
+ .drawer-header-info {{
424
  display: flex;
425
  align-items: center;
426
  gap: 0.625rem;
427
  min-width: 0;
428
  flex: 1;
429
  }}
430
+ .drawer-title {{
431
  margin: 0;
432
+ font-size: 0.9375rem;
433
  font-weight: 600;
434
  color: var(--text-primary, #f5f5f7);
435
  font-family: 'SF Mono', Monaco, monospace;
 
437
  overflow: hidden;
438
  text-overflow: ellipsis;
439
  }}
440
+ .drawer-status {{
441
  padding: 0.125rem 0.375rem;
442
  border-radius: 4px;
443
  font-size: 0.5625rem;
 
445
  text-transform: uppercase;
446
  flex-shrink: 0;
447
  }}
448
+ .drawer-status.active {{
449
  background: rgba(52, 199, 89, 0.15);
450
  color: #34c759;
451
  }}
452
+ .drawer-close {{
453
  background: none;
454
  border: none;
455
+ font-size: 1.5rem;
456
  color: var(--text-tertiary, #6e6e73);
457
  cursor: pointer;
458
  padding: 0;
 
460
  transition: color 0.15s;
461
  flex-shrink: 0;
462
  }}
463
+ .drawer-close:hover {{
464
  color: var(--text-primary, #f5f5f7);
465
  }}
466
+ .drawer-body {{
467
+ padding: 1.25rem 1.5rem;
468
+ overflow-y: auto;
469
+ flex: 1;
470
  }}
471
 
472
  /* Accordion sections */
473
  .config-accordion {{
474
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
475
  border-radius: 8px;
476
+ margin-bottom: 0.75rem;
477
  overflow: hidden;
478
  }}
479
  .config-summary {{
480
  display: flex;
481
  align-items: center;
482
  gap: 0.5rem;
483
+ padding: 0.875rem 1rem;
484
  cursor: pointer;
485
  user-select: none;
486
  background: var(--bg-secondary, #1c1c1e);
 
494
  background: var(--bg-tertiary, #2c2c2e);
495
  }}
496
  .config-icon {{
497
+ font-size: 1rem;
498
  }}
499
  .config-label {{
500
+ font-size: 0.875rem;
501
  font-weight: 500;
502
  color: var(--text-primary, #f5f5f7);
503
  flex: 1;
 
514
  transform: rotate(180deg);
515
  }}
516
  .config-content {{
517
+ padding: 0.875rem 1rem;
518
  border-top: 1px solid var(--border-color, rgba(255,255,255,0.08));
519
  background: var(--bg-primary, #000);
520
  }}
521
  .config-desc {{
522
+ font-size: 0.8125rem;
523
  color: var(--text-tertiary, #6e6e73);
524
+ margin: 0 0 0.625rem 0;
525
  }}
526
  .code-wrapper {{
527
  position: relative;
528
  }}
529
  .code-block {{
530
+ padding: 0.875rem;
531
+ padding-right: 4rem;
532
  background: var(--bg-secondary, #1c1c1e);
533
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
534
  border-radius: 6px;
535
  font-family: 'SF Mono', Monaco, monospace;
536
+ font-size: 0.75rem;
537
  color: var(--text-primary, #f5f5f7);
538
  overflow-x: auto;
539
  white-space: pre;
 
546
  }}
547
  .token-block {{
548
  display: block;
549
+ padding: 0.625rem 0.875rem;
550
+ padding-right: 4rem;
551
  background: var(--bg-secondary, #1c1c1e);
552
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
553
  border-radius: 6px;
554
  font-family: 'SF Mono', Monaco, monospace;
555
+ font-size: 0.75rem;
556
  color: var(--text-primary, #f5f5f7);
557
  word-break: break-all;
558
  }}
 
560
  position: absolute;
561
  top: 0.5rem;
562
  right: 0.5rem;
563
+ padding: 0.375rem 0.625rem;
564
  background: var(--bg-tertiary, #2c2c2e);
565
  color: var(--text-secondary, #a1a1a6);
566
  border: 1px solid var(--border-color, rgba(255,255,255,0.08));
567
  border-radius: 4px;
568
+ font-size: 0.6875rem;
569
  font-weight: 500;
570
  cursor: pointer;
571
  transition: all 0.15s;
 
584
  display: flex;
585
  align-items: center;
586
  gap: 0.375rem;
587
+ padding: 0.75rem 1rem;
588
  background: rgba(0, 122, 255, 0.08);
589
  border-radius: 6px;
590
+ font-size: 0.75rem;
591
  color: var(--text-secondary, #a1a1a6);
592
+ margin-top: 0.5rem;
593
  }}
594
  .config-tip code {{
595
  background: var(--bg-secondary, #1c1c1e);
596
+ padding: 0.125rem 0.375rem;
597
  border-radius: 3px;
598
+ font-size: 0.6875rem;
599
  color: var(--text-primary, #f5f5f7);
600
  }}
601
 
 
607
  .subscription-actions {{
608
  justify-content: flex-start;
609
  }}
610
+ .drawer-panel {{
611
+ max-width: 100%;
 
612
  }}
613
  }}
614
  </style>