Nymbo commited on
Commit
64334e8
·
verified ·
1 Parent(s): af1292a

Changing tabbed UI for a Sidebar

Browse files
Files changed (1) hide show
  1. app.py +270 -131
app.py CHANGED
@@ -120,6 +120,18 @@ CSS_STYLES = """
120
  white-space: pre-wrap;
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  /* Historical safeguard: if any h1 appears inside tabs, don't attach pseudo content */
124
  .gradio-container [role=\"tabpanel\"] h1::before,
125
  .gradio-container [role=\"tabpanel\"] h1::after {
@@ -197,6 +209,13 @@ CSS_STYLES = """
197
  .info-card pre code {
198
  display: block;
199
  }
 
 
 
 
 
 
 
200
  .info-list {
201
  margin: 6px 0 0 18px;
202
  padding: 0;
@@ -222,58 +241,59 @@ CSS_STYLES = """
222
  }
223
  }
224
 
225
- /* Tabs - modern, evenly distributed full-width buttons */
226
- .gradio-container [role="tablist"] {
227
- display: flex;
228
- gap: 8px;
229
- flex-wrap: nowrap;
230
- align-items: stretch;
231
- width: 100%;
232
- }
233
- .gradio-container [role="tab"] {
234
- flex: 1 1 0;
235
- min-width: 0; /* allow shrinking to fit */
236
- display: inline-flex;
237
- justify-content: center;
238
- align-items: center;
239
- padding: 10px 12px;
240
- border-radius: 10px;
241
- border: 1px solid rgba(255, 255, 255, 0.08);
242
- background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.03));
243
- transition: background .2s ease, border-color .2s ease, box-shadow .2s ease, transform .06s ease;
244
- overflow: hidden;
245
- white-space: nowrap;
246
- text-overflow: ellipsis;
247
  }
248
- .gradio-container [role="tab"]:hover {
249
- border-color: rgba(99,102,241,0.28);
250
- background: linear-gradient(180deg, rgba(99,102,241,0.10), rgba(59,130,246,0.08));
 
 
 
251
  }
252
- .gradio-container [role="tab"][aria-selected="true"] {
253
- border-color: rgba(99,102,241,0.35);
254
- box-shadow: inset 0 0 0 1px rgba(99,102,241,0.25), 0 1px 2px rgba(0,0,0,0.25);
255
- background: linear-gradient(180deg, rgba(99,102,241,0.18), rgba(59,130,246,0.14));
256
- color: rgba(255, 255, 255, 0.95) !important;
 
 
 
 
 
 
 
 
257
  }
258
- .gradio-container [role="tab"]:active {
259
- transform: translateY(0.5px);
 
260
  }
261
- .gradio-container [role="tab"]:focus-visible {
262
- outline: none;
263
- box-shadow: 0 0 0 2px rgba(59,130,246,0.35);
 
 
 
264
  }
 
 
265
  @media (prefers-color-scheme: light) {
266
- .gradio-container [role="tab"] {
267
- border-color: rgba(0, 0, 0, 0.08);
268
- background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(255,255,255,0.90));
 
269
  }
270
- .gradio-container [role="tab"]:hover {
271
- border-color: rgba(99,102,241,0.25);
272
- background: linear-gradient(180deg, rgba(99,102,241,0.08), rgba(59,130,246,0.06));
273
  }
274
- .gradio-container [role="tab"][aria-selected="true"] {
275
- border-color: rgba(99,102,241,0.35);
276
- background: linear-gradient(180deg, rgba(99,102,241,0.16), rgba(59,130,246,0.12));
277
  color: rgba(0, 0, 0, 0.85) !important;
278
  }
279
  }
@@ -299,6 +319,63 @@ CSS_STYLES = """
299
  background-color: var(--input-background-fill) !important;
300
  color: var(--body-text-color) !important;
301
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  """
303
 
304
  # Build each tab interface using modular builders
@@ -345,108 +422,170 @@ _tab_names = [
345
  ]
346
 
347
  with gr.Blocks(title="Nymbo/Tools MCP") as demo:
348
- # Title and information panel unchanged to preserve UI
349
- gr.HTML("<h1 class='app-title'>Nymbo/Tools MCP</h1>")
350
-
351
- with gr.Accordion("Information", open=False):
352
- gr.HTML(
353
- """
354
- <div class="info-accordion">
355
- <div class="info-grid">
356
- <section class="info-card">
357
- <div class="info-card__icon">🔐</div>
358
- <div class="info-card__body">
359
- <h3>Enable Image Gen, Video Gen, and Deep Research</h3>
360
- <p>
361
- The <code>Generate_Image</code>, <code>Generate_Video</code>, and <code>Deep_Research</code> tools require a
362
- <code>HF_READ_TOKEN</code> set as a secret or environment variable.
363
- </p>
364
- <ul class="info-list">
365
- <li>Duplicate this Space and add a HF token with model read access.</li>
366
- <li>Or run locally with <code>HF_READ_TOKEN</code> in your environment.</li>
367
- </ul>
368
- <div class="info-hint">
369
- MCP clients can see these tools even without tokens, but calls will fail until a valid token is provided.
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  </div>
371
- </div>
372
- </section>
373
-
374
- <section class="info-card">
375
- <div class="info-card__icon">🧠</div>
376
- <div class="info-card__body">
377
- <h3>Persistent Memories and Files</h3>
378
- <p>
379
- In this public demo, memories and files created with the <code>Memory_Manager</code> and <code>File_System</code> are stored in the Space's running container and are cleared when the Space restarts. Content is visible to everyone—avoid personal data.
380
- </p>
381
- <p>
382
- When running locally, memories are saved to <code>memories.json</code> at the repo root for privacy, and files are saved to the <code>Tools/Filesystem</code> directory on disk.
383
- </p>
384
- </div>
385
- </section>
386
-
387
- <section class="info-card">
388
- <div class="info-card__icon">🔗</div>
389
- <div class="info-card__body">
390
- <h3>Connecting from an MCP Client</h3>
391
- <p>
392
- This Space also runs as a Model Context Protocol (MCP) server. Point your client to:
393
- <br/>
394
- <code>https://nymbo.net/gradio_api/mcp/</code>
395
- </p>
396
- <p>Example client configuration:</p>
397
- <pre><code class="language-json">{
398
  "mcpServers": {
399
  "nymbo-tools": {
400
  "url": "https://nymbo.net/gradio_api/mcp/"
401
  }
402
  }
403
  }</code></pre>
404
- <p>Run the following commands in sequence to run the server locally:</p>
405
- <pre><code>git clone https://huggingface.co/spaces/Nymbo/Tools
406
  cd Tools
407
  python -m venv env
408
  source env/bin/activate
409
  pip install -r requirements.txt
410
  python app.py</code></pre>
411
- </div>
412
- </section>
413
-
414
- <section class="info-card">
415
- <div class="info-card__icon">🛠️</div>
416
- <div class="info-card__body">
417
- <h3>Tool Notes &amp; Kokoro Voice Legend</h3>
418
- <p>
419
- No authentication required for: <code>Web_Fetch</code>, <code>Web_Search</code>,
420
- <code>Agent_Terminal</code>, <code>Code_Interpreter</code>, <code>Memory_Manager</code>, <code>Generate_Speech</code>, <code>File_System</code>, <code>Shell_Command</code>, and <code>Agent_Terminal</code>.
421
- </p>
422
- <p><strong>Kokoro voice prefixes</strong></p>
423
- <ul class="info-list" style="display:grid;grid-template-columns:repeat(2,minmax(160px,1fr));gap:6px 16px;">
424
- <li><code>af</code> — American female</li>
425
- <li><code>am</code> — American male</li>
426
- <li><code>bf</code> — British female</li>
427
- <li><code>bm</code> — British male</li>
428
- <li><code>ef</code> European female</li>
429
- <li><code>em</code> European male</li>
430
- <li><code>hf</code> — Hindi female</li>
431
- <li><code>hm</code> Hindi male</li>
432
- <li><code>if</code> Italian female</li>
433
- <li><code>im</code> Italian male</li>
434
- <li><code>jf</code> Japanese female</li>
435
- <li><code>jm</code> — Japanese male</li>
436
- <li><code>pf</code> — Portuguese female</li>
437
- <li><code>pm</code> — Portuguese male</li>
438
- <li><code>zf</code> Chinese female</li>
439
- <li><code>zm</code> Chinese male</li>
440
- <li><code>ff</code> — French female</li>
441
- </ul>
442
- </div>
443
- </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  </div>
445
- </div>
446
- """
 
 
 
 
 
 
 
 
 
447
  )
448
 
449
- gr.TabbedInterface(interface_list=_interfaces, tab_names=_tab_names)
 
 
 
 
 
 
 
 
 
 
 
450
 
451
  if __name__ == "__main__":
452
  demo.launch(mcp_server=True, theme="Nymbo/Nymbo_Theme", css=CSS_STYLES, ssr_mode=False)
 
120
  white-space: pre-wrap;
121
  }
122
 
123
+ /* Sidebar Container */
124
+ .app-sidebar {
125
+ background-color: rgba(255, 255, 255, 0.02) !important;
126
+ border-right: 1px solid rgba(255, 255, 255, 0.08) !important;
127
+ }
128
+ @media (prefers-color-scheme: light) {
129
+ .app-sidebar {
130
+ background-color: rgba(0, 0, 0, 0.02) !important;
131
+ border-right: 1px solid rgba(0, 0, 0, 0.08) !important;
132
+ }
133
+ }
134
+
135
  /* Historical safeguard: if any h1 appears inside tabs, don't attach pseudo content */
136
  .gradio-container [role=\"tabpanel\"] h1::before,
137
  .gradio-container [role=\"tabpanel\"] h1::after {
 
209
  .info-card pre code {
210
  display: block;
211
  }
212
+ .info-card p {
213
+ word-wrap: break-word;
214
+ overflow-wrap: break-word;
215
+ }
216
+ .info-card p code {
217
+ word-break: break-all;
218
+ }
219
  .info-list {
220
  margin: 6px 0 0 18px;
221
  padding: 0;
 
241
  }
242
  }
243
 
244
+ /* Sidebar Navigation - styled like the previous tabs */
245
+ .sidebar-nav {
246
+ background: transparent !important;
247
+ border: none !important;
248
+ padding: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  }
250
+ .sidebar-nav .form {
251
+ gap: 8px !important;
252
+ display: flex !important;
253
+ flex-direction: column !important;
254
+ border: none !important;
255
+ background: transparent !important;
256
  }
257
+ .sidebar-nav label {
258
+ display: flex !important;
259
+ align-items: center !important;
260
+ padding: 10px 12px !important;
261
+ border-radius: 10px !important;
262
+ border: 1px solid rgba(255, 255, 255, 0.08) !important;
263
+ background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.03)) !important;
264
+ transition: background .2s ease, border-color .2s ease, box-shadow .2s ease, transform .06s ease !important;
265
+ cursor: pointer !important;
266
+ margin-bottom: 0 !important;
267
+ width: 100% !important;
268
+ justify-content: flex-start !important;
269
+ text-align: left !important;
270
  }
271
+ .sidebar-nav label:hover {
272
+ border-color: rgba(99,102,241,0.28) !important;
273
+ background: linear-gradient(180deg, rgba(99,102,241,0.10), rgba(59,130,246,0.08)) !important;
274
  }
275
+ /* Selected state - Gradio adds 'selected' class to the label in some versions, or we check input:checked */
276
+ .sidebar-nav label.selected {
277
+ border-color: rgba(99,102,241,0.35) !important;
278
+ box-shadow: inset 0 0 0 1px rgba(99,102,241,0.25), 0 1px 2px rgba(0,0,0,0.25) !important;
279
+ background: linear-gradient(180deg, rgba(99,102,241,0.18), rgba(59,130,246,0.14)) !important;
280
+ color: rgba(255, 255, 255, 0.95) !important;
281
  }
282
+
283
+ /* Light theme adjustments for sidebar */
284
  @media (prefers-color-scheme: light) {
285
+ .sidebar-nav label {
286
+ border-color: rgba(0, 0, 0, 0.08) !important;
287
+ background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(255,255,255,0.90)) !important;
288
+ color: rgba(0, 0, 0, 0.85) !important;
289
  }
290
+ .sidebar-nav label:hover {
291
+ border-color: rgba(99,102,241,0.25) !important;
292
+ background: linear-gradient(180deg, rgba(99,102,241,0.08), rgba(59,130,246,0.06)) !important;
293
  }
294
+ .sidebar-nav label.selected {
295
+ border-color: rgba(99,102,241,0.35) !important;
296
+ background: linear-gradient(180deg, rgba(99,102,241,0.16), rgba(59,130,246,0.12)) !important;
297
  color: rgba(0, 0, 0, 0.85) !important;
298
  }
299
  }
 
319
  background-color: var(--input-background-fill) !important;
320
  color: var(--body-text-color) !important;
321
  }
322
+
323
+ /* Hide the actual tabs since we use the sidebar to control them */
324
+ .hidden-tabs .tab-nav,
325
+ .hidden-tabs [role="tablist"] {
326
+ display: none !important;
327
+ }
328
+ /* Hide the entire first row of the tabs container (contains tab buttons + overflow) */
329
+ .hidden-tabs > div:first-child {
330
+ display: none !important;
331
+ }
332
+ /* Ensure audio component buttons remain visible - they're inside tab panels, not the first row */
333
+ .hidden-tabs [role="tabpanel"] button {
334
+ display: inline-flex !important;
335
+ }
336
+
337
+ /* Custom scrollbar styling */
338
+ * {
339
+ scrollbar-width: thin;
340
+ scrollbar-color: rgba(61, 212, 159, 0.4) rgba(255, 255, 255, 0.05);
341
+ }
342
+ *::-webkit-scrollbar {
343
+ width: 8px;
344
+ height: 8px;
345
+ }
346
+ *::-webkit-scrollbar-track {
347
+ background: rgba(255, 255, 255, 0.05);
348
+ border-radius: 4px;
349
+ }
350
+ *::-webkit-scrollbar-thumb {
351
+ background: linear-gradient(180deg, rgba(61, 212, 159, 0.5), rgba(17, 186, 136, 0.4));
352
+ border-radius: 4px;
353
+ border: 1px solid rgba(119, 247, 209, 0.2);
354
+ }
355
+ *::-webkit-scrollbar-thumb:hover {
356
+ background: linear-gradient(180deg, rgba(85, 250, 192, 0.7), rgba(65, 184, 131, 0.6));
357
+ }
358
+ *::-webkit-scrollbar-corner {
359
+ background: rgba(255, 255, 255, 0.05);
360
+ }
361
+ @media (prefers-color-scheme: light) {
362
+ * {
363
+ scrollbar-color: rgba(61, 212, 159, 0.4) rgba(0, 0, 0, 0.05);
364
+ }
365
+ *::-webkit-scrollbar-track {
366
+ background: rgba(0, 0, 0, 0.05);
367
+ }
368
+ *::-webkit-scrollbar-thumb {
369
+ background: linear-gradient(180deg, rgba(61, 212, 159, 0.5), rgba(17, 186, 136, 0.4));
370
+ border-color: rgba(0, 0, 0, 0.1);
371
+ }
372
+ *::-webkit-scrollbar-thumb:hover {
373
+ background: linear-gradient(180deg, rgba(85, 250, 192, 0.7), rgba(65, 184, 131, 0.6));
374
+ }
375
+ *::-webkit-scrollbar-corner {
376
+ background: rgba(0, 0, 0, 0.05);
377
+ }
378
+ }
379
  """
380
 
381
  # Build each tab interface using modular builders
 
422
  ]
423
 
424
  with gr.Blocks(title="Nymbo/Tools MCP") as demo:
425
+
426
+ with gr.Sidebar(width=300, elem_classes="app-sidebar"):
427
+ gr.Markdown("## Nymbo/Tools MCP\n<p style='font-size: 0.7rem; opacity: 0.85; margin-top: 2px;'>General purpose tools useful for any agent.</p>\n<code style='font-size: 0.7rem; word-break: break-all;'>https://nymbo.net/gradio_api/mcp/</code>")
428
+
429
+ with gr.Accordion("Information", open=False):
430
+ gr.HTML(
431
+ """
432
+ <div class="info-accordion">
433
+ <div class="info-grid" style="grid-template-columns: 1fr;">
434
+ <section class="info-card">
435
+ <div class="info-card__body">
436
+ <h3>Enable Image Gen, Video Gen, and Deep Research</h3>
437
+ <p>
438
+ The <code>Generate_Image</code>, <code>Generate_Video</code>, and <code>Deep_Research</code> tools require a
439
+ <code>HF_READ_TOKEN</code> set as a secret or environment variable.
440
+ </p>
441
+ <ul class="info-list">
442
+ <li>Duplicate this Space and add a HF token with model read access.</li>
443
+ <li>Or run locally with <code>HF_READ_TOKEN</code> in your environment.</li>
444
+ </ul>
445
+ <div class="info-hint">
446
+ MCP clients can see these tools even without tokens, but calls will fail until a valid token is provided.
447
+ </div>
448
+ </div>
449
+ </section>
450
+
451
+ <section class="info-card">
452
+ <div class="info-card__body">
453
+ <h3>Persistent Memories and Files</h3>
454
+ <p>
455
+ In this public demo, memories and files created with the <code>Memory_Manager</code> and <code>File_System</code> are stored in the Space's running container and are cleared when the Space restarts. Content is visible to everyone—avoid personal data.
456
+ </p>
457
+ <p>
458
+ When running locally, memories are saved to <code>memories.json</code> at the repo root for privacy, and files are saved to the <code>Tools/Filesystem</code> directory on disk.
459
+ </p>
460
  </div>
461
+ </section>
462
+
463
+ <section class="info-card">
464
+ <div class="info-card__body">
465
+ <h3>Connecting from an MCP Client</h3>
466
+ <p>
467
+ This Space also runs as a Model Context Protocol (MCP) server. Point your client to:
468
+ <br/>
469
+ <code>https://nymbo.net/gradio_api/mcp/</code>
470
+ </p>
471
+ <p>Example client configuration:</p>
472
+ <pre><code class="language-json">{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  "mcpServers": {
474
  "nymbo-tools": {
475
  "url": "https://nymbo.net/gradio_api/mcp/"
476
  }
477
  }
478
  }</code></pre>
479
+ <p>Run the following commands in sequence to run the server locally:</p>
480
+ <pre><code>git clone https://huggingface.co/spaces/Nymbo/Tools
481
  cd Tools
482
  python -m venv env
483
  source env/bin/activate
484
  pip install -r requirements.txt
485
  python app.py</code></pre>
486
+ </div>
487
+ </section>
488
+
489
+ <section class="info-card">
490
+ <div class="info-card__body">
491
+ <h3>Tool Notes &amp; Kokoro Voice Legend</h3>
492
+ <p><strong>No authentication required for:</strong></p>
493
+ <ul class="info-list">
494
+ <li><code>Web_Fetch</code></li>
495
+ <li><code>Web_Search</code></li>
496
+ <li><code>Agent_Terminal</code></li>
497
+ <li><code>Code_Interpreter</code></li>
498
+ <li><code>Memory_Manager</code></li>
499
+ <li><code>Generate_Speech</code></li>
500
+ <li><code>File_System</code></li>
501
+ <li><code>Shell_Command</code></li>
502
+ </ul>
503
+ <p><strong>Kokoro voice prefixes</strong></p>
504
+ <table style="width:100%; border-collapse:collapse; font-size:0.9em; margin-top:8px;">
505
+ <thead>
506
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.15);">
507
+ <th style="padding:6px 8px; text-align:left;">Accent</th>
508
+ <th style="padding:6px 8px; text-align:center;">Female</th>
509
+ <th style="padding:6px 8px; text-align:center;">Male</th>
510
+ </tr>
511
+ </thead>
512
+ <tbody>
513
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
514
+ <td style="padding:6px 8px; font-weight:600;">American</td>
515
+ <td style="padding:6px 8px; text-align:center;"><code>af</code></td>
516
+ <td style="padding:6px 8px; text-align:center;"><code>am</code></td>
517
+ </tr>
518
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
519
+ <td style="padding:6px 8px; font-weight:600;">British</td>
520
+ <td style="padding:6px 8px; text-align:center;"><code>bf</code></td>
521
+ <td style="padding:6px 8px; text-align:center;"><code>bm</code></td>
522
+ </tr>
523
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
524
+ <td style="padding:6px 8px; font-weight:600;">European</td>
525
+ <td style="padding:6px 8px; text-align:center;"><code>ef</code></td>
526
+ <td style="padding:6px 8px; text-align:center;"><code>em</code></td>
527
+ </tr>
528
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
529
+ <td style="padding:6px 8px; font-weight:600;">French</td>
530
+ <td style="padding:6px 8px; text-align:center;"><code>ff</code></td>
531
+ <td style="padding:6px 8px; text-align:center;">—</td>
532
+ </tr>
533
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
534
+ <td style="padding:6px 8px; font-weight:600;">Hindi</td>
535
+ <td style="padding:6px 8px; text-align:center;"><code>hf</code></td>
536
+ <td style="padding:6px 8px; text-align:center;"><code>hm</code></td>
537
+ </tr>
538
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
539
+ <td style="padding:6px 8px; font-weight:600;">Italian</td>
540
+ <td style="padding:6px 8px; text-align:center;"><code>if</code></td>
541
+ <td style="padding:6px 8px; text-align:center;"><code>im</code></td>
542
+ </tr>
543
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
544
+ <td style="padding:6px 8px; font-weight:600;">Japanese</td>
545
+ <td style="padding:6px 8px; text-align:center;"><code>jf</code></td>
546
+ <td style="padding:6px 8px; text-align:center;"><code>jm</code></td>
547
+ </tr>
548
+ <tr style="border-bottom:1px solid rgba(255,255,255,0.08);">
549
+ <td style="padding:6px 8px; font-weight:600;">Portuguese</td>
550
+ <td style="padding:6px 8px; text-align:center;"><code>pf</code></td>
551
+ <td style="padding:6px 8px; text-align:center;"><code>pm</code></td>
552
+ </tr>
553
+ <tr>
554
+ <td style="padding:6px 8px; font-weight:600;">Chinese</td>
555
+ <td style="padding:6px 8px; text-align:center;"><code>zf</code></td>
556
+ <td style="padding:6px 8px; text-align:center;"><code>zm</code></td>
557
+ </tr>
558
+ </tbody>
559
+ </table>
560
+ </div>
561
+ </section>
562
+ </div>
563
  </div>
564
+ """
565
+ )
566
+
567
+ gr.Markdown("### Tools")
568
+ tool_selector = gr.Radio(
569
+ choices=_tab_names,
570
+ value=_tab_names[0],
571
+ label="Select Tool",
572
+ show_label=False,
573
+ container=False,
574
+ elem_classes="sidebar-nav"
575
  )
576
 
577
+ with gr.Tabs(elem_classes="hidden-tabs", selected=_tab_names[0]) as tool_tabs:
578
+ for name, interface in zip(_tab_names, _interfaces):
579
+ with gr.TabItem(label=name, id=name, elem_id=f"tab-{name}"):
580
+ interface.render()
581
+
582
+ # Use JavaScript to click the hidden tab button when the radio selection changes
583
+ tool_selector.change(
584
+ fn=None,
585
+ inputs=tool_selector,
586
+ outputs=None,
587
+ js="(selected_tool) => { const buttons = document.querySelectorAll('.hidden-tabs button'); buttons.forEach(btn => { if (btn.innerText.trim() === selected_tool) { btn.click(); } }); }"
588
+ )
589
 
590
  if __name__ == "__main__":
591
  demo.launch(mcp_server=True, theme="Nymbo/Nymbo_Theme", css=CSS_STYLES, ssr_mode=False)