SeaWolf-AI commited on
Commit
6d6166b
ยท
verified ยท
1 Parent(s): 59d32ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +272 -37
app.py CHANGED
@@ -343,49 +343,284 @@ def generate_reply(
343
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
344
  # 6. GRADIO CHAT INTERFACE โ€” ZeroGPU compatible (must use demo.launch())
345
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
346
- demo = gr.ChatInterface(
347
- fn=generate_reply,
348
- chatbot=gr.Chatbot(
349
- scale=1,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  latex_delimiters=[
351
  {"left": "$$", "right": "$$", "display": True},
352
  {"left": "$", "right": "$", "display": False},
353
  {"left": "\\(", "right": "\\)", "display": False},
354
  {"left": "\\[", "right": "\\]", "display": True},
355
  ],
356
- ),
357
- textbox=gr.Textbox(placeholder="Message Gemma 4โ€ฆ", lines=1, scale=7),
358
- additional_inputs=[
359
- gr.Radio(
360
- choices=["โšก Fast Mode (direct answer)",
361
- "๐Ÿง  Thinking Mode (chain-of-thought reasoning)"],
362
- value="โšก Fast Mode (direct answer)",
363
- label="Mode",
364
- ),
365
- gr.Textbox(value="", label="Image (base64 or URL)", visible=False),
366
- gr.Textbox(value=PRESETS["general"], label="System Prompt", lines=2),
367
- gr.Slider(minimum=64, maximum=8192, value=4096, step=64, label="Max Tokens"),
368
- gr.Slider(minimum=0.0, maximum=1.5, value=0.6, step=0.05, label="Temperature"),
369
- gr.Slider(minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-P"),
370
- gr.Dropdown(
371
- choices=list(MODELS.keys()),
372
- value=DEFAULT_MODEL,
373
- label="Model",
374
- info="26B-A4B: MoE 3.8B active (fast) | 31B: Dense (best quality)",
375
- ),
376
- ],
377
- additional_inputs_accordion=gr.Accordion("โš™๏ธ Settings", open=False),
378
- title="๐Ÿ’Ž Gemma 4 Playground",
379
- description="Google DeepMind Gemma 4 โ€” Dense 31B or MoE 26B-A4B ยท Vision ยท Thinking ยท Apache 2.0",
380
- examples=[
381
- ["Explain how Gemma 4 achieves frontier-level performance with Mixture-of-Experts architecture."],
382
- ["Write a Python async web scraper with retry logic and rate limiting."],
383
- ["ํ•œ๊ตญ์˜ K-pop์ด ์„ธ๊ณ„์ ์œผ๋กœ ์„ฑ๊ณตํ•œ ์ด์œ ๋ฅผ ๋ฌธํ™”์ , ๊ฒฝ์ œ์  ๊ด€์ ์—์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”."],
384
- ["Solve: What is the sum of all prime numbers less than 100?"],
385
- ],
386
- run_examples_on_click=False,
387
- cache_examples=False,
388
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
391
  # 7. LAUNCH โ€” must use demo.launch() for ZeroGPU @spaces.GPU registration
 
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(css=CSS, 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, type="messages",
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