"""
Nyra Chat — Hugging Face Space (Gradio + ZeroGPU).
Local: mock streaming only (no GGUF download).
Spaces: loads HauhauCS Qwen3.6-35B-A3B Aggressive GGUF under @spaces.GPU.
"""
from __future__ import annotations
import inspect
import os
from pathlib import Path
import gradio as gr
from model_engine import (
DEFAULT_MAX_TOKENS,
DEFAULT_TEMPERATURE,
is_spaces,
runtime_mode,
start_background_warmup,
status_label,
stream_chat,
)
from theme import build_theme
# On Spaces: download GGUF + ensure llama-cpp on CPU before the first GPU call
# so ZeroGPU duration is not wasted on pip/download.
if is_spaces():
start_background_warmup()
ROOT = Path(__file__).resolve().parent
CSS_PATH = ROOT / "static" / "app.css"
JS_PATH = ROOT / "static" / "app.js"
try:
import spaces
GPU = spaces.GPU
except Exception: # noqa: BLE001
def GPU(*_args, **_kwargs): # type: ignore[misc]
def deco(fn):
return fn
if _args and callable(_args[0]) and not _kwargs:
return _args[0]
return deco
UI = {
"pt": {
"hello": "Oi, eu sou a Nyra.",
"subtitle": "Chat imersivo · modelo no ZeroGPU do Space.",
"tagline": "Pense livre. Responda com clareza.",
"placeholder": "Pergunte qualquer coisa…",
"send": "Enviar",
"new_chat": "Novo chat",
"clear": "Limpar",
"settings": "Ajustes",
"temperature": "Temperatura",
"max_tokens": "Máx. tokens",
"thinking": "Thinking (Qwen)",
"lang_btn": "EN",
"chips": [
("⚡ Quantum em 5 bullets", "Explique computação quântica em 5 bullets claros."),
("💻 Snippet Python", "Escreva um snippet Python elegante para retry com backoff exponencial."),
("🌍 Viagem barata", "Me dê ideias de viagem barata na América do Sul por 7 dias."),
("💡 Ideia de app", "Brainstorme um app original com IA para estudantes."),
],
"banner_mock": "Demo local — o modelo **não** baixa aqui; só no Space.",
"banner_spaces": "ZeroGPU ativo — primeira resposta pode demorar (download/load do GGUF).",
},
"en": {
"hello": "Hi, I'm Nyra.",
"subtitle": "Immersive chat · model on Space ZeroGPU.",
"tagline": "Think freely. Answer clearly.",
"placeholder": "Ask anything…",
"send": "Send",
"new_chat": "New chat",
"clear": "Clear",
"settings": "Settings",
"temperature": "Temperature",
"max_tokens": "Max tokens",
"thinking": "Thinking (Qwen)",
"lang_btn": "PT",
"chips": [
("⚡ Quantum in 5 bullets", "Explain quantum computing in 5 clear bullets."),
("💻 Python snippet", "Write an elegant Python snippet for retry with exponential backoff."),
("🌍 Cheap trip", "Give me cheap 7-day trip ideas in South America."),
("💡 App idea", "Brainstorm an original AI app for students."),
],
"banner_mock": "Local demo — model is **not** downloaded here; only on the Space.",
"banner_spaces": "ZeroGPU on — first reply may take a while (GGUF download/load).",
},
}
def load_css() -> str:
return CSS_PATH.read_text(encoding="utf-8") if CSS_PATH.exists() else ""
def load_js() -> str:
return JS_PATH.read_text(encoding="utf-8") if JS_PATH.exists() else ""
# Critical CSS inlined so HF iframe cannot ignore/stale-cache file CSS
CRITICAL_CSS = """
html,body,.gradio-container,main.app,.wrap,.contain{height:auto!important;min-height:0!important;flex-grow:0!important}
footer,.built-with,button.show-api,button.settings{display:none!important}
#nyra-input-row{height:56px!important;max-height:56px!important;min-height:56px!important;overflow:hidden!important;flex:0 0 56px!important}
#nyra-input textarea,textarea[data-testid=textbox]{height:40px!important;min-height:40px!important;max-height:40px!important;resize:none!important;overflow:hidden!important;line-height:40px!important}
#nyra-chatbot{height:380px!important;max-height:380px!important;min-height:380px!important}
"""
HEAD = f"""
"""
def hero_html(lang: str) -> str:
t = UI[lang]
return f"""