from __future__ import annotations import html import json import logging import os import sys import time from functools import lru_cache from pathlib import Path from typing import Any import gradio as gr from dotenv import load_dotenv from fastapi.responses import HTMLResponse from pydantic import BaseModel, Field try: import spaces except ImportError: class _SpacesFallback: @staticmethod def GPU(*args: Any, **kwargs: Any): def decorator(fn): return fn return decorator spaces = _SpacesFallback() try: from .backend.smolnalysis_model_wrapper import SmolnalysisMoE except ImportError: from backend.smolnalysis_model_wrapper import SmolnalysisMoE REPO_ROOT = Path(__file__).resolve().parents[1] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) from openui_adapter_demo import clean_component_output, render_component_preview # type: ignore # noqa: E402 load_dotenv() logging.basicConfig( level=os.getenv("SMOLNALYSIS_LOG_LEVEL", "INFO").upper(), format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger(__name__) DEFAULT_MAX_NEW_TOKENS = int(os.getenv("SMOLNALYSIS_MINICPM_MAX_NEW_TOKENS", "512")) DEFAULT_TEMPERATURE = float(os.getenv("SMOLNALYSIS_MINICPM_TEMPERATURE", "0.7")) DEFAULT_TOP_P = float(os.getenv("SMOLNALYSIS_MINICPM_TOP_P", "0.95")) DEFAULT_TOP_K = int(os.getenv("SMOLNALYSIS_MINICPM_TOP_K", "64")) LOAD_IN_4BIT = os.getenv("SMOLNALYSIS_MINICPM_LOAD_IN_4BIT", "true").casefold() not in {"0", "false", "no", "off"} EXAMPLE_PROMPTS = [ ["Zeige mir Heizbedarf pro Monat für 2023 in Bürgerbüro Pasing."], ["Prüfe den Grenzwert für Stromverbrauch in Bogenhausen."], ["Say 'Hello World!' in Python"], ] OPENUI_PREVIEW_CSS = """ .preview { border: 1px solid #d7dde8; border-radius: 8px; padding: 14px; background: #fff; color: #111827; } .preview, .preview * { color: #111827; } .preview h2 { margin: 0 0 10px; font-size: 18px; color: #0f172a; } .preview p { color: #334155; } .insight, .chart-preview, .table-preview { background: #fff; color: #111827; } .insight { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; margin-bottom: 10px; } .insight p, .chart-preview p { color: #334155; margin: 0 0 10px; } .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; } .stat { border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px; display: grid; gap: 3px; background: #f8fafc; } .stat span, .stat small { color: #475569; font-size: 12px; } .stat strong { color: #0f172a; font-size: 20px; } .bar-row { display: grid; grid-template-columns: minmax(72px, 150px) 1fr minmax(72px, 120px); gap: 8px; align-items: center; margin: 8px 0; } .bar-row span, .bar-row b { color: #1f2937; font-size: 12px; overflow-wrap: anywhere; } .bar-row div, .progress { height: 14px; background: #e5e7eb; border-radius: 999px; overflow: hidden; } .bar-row i, .progress i { display: block; height: 100%; background: #2563eb; border-radius: 999px; } .alert.warning { border-color: #f59e0b; background: #fffbeb; } .alert.danger { border-color: #ef4444; background: #fef2f2; } .alert.success { border-color: #10b981; background: #ecfdf5; } .preview table { width: 100%; border-collapse: collapse; } .preview th { color: #111827; border-bottom: 1px solid #cbd5e1; padding: 7px 6px; font-size: 13px; text-align: left; } .preview td { color: #1f2937; border-top: 1px solid #e5e7eb; padding: 7px 6px; font-size: 13px; } .preview td:nth-child(2), .preview td:nth-child(3) { text-align: right; } .histogram { display: flex; align-items: end; gap: 4px; height: 160px; padding-top: 8px; } .histogram-bar { flex: 1; min-width: 8px; background: #2563eb; border-radius: 4px 4px 0 0; } .error { border-color: #ef4444; background: #fef2f2; } .error pre { white-space: pre-wrap; } """ def _css() -> str: return """ """ @lru_cache(maxsize=1) def _model() -> SmolnalysisMoE: logger.info("Loading SmolnalysisMoE wrapper") model = SmolnalysisMoE(load_in_4bit=LOAD_IN_4BIT) logger.info("SmolnalysisMoE wrapper loaded") return model def _generation_kwargs() -> dict[str, Any]: return { "max_new_tokens": DEFAULT_MAX_NEW_TOKENS, "temperature": DEFAULT_TEMPERATURE, "top_p": DEFAULT_TOP_P, "top_k": DEFAULT_TOP_K, } def _run_model_chat(messages: list[dict[str, str]]) -> tuple[str, dict[str, Any]]: model = _model() started = time.perf_counter() result = model.generate_chat(messages, **_generation_kwargs()) content = str(result["content"]) if any(stage.get("adapter") == "openui_translator" for stage in result.get("stages", [])): content = clean_component_output(content) trace = { "stages": result["stages"], "duration_ms": round((time.perf_counter() - started) * 1000, 1), } if result.get("tool_result"): trace["tool_result"] = result["tool_result"] return content, trace def _is_openui_lang(content: str) -> bool: return any(line.strip().startswith("root =") for line in content.splitlines()) def _openui_mount_html(openui_lang: str) -> str: rendered = render_component_preview(openui_lang) source = html.escape(openui_lang) return ( f'
{rendered}
' '
' "OpenUI Lang" f"
{source}
" "
" ) def _assistant_message(content: str, trace: dict[str, Any]) -> str: if _is_openui_lang(content): rendered = _openui_mount_html(content) else: rendered = html.escape(content).replace("\n", "
") meta = html.escape(json.dumps(trace, ensure_ascii=False, default=str)) return f"{rendered}
{meta}
" @spaces.GPU(duration=120) def submit_message( user_message: str, chat_history: list[dict[str, Any]] | None, model_history: list[dict[str, str]] | None, ) -> tuple[str, list[dict[str, Any]], list[dict[str, str]]]: user_message = str(user_message or "").strip() if not user_message: return "", chat_history or [], model_history or [] rendered_history = [*(chat_history or []), {"role": "user", "content": user_message}] messages = [*(model_history or []), {"role": "user", "content": user_message}] try: content, trace = _run_model_chat(messages) rendered_history.append({"role": "assistant", "content": _assistant_message(content, trace)}) messages.append({"role": "assistant", "content": content}) except Exception as exc: logger.exception("SmolnalysisMoE chat failed") detail = f"{type(exc).__name__}: {str(exc).strip() or type(exc).__name__}" rendered_history.append({"role": "assistant", "content": html.escape(detail)}) messages.append({"role": "assistant", "content": detail}) return "", rendered_history, messages def clear_chat() -> tuple[list[dict[str, Any]], list[dict[str, str]], str]: return [], [], "" class ChatRequest(BaseModel): message: str history: list[dict[str, str]] = Field(default_factory=list) def _server_page() -> str: examples = [ {"label": "Monthly chart", "prompt": EXAMPLE_PROMPTS[0][0]}, {"label": "Energy", "prompt": EXAMPLE_PROMPTS[1][0]}, {"label": "District values", "prompt": EXAMPLE_PROMPTS[2][0]}, #{"label": "Threshold", "prompt": EXAMPLE_PROMPTS[3][0]}, #{"label": "Table", "prompt": EXAMPLE_PROMPTS[4][0]}, #{"label": "General", "prompt": EXAMPLE_PROMPTS[5][0]}, ] return ( "" '' "" '' '' "smolnalysis" + _css() + """ """ "" "" '
' '
' '
' '

smolnalysis

' '

Ask for open data, get a rendered interface back. (Check the logs if the response takes too long, maybe there is a model download.)

' "
" '
Router + adapters online
' "
" '
' '
' '
' '
Start with an example promptResults that produce OpenUI-Lang render directly in the chat.
' "
" '
' '' '' '' "
" "
" '" "
" "
" "" "" ) app = gr.Server(title="smolnalysis") @app.get("/", response_class=HTMLResponse) def index() -> HTMLResponse: return HTMLResponse(_server_page()) @app.post("/api/chat") @spaces.GPU(duration=120) def chat_api(request: ChatRequest) -> dict[str, Any]: user_message = str(request.message or "").strip() if not user_message: return {"html": "", "history": request.history, "trace": {}} messages = [*request.history, {"role": "user", "content": user_message}] try: content, trace = _run_model_chat(messages) messages.append({"role": "assistant", "content": content}) return { "content": content, "html": _assistant_message(content, trace), "history": messages, "trace": trace, } except Exception as exc: logger.exception("SmolnalysisMoE chat failed") detail = f"{type(exc).__name__}: {str(exc).strip() or type(exc).__name__}" messages.append({"role": "assistant", "content": detail}) return { "content": detail, "html": html.escape(detail), "history": messages, "trace": {"error": detail}, } if __name__ == "__main__": app.launch()