SeaWolf-AI commited on
Commit
d0ead96
·
verified ·
1 Parent(s): 7887818

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -40
app.py CHANGED
@@ -343,28 +343,36 @@ def generate_reply(
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}
@@ -372,6 +380,7 @@ footer,.svelte-1rjryqp{display:none!important}
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)}
@@ -384,40 +393,41 @@ footer,.svelte-1rjryqp{display:none!important}
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>'
@@ -436,14 +446,7 @@ MODEL_CARDS_HTML = """<div class="sec-lbl">Select Model</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){
@@ -453,16 +456,16 @@ window._selectModel=function(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
 
@@ -470,7 +473,9 @@ window._setPreset=function(key){
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)
@@ -484,11 +489,14 @@ with gr.Blocks(title="Gemma 4 Playground", fill_height=True) as demo:
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},
@@ -496,19 +504,27 @@ with gr.Blocks(title="Gemma 4 Playground", fill_height=True) as demo:
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": ""}]
 
343
  # ══════════════════════════════════════════════════════════════════════════════
344
 
345
  # ══════════════════════════════════════════════════════════════════════════════
346
+ # 6. GRADIO UI
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
+
354
+ /* === FIX: lock viewport, kill all outer scrolling === */
355
+ html,body,.gradio-container,.main,.wrap,.contain{overflow:hidden!important;height:100vh!important;max-height:100vh!important}
356
  .gradio-container{background:var(--cream)!important;font-family:'Cabinet Grotesk',sans-serif!important;max-width:100%!important;padding:0!important}
357
  footer,.svelte-1rjryqp{display:none!important}
358
 
359
+ /* Background orbs */
360
  #orbs{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
361
+ .orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.4;animation:drift 22s linear infinite}
362
+ .orb1{width:450px;height:450px;background:radial-gradient(circle,rgba(109,40,217,.3),transparent 70%);top:-80px;left:-80px}
363
+ .orb2{width:350px;height:350px;background:radial-gradient(circle,rgba(16,185,129,.25),transparent 70%);top:5%;right:-50px;animation-delay:-9s}
364
  @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)}}
365
 
366
+ /* === Shell: sidebar + main === */
367
+ #shell-row{height:100vh!important;overflow:hidden!important;flex-wrap:nowrap!important;gap:0!important;padding:0!important;position:relative;z-index:1}
368
+
369
+ /* === Sidebar === */
370
+ #sidebar{background:rgba(255,255,255,.78)!important;backdrop-filter:blur(24px);border-right:1px solid var(--line)!important;overflow-y:auto!important;overflow-x:hidden!important;padding:0!important;border-radius:0!important;max-width:280px!important;min-width:280px!important;height:100vh!important}
371
  #sidebar::-webkit-scrollbar{width:3px}
372
  #sidebar::-webkit-scrollbar-thumb{background:var(--line);border-radius:10px}
373
+ #sidebar .gr-group{background:none!important;border:none!important;padding:0!important;box-shadow:none!important}
374
 
375
+ /* Logo */
376
  #logo-area{padding:18px 16px 14px;border-bottom:1px solid var(--line)}
377
  .logo-row{display:flex;align-items:center;gap:10px}
378
  .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}
 
380
  .logo-name em{color:var(--v);font-style:italic}
381
  .logo-sub{font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--ink4)}
382
 
383
+ /* Model cards */
384
  .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}
385
  .mcard:hover{border-color:rgba(109,40,217,.3);box-shadow:0 2px 12px rgba(109,40,217,.08)}
386
  .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)}
 
393
  .mc-ok{background:rgba(22,163,74,.09);color:#16a34a}
394
  .mc-desc{font-size:9px;color:var(--ink3);line-height:1.5}
395
  .mc-check{display:none;font-size:14px}.mcard.active .mc-check{display:inline}
 
396
  .sec-lbl{font-size:8px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:var(--ink4);padding:10px 14px 4px}
397
  .preset-chips{display:flex;flex-wrap:wrap;gap:3px;padding:0 14px 6px}
398
  .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}
399
  .pchip:hover{background:rgba(109,40,217,.08);border-color:rgba(109,40,217,.25);color:var(--v)}
400
+ #sidebar-settings{padding:4px 12px 12px}
 
401
  #sidebar-settings label span{font-size:11px!important;font-weight:600!important;color:var(--ink3)!important}
402
+ #sidebar-settings textarea{background:var(--fog)!important;border:1.5px solid var(--line)!important;border-radius:8px!important;font-size:11px!important;min-height:50px!important}
403
  #sidebar-settings textarea:focus{border-color:rgba(109,40,217,.35)!important;box-shadow:0 0 0 3px rgba(109,40,217,.08)!important}
404
  #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}
405
  #clear-btn:hover{border-color:rgba(225,29,72,.3)!important;color:#e11d48!important}
406
 
407
+ /* === Main column === */
408
+ #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-radius:0!important;border:none!important}
409
+ #chat-header{padding:12px 20px;border-bottom:1px solid var(--line);background:rgba(255,255,255,.8);backdrop-filter:blur(20px);flex-shrink:0}
410
+ .hdr-row{display:flex;align-items:center;gap:10px}
411
  .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}
412
  @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)}}
413
  .hdr-model-name{font-size:12px;font-weight:700;color:var(--v);font-family:'Geist Mono',monospace}
414
  .hdr-model-meta{font-size:10px;color:var(--ink4);margin-left:auto;font-family:'Geist Mono',monospace}
415
 
416
+ /* === CHATBOT: fill remaining space, internal scroll === */
417
+ #chatbot{flex:1 1 0!important;min-height:0!important;max-height:none!important;height:auto!important;overflow-y:auto!important;border:none!important;border-radius:0!important;background:transparent!important}
418
+ #chatbot>.wrapper{height:100%!important;max-height:none!important}
419
  #chatbot .bot{background:rgba(109,40,217,.03)!important;border-left:3px solid rgba(109,40,217,.15)!important;border-radius:12px!important}
420
+
421
+ /* === Input row: fixed at bottom === */
422
+ #input-row{padding:10px 16px!important;border-top:1px solid var(--line)!important;background:rgba(255,255,255,.85)!important;backdrop-filter:blur(20px);flex-shrink:0!important;flex-grow:0!important}
423
  #chat-input textarea{border-radius:14px!important;border:1.5px solid var(--line)!important;background:white!important;font-size:13px!important}
424
  #chat-input textarea:focus{border-color:rgba(109,40,217,.35)!important;box-shadow:0 0 0 3px rgba(109,40,217,.08)!important}
425
  #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}
 
 
 
 
 
426
 
427
+ @media(max-width:768px){
428
+ #shell-row{flex-direction:column!important}
429
+ #sidebar{display:none!important}
430
+ }
431
  """
432
 
433
  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>'
 
446
 
447
  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>'
448
 
449
+ CHAT_HEADER_HTML = '<div id="chat-header"><div class="hdr-row"><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></div>'
 
 
 
 
 
 
 
450
 
451
  BRIDGE_JS = """<script>
452
  window._selectModel=function(name){
 
456
  var meta={'Gemma-4-26B-A4B-it':'MoE · 3.8B active · 256K ctx','Gemma-4-31B-it':'Dense · 30.7B · 256K ctx'};
457
  var hm=document.getElementById('hdrModelMeta');if(hm)hm.textContent=meta[name]||'';
458
  var dd=document.querySelector('#model-select input');
459
+ if(dd){var ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;ns.call(dd,name);dd.dispatchEvent(new Event('input',{bubbles:true}));}
460
  };
461
  window._sendEx=function(text){
462
  var ta=document.querySelector('#chat-input textarea');
463
+ if(ta){var ns=Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set;ns.call(ta,text);ta.dispatchEvent(new Event('input',{bubbles:true}));ta.focus();}
464
  };
465
  window._setPreset=function(key){
466
+ var p={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.',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.'};
467
  var ta=document.querySelector('#sidebar-settings textarea');
468
+ if(ta){var ns=Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set;ns.call(ta,p[key]||p.general);ta.dispatchEvent(new Event('input',{bubbles:true}));}
469
  };
470
  </script>"""
471
 
 
473
  with gr.Blocks(title="Gemma 4 Playground", fill_height=True) as demo:
474
  gr.HTML('<div id="orbs"><div class="orb orb1"></div><div class="orb orb2"></div></div>')
475
 
476
+ with gr.Row(elem_id="shell-row", equal_height=True):
477
+
478
+ # ═══ SIDEBAR ═══
479
  with gr.Column(scale=0, min_width=280, elem_id="sidebar"):
480
  gr.HTML(SIDEBAR_LOGO)
481
  gr.HTML(MODEL_CARDS_HTML)
 
489
  topp_sl = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
490
  clear_btn = gr.Button("🗑️ Clear conversation", elem_id="clear-btn")
491
 
492
+ # ═══ MAIN ═══
493
  with gr.Column(scale=1, elem_id="main-col"):
494
  gr.HTML(CHAT_HEADER_HTML)
495
+
496
  chatbot = gr.Chatbot(
497
+ elem_id="chatbot",
498
+ show_label=False,
499
+ height="calc(100vh - 140px)",
500
  latex_delimiters=[
501
  {"left": "$$", "right": "$$", "display": True},
502
  {"left": "$", "right": "$", "display": False},
 
504
  {"left": "\\[", "right": "\\]", "display": True},
505
  ],
506
  )
507
+
508
  image_input = gr.Textbox(value="", visible=False)
509
+
510
  with gr.Row(elem_id="input-row"):
511
+ chat_input = gr.Textbox(
512
+ placeholder="Message Gemma 4…", lines=1, max_lines=4,
513
+ scale=7, elem_id="chat-input", show_label=False, autofocus=True,
514
+ )
515
  send_btn = gr.Button("↑", variant="primary", scale=0, min_width=48, elem_id="send-btn")
516
 
517
  gr.HTML(BRIDGE_JS)
518
 
519
+ # ── Chat logic ──
520
  def user_msg(message, history):
521
+ if not message.strip():
522
+ return "", history
523
  return "", history + [{"role": "user", "content": message}]
524
 
525
  def bot_reply(history, thinking_mode, image_data, system_prompt, max_new_tokens, temperature, top_p, model_choice):
526
+ if not history or history[-1]["role"] != "user":
527
+ return history
528
  user_text = history[-1]["content"]
529
  past = history[:-1]
530
  history = history + [{"role": "assistant", "content": ""}]