SeaWolf-AI commited on
Commit
5cfe54f
Β·
verified Β·
1 Parent(s): 5c1be4f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -206
app.py CHANGED
@@ -6,7 +6,7 @@ import sys
6
  print(f"[BOOT] Python {sys.version}", flush=True)
7
 
8
  import base64, os, re, json, subprocess
9
- from typing import Generator, Optional
10
  from collections.abc import Iterator
11
  from pathlib import Path
12
  from threading import Thread
@@ -29,23 +29,12 @@ if not _installed:
29
  print("[BOOT] Installing transformers from PyPI...", flush=True)
30
  subprocess.check_call([sys.executable, "-m", "pip", "install", "transformers>=4.49"])
31
 
32
- import urllib3
33
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
34
-
35
  try:
36
  import gradio as gr
37
  print(f"[BOOT] gradio {gr.__version__}", flush=True)
38
  except ImportError as e:
39
  print(f"[BOOT] FATAL: {e}", flush=True); sys.exit(1)
40
 
41
- try:
42
- import httpx, uvicorn, requests
43
- from fastapi import FastAPI, Request
44
- from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
45
- print("[BOOT] All imports OK", flush=True)
46
- except ImportError as e:
47
- print(f"[BOOT] FATAL: {e}", flush=True); sys.exit(1)
48
-
49
  import torch
50
  import spaces
51
  from transformers import AutoModelForMultimodalLM, AutoProcessor, BatchFeature
@@ -118,9 +107,9 @@ def _load_model(model_name: str):
118
  import gc; gc.collect()
119
  print(f"[MODEL] Unloaded previous model", flush=True)
120
 
121
- _processor = AutoProcessor.from_pretrained(model_id, use_fast=False)
122
  _model = AutoModelForMultimodalLM.from_pretrained(
123
- model_id, device_map="auto", torch_dtype=torch.bfloat16,
124
  )
125
 
126
  # Build strip tokens list (keep thinking delimiters)
@@ -352,202 +341,55 @@ def generate_reply(
352
 
353
 
354
  # ══════════════════════════════════════════════════════════════════════════════
355
- # 6. GRADIO BLOCKS (hidden β€” serves API for frontend)
356
  # ══════════════════════════════════════════════════════════════════════════════
357
- with gr.Blocks(title="Gemma 4 Playground") as gradio_demo:
358
- thinking_toggle = gr.Radio(
359
- choices=["⚑ Fast Mode (direct answer)",
360
- "🧠 Thinking Mode (chain-of-thought reasoning)"],
361
- value="⚑ Fast Mode (direct answer)",
362
- visible=False,
363
- )
364
- image_input = gr.Textbox(value="", visible=False)
365
- system_prompt = gr.Textbox(value=PRESETS["general"], visible=False)
366
- max_new_tokens = gr.Slider(minimum=64, maximum=8192, value=4096, visible=False)
367
- temperature = gr.Slider(minimum=0.0, maximum=1.5, value=0.6, visible=False)
368
- top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.9, visible=False)
369
- model_selector = gr.Dropdown(
370
- choices=list(MODELS.keys()),
371
- value=DEFAULT_MODEL,
372
- visible=False,
373
- )
374
-
375
- gr.ChatInterface(
376
- fn=generate_reply,
377
- api_name="chat",
378
- additional_inputs=[
379
- thinking_toggle, image_input,
380
- system_prompt, max_new_tokens, temperature, top_p,
381
- model_selector,
382
  ],
383
- )
384
-
385
-
386
- # ══════════════════════════════════════════════════════════════════════════════
387
- # 7. FASTAPI β€” index.html + HF OAuth + API endpoints
388
- # ══════════════════════════════════════════════════════════════════════════════
389
- import pathlib, secrets
390
-
391
- fapp = FastAPI()
392
- SESSIONS: dict[str, dict] = {}
393
- HTML = pathlib.Path(__file__).parent / "index.html"
394
-
395
- CLIENT_ID = os.getenv("OAUTH_CLIENT_ID", "")
396
- CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET", "")
397
- SPACE_HOST = os.getenv("SPACE_HOST", "localhost:7860")
398
- REDIRECT_URI = f"https://{SPACE_HOST}/login/callback"
399
-
400
- print(f"[OAuth] CLIENT_ID set: {bool(CLIENT_ID)}")
401
- print(f"[OAuth] SPACE_HOST: {SPACE_HOST}")
402
- HF_AUTH_URL = "https://huggingface.co/oauth/authorize"
403
- HF_TOKEN_URL = "https://huggingface.co/oauth/token"
404
- HF_USER_URL = "https://huggingface.co/oauth/userinfo"
405
- SCOPES = os.getenv("OAUTH_SCOPES", "openid profile")
406
-
407
- from urllib.parse import urlencode
408
-
409
- def _sid(req: Request) -> Optional[str]:
410
- return req.cookies.get("mc_session")
411
-
412
- def _user(req: Request) -> Optional[dict]:
413
- sid = _sid(req)
414
- return SESSIONS.get(sid) if sid else None
415
-
416
- @fapp.get("/")
417
- async def root(request: Request):
418
- html = HTML.read_text(encoding="utf-8") if HTML.exists() else "<h2>index.html missing</h2>"
419
- return HTMLResponse(html)
420
-
421
- @fapp.get("/oauth/user")
422
- async def oauth_user(request: Request):
423
- u = _user(request)
424
- return JSONResponse(u) if u else JSONResponse({"logged_in": False}, status_code=401)
425
-
426
- @fapp.get("/oauth/login")
427
- async def oauth_login(request: Request):
428
- if not CLIENT_ID:
429
- return RedirectResponse("/?oauth_error=not_configured")
430
- state = secrets.token_urlsafe(16)
431
- params = {"response_type":"code","client_id":CLIENT_ID,"redirect_uri":REDIRECT_URI,"scope":SCOPES,"state":state}
432
- return RedirectResponse(f"{HF_AUTH_URL}?{urlencode(params)}", status_code=302)
433
-
434
- @fapp.get("/login/callback")
435
- async def oauth_callback(code: str = "", error: str = "", state: str = ""):
436
- if error or not code:
437
- return RedirectResponse("/?auth_error=1")
438
- basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
439
- async with httpx.AsyncClient() as client:
440
- tok = await client.post(HF_TOKEN_URL, data={"grant_type":"authorization_code","code":code,"redirect_uri":REDIRECT_URI},
441
- headers={"Accept":"application/json","Authorization":f"Basic {basic}"})
442
- if tok.status_code != 200:
443
- return RedirectResponse("/?auth_error=1")
444
- access_token = tok.json().get("access_token", "")
445
- if not access_token:
446
- return RedirectResponse("/?auth_error=1")
447
- uinfo = await client.get(HF_USER_URL, headers={"Authorization":f"Bearer {access_token}"})
448
- if uinfo.status_code != 200:
449
- return RedirectResponse("/?auth_error=1")
450
- user = uinfo.json()
451
-
452
- sid = secrets.token_urlsafe(32)
453
- SESSIONS[sid] = {
454
- "logged_in": True,
455
- "username": user.get("preferred_username", user.get("name", "User")),
456
- "name": user.get("name", ""),
457
- "avatar": user.get("picture", ""),
458
- "profile": f"https://huggingface.co/{user.get('preferred_username', '')}",
459
- }
460
- resp = RedirectResponse("/")
461
- resp.set_cookie("mc_session", sid, httponly=True, samesite="lax", secure=True, max_age=60*60*24*7)
462
- return resp
463
-
464
- @fapp.get("/oauth/logout")
465
- async def oauth_logout(request: Request):
466
- sid = _sid(request)
467
- if sid and sid in SESSIONS: del SESSIONS[sid]
468
- resp = RedirectResponse("/")
469
- resp.delete_cookie("mc_session")
470
- return resp
471
-
472
- # ── Model Info API (for frontend model selector) ────────────────────────
473
- @fapp.get("/api/models")
474
- async def api_models():
475
- return JSONResponse({
476
- "models": {k: v for k, v in MODELS.items()},
477
- "current": _loaded_model_name,
478
- "default": DEFAULT_MODEL,
479
- })
480
-
481
- @fapp.get("/health")
482
- async def health():
483
- return {
484
- "status": "ok",
485
- "model_loaded": _loaded_model_name,
486
- "model_arch": MODELS.get(_loaded_model_name, {}).get("arch", "unknown"),
487
- "gpu": torch.cuda.is_available(),
488
- }
489
-
490
- # ── Web Search API (Brave) ──────────────────────────────────────────────
491
- BRAVE_API_KEY = os.getenv("BRAVE_API_KEY", "")
492
-
493
- @fapp.post("/api/search")
494
- async def api_search(request: Request):
495
- body = await request.json()
496
- query = body.get("query", "").strip()
497
- if not query:
498
- return JSONResponse({"error": "empty query"}, status_code=400)
499
- key = BRAVE_API_KEY
500
- if not key:
501
- return JSONResponse({"error": "BRAVE_API_KEY not set"}, status_code=500)
502
- try:
503
- r = requests.get(
504
- "https://api.search.brave.com/res/v1/web/search",
505
- headers={"X-Subscription-Token": key, "Accept": "application/json"},
506
- params={"q": query, "count": 5}, timeout=10,
507
- )
508
- r.raise_for_status()
509
- results = r.json().get("web", {}).get("results", [])
510
- items = []
511
- for item in results[:5]:
512
- items.append({
513
- "title": item.get("title", ""),
514
- "desc": item.get("description", ""),
515
- "url": item.get("url", ""),
516
- })
517
- return JSONResponse({"results": items})
518
- except Exception as e:
519
- return JSONResponse({"error": str(e)}, status_code=500)
520
-
521
- # ── PDF Text Extraction ─────────────────────────────────────────────────
522
- @fapp.post("/api/extract-pdf")
523
- async def api_extract_pdf(request: Request):
524
- try:
525
- body = await request.json()
526
- b64 = body.get("data", "")
527
- if "," in b64:
528
- b64 = b64.split(",", 1)[1]
529
- import io
530
- pdf_bytes = base64.b64decode(b64)
531
- text = ""
532
- try:
533
- import fitz # PyMuPDF
534
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
535
- for page in doc:
536
- text += page.get_text() + "\n"
537
- except ImportError:
538
- content = pdf_bytes.decode("utf-8", errors="ignore")
539
- text = re.sub(r'[^\x20-\x7E\n\r\uAC00-\uD7A3\u3040-\u309F\u30A0-\u30FF]', '', content)
540
- text = text.strip()[:8000]
541
- return JSONResponse({"text": text, "chars": len(text)})
542
- except Exception as e:
543
- return JSONResponse({"error": str(e)}, status_code=500)
544
-
545
 
546
  # ══════════════════════════════════════════════════════════════════════════════
547
- # 8. MOUNT & LAUNCH
548
  # ══════════════════════════════════════════════════════════════════════════════
549
- app = gr.mount_gradio_app(fapp, gradio_demo, path="/gradio")
550
-
551
  if __name__ == "__main__":
552
  print(f"[BOOT] Gemma 4 Playground Β· Default: {DEFAULT_MODEL}", flush=True)
553
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
6
  print(f"[BOOT] Python {sys.version}", flush=True)
7
 
8
  import base64, os, re, json, subprocess
9
+ from typing import Generator
10
  from collections.abc import Iterator
11
  from pathlib import Path
12
  from threading import Thread
 
29
  print("[BOOT] Installing transformers from PyPI...", flush=True)
30
  subprocess.check_call([sys.executable, "-m", "pip", "install", "transformers>=4.49"])
31
 
 
 
 
32
  try:
33
  import gradio as gr
34
  print(f"[BOOT] gradio {gr.__version__}", flush=True)
35
  except ImportError as e:
36
  print(f"[BOOT] FATAL: {e}", flush=True); sys.exit(1)
37
 
 
 
 
 
 
 
 
 
38
  import torch
39
  import spaces
40
  from transformers import AutoModelForMultimodalLM, AutoProcessor, BatchFeature
 
107
  import gc; gc.collect()
108
  print(f"[MODEL] Unloaded previous model", flush=True)
109
 
110
+ _processor = AutoProcessor.from_pretrained(model_id, backend="pil")
111
  _model = AutoModelForMultimodalLM.from_pretrained(
112
+ model_id, device_map="auto", dtype=torch.bfloat16,
113
  )
114
 
115
  # Build strip tokens list (keep thinking delimiters)
 
341
 
342
 
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
392
  # ══════════════════════════════════════════════════════════════════════════════
 
 
393
  if __name__ == "__main__":
394
  print(f"[BOOT] Gemma 4 Playground Β· Default: {DEFAULT_MODEL}", flush=True)
395
+ demo.launch(server_name="0.0.0.0", server_port=7860)