SeaWolf-AI commited on
Commit
7887818
ยท
verified ยท
1 Parent(s): eb26902

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -253
app.py CHANGED
@@ -341,289 +341,189 @@ def generate_reply(
341
 
342
 
343
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
344
- # 6. GRADIO CHAT INTERFACE โ€” ZeroGPU compatible (must use demo.launch())
345
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
346
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
347
- # 6. GRADIO UI โ€” Custom design inspired by index.html
348
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
349
 
350
  CSS = """
351
  @import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap');
352
  @import url('https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@400;500;700;800&display=swap');
353
-
354
- :root {
355
- --v: #6d28d9; --v2: #7c3aed; --teal: #0d9488;
356
- --cream: #faf8f5; --ink: #1c1917; --ink3: #78716c; --ink4: #a8a29e;
357
- --line: #e4dfd8; --fog: #ede9e3;
358
- }
359
-
360
- /* Global overrides */
361
- .gradio-container { background: var(--cream) !important; font-family: 'Cabinet Grotesk', sans-serif !important; }
362
- footer { display: none !important; }
363
-
364
- /* Animated background orbs */
365
- #orbs { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
366
- .orb { position: absolute; border-radius: 50%; filter: blur(80px); opacity: .45; animation: drift linear infinite; }
367
- .orb1 { width: 500px; height: 500px; background: radial-gradient(circle, rgba(109,40,217,.35), transparent 70%); top: -100px; left: -100px; animation-duration: 22s; }
368
- .orb2 { width: 380px; height: 380px; background: radial-gradient(circle, rgba(16,185,129,.28), transparent 70%); top: 5%; right: -60px; animation-duration: 28s; animation-delay: -9s; }
369
- .orb3 { width: 320px; height: 320px; background: radial-gradient(circle, rgba(252,211,77,.22), transparent 70%); bottom: -60px; left: 35%; animation-duration: 19s; animation-delay: -5s; }
370
- @keyframes drift { 0%{transform:translate(0,0) scale(1)} 33%{transform:translate(40px,-30px) scale(1.05)} 66%{transform:translate(-20px,20px) scale(.97)} 100%{transform:translate(0,0) scale(1)} }
371
- .bg-grid { position: absolute; inset: 0; background-image: radial-gradient(circle, rgba(28,25,23,.05) 1px, transparent 1px); background-size: 28px 28px; }
372
-
373
- /* Header banner */
374
- #header-banner { text-align: center; padding: 24px 16px 8px; position: relative; z-index: 1; }
375
- #header-banner .logo-icon { display: inline-flex; width: 56px; height: 56px; border-radius: 16px; background: linear-gradient(135deg, #6d28d9, #a78bfa, #10b981); align-items: center; justify-content: center; font-size: 28px; box-shadow: 0 8px 30px rgba(109,40,217,.25); margin-bottom: 10px; animation: float 4s ease-in-out infinite; }
376
- @keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-6px)} }
377
- #header-banner h1 { font-family: 'Instrument Serif', serif !important; font-size: 32px !important; color: var(--ink) !important; font-weight: 400 !important; margin: 0 !important; padding: 0 !important; background: none !important; }
378
- #header-banner h1 em { color: var(--v); font-style: italic; }
379
- #header-banner .sub { font-size: 13px; color: var(--ink3); margin-top: 4px; }
380
- #header-banner .badges { display: flex; gap: 8px; justify-content: center; margin-top: 10px; flex-wrap: wrap; }
381
- #header-banner .badge { font-size: 11px; font-weight: 600; padding: 4px 12px; border-radius: 20px; font-family: 'Geist Mono', monospace; }
382
- .wb-purple { background: rgba(109,40,217,.08); color: var(--v); }
383
- .wb-green { background: rgba(22,163,74,.08); color: #16a34a; }
384
- .wb-amber { background: rgba(217,119,6,.08); color: #d97706; }
385
- .wb-teal { background: rgba(13,148,136,.08); color: var(--teal); }
386
-
387
- /* Model card */
388
- #model-card { margin: 8px auto; max-width: 680px; padding: 14px 18px; border-radius: 12px; border: 1.5px solid rgba(109,40,217,.2); background: linear-gradient(135deg, rgba(109,40,217,.04), rgba(16,185,129,.03)); position: relative; z-index: 1; }
389
- #model-card .mc-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
390
- #model-card .mc-name { font-size: 13px; font-weight: 700; color: var(--ink); }
391
- #model-card .mc-arch { font-size: 9px; font-weight: 700; padding: 2px 8px; border-radius: 10px; background: rgba(109,40,217,.08); color: var(--v); font-family: 'Geist Mono', monospace; letter-spacing: .4px; }
392
- #model-card .mc-stats { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
393
- #model-card .mc-stat { font-size: 9px; font-weight: 600; padding: 2px 7px; border-radius: 6px; font-family: 'Geist Mono', monospace; }
394
- .mc-hl { background: rgba(109,40,217,.08); color: var(--v); }
395
- .mc-ok { background: rgba(22,163,74,.09); color: #16a34a; }
396
- #model-card .mc-desc { font-size: 10px; color: var(--ink3); line-height: 1.6; }
397
- #model-card .mc-links { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
398
- #model-card .mc-links a { font-size: 10px; font-weight: 700; padding: 3px 10px; border-radius: 16px; text-decoration: none; transition: all .2s; display: inline-flex; align-items: center; gap: 4px; }
399
-
400
- /* Example cards */
401
- #examples-area { max-width: 680px; margin: 12px auto; display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; position: relative; z-index: 1; }
402
- .ex-card { background: white; border: 1.5px solid var(--line); border-radius: 12px; padding: 12px 14px; cursor: pointer; transition: all .22s; }
403
- .ex-card:hover { border-color: rgba(109,40,217,.3); box-shadow: 0 2px 12px rgba(28,25,23,.06); transform: translateY(-2px); }
404
- .ex-icon { font-size: 18px; display: block; margin-bottom: 4px; }
405
- .ex-title { font-size: 12px; font-weight: 700; color: var(--ink); margin-bottom: 2px; }
406
- .ex-desc { font-size: 10px; color: var(--ink4); }
407
-
408
- /* Chat area styling */
409
- #chatbot { border-radius: 16px !important; border: 1.5px solid var(--line) !important; background: rgba(255,255,255,.7) !important; backdrop-filter: blur(12px); position: relative; z-index: 1; }
410
- #chatbot .message { font-family: 'Cabinet Grotesk', sans-serif !important; }
411
- #chatbot .bot { background: rgba(109,40,217,.03) !important; border-left: 3px solid rgba(109,40,217,.2) !important; }
412
-
413
- /* Settings row */
414
- #settings-row { position: relative; z-index: 1; }
415
- #settings-row .gr-accordion { border-radius: 12px !important; border: 1.5px solid var(--line) !important; background: rgba(255,255,255,.6) !important; }
416
-
417
- /* Input area */
418
- #chat-input { border-radius: 14px !important; border: 1.5px solid var(--line) !important; background: white !important; }
419
- #chat-input:focus-within { border-color: rgba(109,40,217,.35) !important; box-shadow: 0 0 0 3px rgba(109,40,217,.08) !important; }
420
-
421
- /* Submit button */
422
- .primary { background: linear-gradient(135deg, var(--v), var(--v2)) !important; border: none !important; border-radius: 12px !important; }
423
-
424
- /* Model dropdown */
425
- #model-select .wrap { border-color: rgba(109,40,217,.2) !important; }
426
- #model-select .wrap:focus-within { border-color: var(--v) !important; box-shadow: 0 0 0 3px rgba(109,40,217,.08) !important; }
427
-
428
- @media(max-width:768px) {
429
- #examples-area { grid-template-columns: 1fr; }
430
- #header-banner h1 { font-size: 24px !important; }
431
- }
432
- """
433
-
434
- HEADER_HTML = """
435
- <div id="orbs"><div class="orb orb1"></div><div class="orb orb2"></div><div class="orb orb3"></div><div class="bg-grid"></div></div>
436
- <div id="header-banner">
437
- <div class="logo-icon">๐Ÿ’Ž</div>
438
- <h1>Hello, I'm <em>Gemma 4</em></h1>
439
- <div class="sub">Google DeepMind's most intelligent open model โ€” Dense 31B or MoE 26B-A4B ยท Vision ยท Thinking ยท Apache 2.0</div>
440
- <div class="badges">
441
- <span class="badge wb-purple">AIME 89.2%</span>
442
- <span class="badge wb-green">GPQA 84.3%</span>
443
- <span class="badge wb-amber">Codeforces 2150</span>
444
- <span class="badge wb-teal">256K context</span>
445
- <a href="https://huggingface.co/collections/google/gemma-4" target="_blank" class="badge wb-purple" style="text-decoration:none;">๐Ÿค— Gemma 4 โ†—</a>
446
- </div>
447
- </div>
448
  """
449
 
450
- MODEL_CARD_HTML = """
451
- <div id="model-card">
452
- <div class="mc-top">
453
- <span class="mc-name" id="mcName">Gemma-4-26B-A4B-it</span>
454
- <span class="mc-arch" id="mcArch">MoE 3.8B / 26B</span>
455
- </div>
456
- <div class="mc-stats" id="mcStats">
457
- <span class="mc-stat mc-hl">GPQA 82.3%</span>
458
- <span class="mc-stat mc-ok">AIME 88.3%</span>
459
- <span class="mc-stat mc-ok">๐Ÿ‘๏ธ Vision</span>
460
- <span class="mc-stat mc-ok">256K ctx</span>
461
- </div>
462
- <div class="mc-desc" id="mcDesc">MoE 128 experts ยท 3.8B active params ยท 31B์˜ 95% ์„ฑ๋Šฅ, ์ถ”๋ก  ~8๋ฐฐ ๋น ๋ฆ„ ยท 140+ languages</div>
463
- <div class="mc-links">
464
- <a id="mcHfLink" href="https://huggingface.co/google/gemma-4-26B-A4B-it" target="_blank" style="background:rgba(109,40,217,.08);color:#6d28d9;border:1px solid rgba(109,40,217,.15);">๐Ÿค— Model Card โ†—</a>
465
- <a href="https://deepmind.google/models/gemma/gemma-4/" target="_blank" style="background:rgba(16,185,129,.08);color:#059669;border:1px solid rgba(16,185,129,.15);">๐Ÿ”ฌ DeepMind โ†—</a>
466
- </div>
467
- </div>
468
- """
469
 
470
- EXAMPLES_HTML = """
471
- <div id="examples-area">
472
- <div class="ex-card" onclick="document.querySelector('#chat-input textarea').value='Explain how Gemma 4 MoE architecture works with 128 experts and hybrid attention.';document.querySelector('#chat-input textarea').dispatchEvent(new Event('input',{bubbles:true}));">
473
- <span class="ex-icon">๐Ÿ’Ž</span><div class="ex-title">Gemma 4 Architecture</div><div class="ex-desc">MoE + hybrid attention explained</div>
474
- </div>
475
- <div class="ex-card" onclick="document.querySelector('#chat-input textarea').value='Write a Python async web scraper with retry logic, rate limiting, and type hints.';document.querySelector('#chat-input textarea').dispatchEvent(new Event('input',{bubbles:true}));">
476
- <span class="ex-icon">๐Ÿ’ป</span><div class="ex-title">Code Generation</div><div class="ex-desc">Production-quality Python</div>
477
- </div>
478
- <div class="ex-card" onclick="document.querySelector('#chat-input textarea').value='ํ•œ๊ตญ์˜ K-pop์ด ์„ธ๊ณ„์ ์œผ๋กœ ์„ฑ๊ณตํ•œ ์ด์œ ๋ฅผ ๋ฌธํ™”์ , ๊ฒฝ์ œ์  ๊ด€์ ์—์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.';document.querySelector('#chat-input textarea').dispatchEvent(new Event('input',{bubbles:true}));">
479
- <span class="ex-icon">๐ŸŒ</span><div class="ex-title">140+ Languages</div><div class="ex-desc">Korean, Japanese, Arabicโ€ฆ</div>
480
- </div>
481
- <div class="ex-card" onclick="document.querySelector('#chat-input textarea').value='Solve: Find all real solutions to xยณ - 6xยฒ + 11x - 6 = 0. Show step-by-step reasoning.';document.querySelector('#chat-input textarea').dispatchEvent(new Event('input',{bubbles:true}));">
482
- <span class="ex-icon">๐Ÿงฎ</span><div class="ex-title">Math Reasoning</div><div class="ex-desc">Step-by-step problem solving</div>
483
- </div>
484
  </div>
485
- """
486
-
487
- # Model switcher JS (injected into the page)
488
- MODEL_SWITCH_JS = """
489
- <script>
490
- const MODEL_INFO = {
491
- 'Gemma-4-26B-A4B-it': {
492
- name: 'Gemma-4-26B-A4B-it', arch: 'MoE 3.8B / 26B',
493
- stats: '<span class="mc-stat mc-hl">GPQA 82.3%</span><span class="mc-stat mc-ok">AIME 88.3%</span><span class="mc-stat mc-ok">๐Ÿ‘๏ธ Vision</span><span class="mc-stat mc-ok">256K ctx</span>',
494
- desc: 'MoE 128 experts ยท 3.8B active params ยท 31B์˜ 95% ์„ฑ๋Šฅ, ์ถ”๋ก  ~8๋ฐฐ ๋น ๋ฆ„ ยท 140+ languages',
495
- hf: 'https://huggingface.co/google/gemma-4-26B-A4B-it'
496
- },
497
- 'Gemma-4-31B-it': {
498
- name: 'Gemma-4-31B-it', arch: 'Dense 30.7B',
499
- stats: '<span class="mc-stat mc-hl">AIME 89.2%</span><span class="mc-stat mc-ok">GPQA 84.3%</span><span class="mc-stat mc-ok">๐Ÿ‘๏ธ Vision</span><span class="mc-stat mc-ok">256K ctx</span>',
500
- desc: 'Dense 31B ยท ์ตœ๊ณ  ํ’ˆ์งˆ ยท Codeforces 2150 ยท Arena ์˜คํ”ˆ๋ชจ๋ธ 3์œ„ ยท 140+ languages',
501
- hf: 'https://huggingface.co/google/gemma-4-31B-it'
502
- }
 
 
 
 
 
 
 
 
503
  };
504
- // Watch for model dropdown changes
505
- new MutationObserver(() => {
506
- const sel = document.querySelector('#model-select input');
507
- if (!sel) return;
508
- const val = sel.value;
509
- const m = MODEL_INFO[val];
510
- if (!m) return;
511
- const n = document.getElementById('mcName');
512
- if (n && n.textContent !== m.name) {
513
- n.textContent = m.name;
514
- document.getElementById('mcArch').textContent = m.arch;
515
- document.getElementById('mcStats').innerHTML = m.stats;
516
- document.getElementById('mcDesc').textContent = m.desc;
517
- document.getElementById('mcHfLink').href = m.hf;
518
- }
519
- }).observe(document.body, {subtree: true, childList: true, characterData: true});
520
- </script>
521
- """
522
-
523
 
524
- with gr.Blocks(title="๐Ÿ’Ž Gemma 4 Playground", fill_height=True) as demo:
525
 
526
- gr.HTML(HEADER_HTML)
527
- gr.HTML(MODEL_CARD_HTML)
528
 
529
- with gr.Accordion("โš™๏ธ Settings", open=False, elem_id="settings-row"):
530
- with gr.Row():
531
- model_dd = gr.Dropdown(
532
- choices=list(MODELS.keys()), value=DEFAULT_MODEL,
533
- label="Model", elem_id="model-select", scale=2,
534
- info="26B-A4B: MoE (fast) | 31B: Dense (best quality)",
535
- )
536
- thinking_radio = gr.Radio(
537
- choices=["โšก Fast", "๐Ÿง  Thinking"],
538
- value="โšก Fast", label="Mode", scale=1,
539
- )
540
- with gr.Row():
541
- sys_prompt = gr.Textbox(
542
- value=PRESETS["general"], label="System Prompt", lines=2, scale=3,
543
- )
544
- with gr.Row():
545
- with gr.Column(scale=1):
546
- preset_dd = gr.Dropdown(
547
- choices=list(PRESETS.keys()), value="general",
548
- label="Preset", info="Quick system prompt selection",
549
- )
550
- with gr.Column(scale=1):
551
  max_tok_sl = gr.Slider(64, 8192, value=4096, step=64, label="Max Tokens")
552
- with gr.Column(scale=1):
553
  temp_sl = gr.Slider(0.0, 1.5, value=0.6, step=0.05, label="Temperature")
554
- with gr.Column(scale=1):
555
  topp_sl = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
 
557
- # Preset โ†’ System Prompt
558
- preset_dd.change(
559
- fn=lambda k: PRESETS.get(k, PRESETS["general"]),
560
- inputs=[preset_dd], outputs=[sys_prompt],
561
- )
562
-
563
- gr.HTML(EXAMPLES_HTML)
564
-
565
- image_input = gr.Textbox(value="", visible=False)
566
-
567
- chatbot = gr.Chatbot(
568
- elem_id="chatbot", scale=1,
569
- latex_delimiters=[
570
- {"left": "$$", "right": "$$", "display": True},
571
- {"left": "$", "right": "$", "display": False},
572
- {"left": "\\(", "right": "\\)", "display": False},
573
- {"left": "\\[", "right": "\\]", "display": True},
574
- ],
575
- )
576
 
577
- with gr.Row():
578
- chat_input = gr.Textbox(
579
- placeholder="Message Gemma 4โ€ฆ", lines=1, scale=7,
580
- elem_id="chat-input", show_label=False, autofocus=True,
581
- )
582
- send_btn = gr.Button("Send", variant="primary", scale=1, min_width=80)
583
-
584
- # โ”€โ”€ Chat logic โ”€โ”€
585
  def user_msg(message, history):
586
- if not message.strip():
587
- return "", history
588
- history = history + [{"role": "user", "content": message}]
589
- return "", history
590
-
591
- def bot_reply(history, thinking_mode, image_data, system_prompt,
592
- max_new_tokens, temperature, top_p, model_choice):
593
- if not history or history[-1]["role"] != "user":
594
- return history
595
 
 
 
596
  user_text = history[-1]["content"]
597
  past = history[:-1]
598
-
599
  history = history + [{"role": "assistant", "content": ""}]
600
-
601
- for chunk in generate_reply(
602
- user_text, past, thinking_mode, image_data,
603
- system_prompt, max_new_tokens, temperature, top_p, model_choice,
604
- ):
605
  history[-1]["content"] = chunk
606
  yield history
607
 
608
- common_inputs = [
609
- chatbot, thinking_radio, image_input, sys_prompt,
610
- max_tok_sl, temp_sl, topp_sl, model_dd,
611
- ]
612
-
613
- # Send on button click
614
- send_btn.click(user_msg, [chat_input, chatbot], [chat_input, chatbot], queue=False).then(
615
- bot_reply, common_inputs, chatbot,
616
- )
617
- # Send on Enter
618
- chat_input.submit(user_msg, [chat_input, chatbot], [chat_input, chatbot], queue=False).then(
619
- bot_reply, common_inputs, chatbot,
620
- )
621
-
622
- gr.HTML(MODEL_SWITCH_JS)
623
 
624
 
625
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
626
- # 7. LAUNCH โ€” must use demo.launch() for ZeroGPU @spaces.GPU registration
627
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
628
  if __name__ == "__main__":
629
  print(f"[BOOT] Gemma 4 Playground ยท Default: {DEFAULT_MODEL}", flush=True)
 
341
 
342
 
343
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
344
+
 
345
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
346
+ # 6. GRADIO UI โ€” Sidebar + Chat layout
347
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
348
 
349
  CSS = """
350
  @import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap');
351
  @import url('https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@400;500;700;800&display=swap');
352
+ :root{--v:#6d28d9;--v2:#7c3aed;--teal:#0d9488;--cream:#faf8f5;--ink:#1c1917;--ink3:#78716c;--ink4:#a8a29e;--line:#e4dfd8;--fog:#ede9e3}
353
+ .gradio-container{background:var(--cream)!important;font-family:'Cabinet Grotesk',sans-serif!important;max-width:100%!important;padding:0!important}
354
+ footer,.svelte-1rjryqp{display:none!important}
355
+
356
+ #orbs{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
357
+ .orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.4;animation:drift linear infinite}
358
+ .orb1{width:500px;height:500px;background:radial-gradient(circle,rgba(109,40,217,.3),transparent 70%);top:-100px;left:-100px;animation-duration:22s}
359
+ .orb2{width:380px;height:380px;background:radial-gradient(circle,rgba(16,185,129,.25),transparent 70%);top:5%;right:-60px;animation-duration:28s;animation-delay:-9s}
360
+ @keyframes drift{0%{transform:translate(0,0) scale(1)}33%{transform:translate(40px,-30px) scale(1.05)}66%{transform:translate(-20px,20px) scale(.97)}100%{transform:translate(0,0) scale(1)}}
361
+
362
+ #shell{display:grid!important;grid-template-columns:280px 1fr!important;height:100vh!important;overflow:hidden!important;position:relative;z-index:1;gap:0!important;padding:0!important}
363
+ #sidebar{background:rgba(255,255,255,.78)!important;backdrop-filter:blur(24px);border-right:1px solid var(--line)!important;overflow-y:auto!important;padding:0!important;border:none!important;border-radius:0!important;display:flex!important;flex-direction:column!important;gap:0!important}
364
+ #sidebar::-webkit-scrollbar{width:3px}
365
+ #sidebar::-webkit-scrollbar-thumb{background:var(--line);border-radius:10px}
366
+ #sidebar .gr-group{background:none!important;border:none!important;padding:0!important}
367
+
368
+ #logo-area{padding:18px 16px 14px;border-bottom:1px solid var(--line)}
369
+ .logo-row{display:flex;align-items:center;gap:10px}
370
+ .logo-icon{width:42px;height:42px;border-radius:13px;background:linear-gradient(135deg,#6d28d9,#a78bfa,#10b981);display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 4px 14px rgba(109,40,217,.3);flex-shrink:0}
371
+ .logo-name{font-family:'Instrument Serif',serif;font-size:20px;color:var(--ink)}
372
+ .logo-name em{color:var(--v);font-style:italic}
373
+ .logo-sub{font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--ink4)}
374
+
375
+ .mcard{margin:6px 12px;padding:12px 14px;border-radius:12px;border:2px solid var(--line);background:rgba(255,255,255,.6);cursor:pointer;transition:all .25s}
376
+ .mcard:hover{border-color:rgba(109,40,217,.3);box-shadow:0 2px 12px rgba(109,40,217,.08)}
377
+ .mcard.active{border-color:var(--v)!important;background:linear-gradient(135deg,rgba(109,40,217,.06),rgba(16,185,129,.04))!important;box-shadow:0 4px 20px rgba(109,40,217,.12)}
378
+ .mc-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}
379
+ .mc-name{font-size:12px;font-weight:700;color:var(--ink)}
380
+ .mc-arch{font-size:8px;font-weight:700;padding:2px 7px;border-radius:8px;background:rgba(109,40,217,.08);color:var(--v);font-family:'Geist Mono',monospace}
381
+ .mc-stats{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:5px}
382
+ .mc-stat{font-size:8px;font-weight:600;padding:1px 6px;border-radius:5px;font-family:'Geist Mono',monospace}
383
+ .mc-hl{background:rgba(109,40,217,.08);color:var(--v)}
384
+ .mc-ok{background:rgba(22,163,74,.09);color:#16a34a}
385
+ .mc-desc{font-size:9px;color:var(--ink3);line-height:1.5}
386
+ .mc-check{display:none;font-size:14px}.mcard.active .mc-check{display:inline}
387
+
388
+ .sec-lbl{font-size:8px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:var(--ink4);padding:10px 14px 4px}
389
+ .preset-chips{display:flex;flex-wrap:wrap;gap:3px;padding:0 14px 6px}
390
+ .pchip{font-size:9px;font-weight:600;padding:3px 9px;border-radius:16px;background:var(--fog);border:1px solid var(--line);color:var(--ink3);cursor:pointer;transition:all .2s}
391
+ .pchip:hover{background:rgba(109,40,217,.08);border-color:rgba(109,40,217,.25);color:var(--v)}
392
+
393
+ #sidebar-settings{padding:4px 12px 12px;flex:1}
394
+ #sidebar-settings label span{font-size:11px!important;font-weight:600!important;color:var(--ink3)!important}
395
+ #sidebar-settings textarea{background:var(--fog)!important;border:1.5px solid var(--line)!important;border-radius:8px!important;font-size:11px!important;padding:6px 8px!important;min-height:50px!important}
396
+ #sidebar-settings textarea:focus{border-color:rgba(109,40,217,.35)!important;box-shadow:0 0 0 3px rgba(109,40,217,.08)!important}
397
+ #clear-btn{margin:8px 12px 12px!important;border-radius:8px!important;border:1.5px solid var(--line)!important;background:var(--fog)!important;color:var(--ink3)!important;font-size:11px!important}
398
+ #clear-btn:hover{border-color:rgba(225,29,72,.3)!important;color:#e11d48!important}
399
+
400
+ #main-col{display:flex!important;flex-direction:column!important;height:100vh!important;overflow:hidden!important;padding:0!important;background:rgba(250,248,245,.4)!important;border:none!important;border-radius:0!important}
401
+ #main-col .gr-group{background:none!important;border:none!important}
402
+ #chat-header{padding:12px 20px;border-bottom:1px solid var(--line);background:rgba(255,255,255,.8);backdrop-filter:blur(20px);display:flex;align-items:center;gap:10px}
403
+ .dot{width:7px;height:7px;border-radius:50%;background:linear-gradient(135deg,var(--v),#10b981);animation:pulse 2s ease-in-out infinite;flex-shrink:0}
404
+ @keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(109,40,217,.4)}50%{box-shadow:0 0 0 4px rgba(109,40,217,0)}}
405
+ .hdr-model-name{font-size:12px;font-weight:700;color:var(--v);font-family:'Geist Mono',monospace}
406
+ .hdr-model-meta{font-size:10px;color:var(--ink4);margin-left:auto;font-family:'Geist Mono',monospace}
407
+
408
+ #chatbot{flex:1!important;min-height:0!important;overflow-y:auto!important;border:none!important;border-radius:0!important;background:transparent!important}
409
+ #chatbot .bot{background:rgba(109,40,217,.03)!important;border-left:3px solid rgba(109,40,217,.15)!important;border-radius:12px!important}
410
+ #input-row{padding:12px 20px!important;border-top:1px solid var(--line)!important;background:rgba(255,255,255,.8)!important;backdrop-filter:blur(20px);flex-shrink:0!important}
411
+ #chat-input textarea{border-radius:14px!important;border:1.5px solid var(--line)!important;background:white!important;font-size:13px!important}
412
+ #chat-input textarea:focus{border-color:rgba(109,40,217,.35)!important;box-shadow:0 0 0 3px rgba(109,40,217,.08)!important}
413
+ #send-btn{background:linear-gradient(135deg,var(--v),var(--v2))!important;border:none!important;border-radius:12px!important;color:white!important;font-weight:700!important;min-width:48px!important;font-size:18px!important}
414
+ .ex-card{background:white;border:1.5px solid var(--line);border-radius:12px;padding:11px 13px;cursor:pointer;transition:all .22s}
415
+ .ex-card:hover{border-color:rgba(109,40,217,.3);box-shadow:0 2px 12px rgba(28,25,23,.06);transform:translateY(-2px)}
416
+ .ex-icon{font-size:16px;display:block;margin-bottom:3px}
417
+ .ex-title{font-size:11px;font-weight:700;color:var(--ink);margin-bottom:1px}
418
+ .ex-desc{font-size:9px;color:var(--ink4)}
419
+
420
+ @media(max-width:768px){#shell{grid-template-columns:1fr!important}#sidebar{display:none!important}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  """
422
 
423
+ SIDEBAR_LOGO = '<div id="logo-area"><div class="logo-row"><div class="logo-icon">๐Ÿ’Ž</div><div><div class="logo-name"><em>Gemma</em> 4</div><div class="logo-sub">Google DeepMind</div></div></div></div>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
+ MODEL_CARDS_HTML = """<div class="sec-lbl">Select Model</div>
426
+ <div class="mcard active" id="card-moe" onclick="window._selectModel('Gemma-4-26B-A4B-it')">
427
+ <div class="mc-top"><span class="mc-name">โšก Gemma 4 ยท 26B-A4B</span><span class="mc-arch">MoE</span><span class="mc-check">โœ“</span></div>
428
+ <div class="mc-stats"><span class="mc-stat mc-hl">GPQA 82.3%</span><span class="mc-stat mc-ok">AIME 88.3%</span><span class="mc-stat mc-ok">๐Ÿ‘๏ธ Vision</span></div>
429
+ <div class="mc-desc">3.8B active / 26B total ยท 128 experts ยท ์ถ”๋ก  ๋น ๋ฆ„</div>
 
 
 
 
 
 
 
 
 
430
  </div>
431
+ <div class="mcard" id="card-dense" onclick="window._selectModel('Gemma-4-31B-it')">
432
+ <div class="mc-top"><span class="mc-name">๐Ÿ† Gemma 4 ยท 31B</span><span class="mc-arch">Dense</span><span class="mc-check">โœ“</span></div>
433
+ <div class="mc-stats"><span class="mc-stat mc-hl">AIME 89.2%</span><span class="mc-stat mc-ok">GPQA 84.3%</span><span class="mc-stat mc-ok">๐Ÿ‘๏ธ Vision</span></div>
434
+ <div class="mc-desc">30.7B Dense ยท Codeforces 2150 ยท ์ตœ๊ณ  ํ’ˆ์งˆ</div>
435
+ </div>"""
436
+
437
+ PRESETS_HTML = '<div class="sec-lbl">Preset</div><div class="preset-chips"><span class="pchip" onclick="window._setPreset(\'general\')">General</span><span class="pchip" onclick="window._setPreset(\'code\')">Code</span><span class="pchip" onclick="window._setPreset(\'math\')">Math</span><span class="pchip" onclick="window._setPreset(\'creative\')">Creative</span><span class="pchip" onclick="window._setPreset(\'translate\')">Translate</span><span class="pchip" onclick="window._setPreset(\'research\')">Research</span></div>'
438
+
439
+ CHAT_HEADER_HTML = '<div id="chat-header"><div class="dot"></div><span class="hdr-model-name" id="hdrModelName">Gemma-4-26B-A4B-it</span><span class="hdr-model-meta" id="hdrModelMeta">MoE ยท 3.8B active ยท 256K ctx</span></div>'
440
+
441
+ EXAMPLES_HTML = """<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:8px;max-width:460px;margin:40px auto">
442
+ <div class="ex-card" onclick="window._sendEx('Explain how Gemma 4 MoE architecture works with 128 experts and hybrid attention.')"><span class="ex-icon">๐Ÿ’Ž</span><div class="ex-title">Architecture</div><div class="ex-desc">MoE + hybrid attention</div></div>
443
+ <div class="ex-card" onclick="window._sendEx('Write a Python async web scraper with retry logic, rate limiting, and type hints.')"><span class="ex-icon">๐Ÿ’ป</span><div class="ex-title">Code</div><div class="ex-desc">Production-quality Python</div></div>
444
+ <div class="ex-card" onclick="window._sendEx('ํ•œ๊ตญ์˜ K-pop์ด ์„ธ๊ณ„์ ์œผ๋กœ ์„ฑ๊ณตํ•œ ์ด์œ ๋ฅผ ๋ฌธํ™”์ , ๊ฒฝ์ œ์  ๊ด€์ ์—์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.')"><span class="ex-icon">๐ŸŒ</span><div class="ex-title">๋‹ค๊ตญ์–ด</div><div class="ex-desc">Korean, Japanese, Arabicโ€ฆ</div></div>
445
+ <div class="ex-card" onclick="window._sendEx('Solve: Find all real solutions to xยณ - 6xยฒ + 11x - 6 = 0')"><span class="ex-icon">๐Ÿงฎ</span><div class="ex-title">Math</div><div class="ex-desc">Step-by-step reasoning</div></div>
446
+ </div>"""
447
+
448
+ BRIDGE_JS = """<script>
449
+ window._selectModel=function(name){
450
+ document.querySelectorAll('.mcard').forEach(function(c){c.classList.remove('active')});
451
+ document.getElementById(name==='Gemma-4-26B-A4B-it'?'card-moe':'card-dense').classList.add('active');
452
+ var h=document.getElementById('hdrModelName');if(h)h.textContent=name;
453
+ var meta={'Gemma-4-26B-A4B-it':'MoE ยท 3.8B active ยท 256K ctx','Gemma-4-31B-it':'Dense ยท 30.7B ยท 256K ctx'};
454
+ var hm=document.getElementById('hdrModelMeta');if(hm)hm.textContent=meta[name]||'';
455
+ var dd=document.querySelector('#model-select input');
456
+ if(dd){var nativeSet=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nativeSet.call(dd,name);dd.dispatchEvent(new Event('input',{bubbles:true}));}
457
  };
458
+ window._sendEx=function(text){
459
+ var ta=document.querySelector('#chat-input textarea');
460
+ if(ta){var nativeSet=Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set;nativeSet.call(ta,text);ta.dispatchEvent(new Event('input',{bubbles:true}));ta.focus();}
461
+ };
462
+ window._setPreset=function(key){
463
+ var presets={general:'You are Gemma 4, a highly capable multimodal AI assistant by Google DeepMind. Think step by step for complex questions.',code:'You are an expert software engineer. Write clean, efficient, well-commented code.',math:'You are a world-class mathematician. Break problems step-by-step. Show full working.',creative:'You are a brilliant creative writer. Be imaginative, vivid, and engaging.',translate:'You are a professional translator fluent in 140+ languages.',research:'You are a rigorous research analyst. Provide structured, well-reasoned analysis.'};
464
+ var ta=document.querySelector('#sidebar-settings textarea');
465
+ if(ta){var nativeSet=Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set;nativeSet.call(ta,presets[key]||presets.general);ta.dispatchEvent(new Event('input',{bubbles:true}));}
466
+ };
467
+ </script>"""
 
 
 
 
 
 
 
 
 
468
 
 
469
 
470
+ with gr.Blocks(title="Gemma 4 Playground", fill_height=True) as demo:
471
+ gr.HTML('<div id="orbs"><div class="orb orb1"></div><div class="orb orb2"></div></div>')
472
 
473
+ with gr.Row(elem_id="shell"):
474
+ with gr.Column(scale=0, min_width=280, elem_id="sidebar"):
475
+ gr.HTML(SIDEBAR_LOGO)
476
+ gr.HTML(MODEL_CARDS_HTML)
477
+ model_dd = gr.Dropdown(choices=list(MODELS.keys()), value=DEFAULT_MODEL, elem_id="model-select", visible=False)
478
+ gr.HTML(PRESETS_HTML)
479
+ with gr.Group(elem_id="sidebar-settings"):
480
+ sys_prompt = gr.Textbox(value=PRESETS["general"], label="System Prompt", lines=2)
481
+ thinking_radio = gr.Radio(choices=["โšก Fast", "๐Ÿง  Thinking"], value="โšก Fast", label="Mode")
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  max_tok_sl = gr.Slider(64, 8192, value=4096, step=64, label="Max Tokens")
 
483
  temp_sl = gr.Slider(0.0, 1.5, value=0.6, step=0.05, label="Temperature")
 
484
  topp_sl = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
485
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear conversation", elem_id="clear-btn")
486
+
487
+ with gr.Column(scale=1, elem_id="main-col"):
488
+ gr.HTML(CHAT_HEADER_HTML)
489
+ chatbot = gr.Chatbot(
490
+ elem_id="chatbot", show_label=False,
491
+ placeholder=EXAMPLES_HTML,
492
+ latex_delimiters=[
493
+ {"left": "$$", "right": "$$", "display": True},
494
+ {"left": "$", "right": "$", "display": False},
495
+ {"left": "\\(", "right": "\\)", "display": False},
496
+ {"left": "\\[", "right": "\\]", "display": True},
497
+ ],
498
+ )
499
+ image_input = gr.Textbox(value="", visible=False)
500
+ with gr.Row(elem_id="input-row"):
501
+ chat_input = gr.Textbox(placeholder="Message Gemma 4โ€ฆ", lines=1, max_lines=5, scale=7, elem_id="chat-input", show_label=False, autofocus=True)
502
+ send_btn = gr.Button("โ†‘", variant="primary", scale=0, min_width=48, elem_id="send-btn")
503
 
504
+ gr.HTML(BRIDGE_JS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
 
 
 
 
 
 
 
 
 
506
  def user_msg(message, history):
507
+ if not message.strip(): return "", history
508
+ return "", history + [{"role": "user", "content": message}]
 
 
 
 
 
 
 
509
 
510
+ def bot_reply(history, thinking_mode, image_data, system_prompt, max_new_tokens, temperature, top_p, model_choice):
511
+ if not history or history[-1]["role"] != "user": return history
512
  user_text = history[-1]["content"]
513
  past = history[:-1]
 
514
  history = history + [{"role": "assistant", "content": ""}]
515
+ for chunk in generate_reply(user_text, past, thinking_mode, image_data, system_prompt, max_new_tokens, temperature, top_p, model_choice):
 
 
 
 
516
  history[-1]["content"] = chunk
517
  yield history
518
 
519
+ common_inputs = [chatbot, thinking_radio, image_input, sys_prompt, max_tok_sl, temp_sl, topp_sl, model_dd]
520
+ send_btn.click(user_msg, [chat_input, chatbot], [chat_input, chatbot], queue=False).then(bot_reply, common_inputs, chatbot)
521
+ chat_input.submit(user_msg, [chat_input, chatbot], [chat_input, chatbot], queue=False).then(bot_reply, common_inputs, chatbot)
522
+ clear_btn.click(lambda: [], None, chatbot, queue=False)
 
 
 
 
 
 
 
 
 
 
 
523
 
524
 
525
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
526
+ # 7. LAUNCH
527
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
528
  if __name__ == "__main__":
529
  print(f"[BOOT] Gemma 4 Playground ยท Default: {DEFAULT_MODEL}", flush=True)