Download app.py from VoidOaz/Timur: direct link, hf CLI and curl.
- Browser
- Download file 9.23 kB
-
https://huggingface.co/spaces/VoidOaz/Timur/resolve/main/app.py
- Command line
-
hf download hf://spaces/VoidOaz/Timur/app.py
-
curl -L -o app.py https://huggingface.co/spaces/VoidOaz/Timur/resolve/main/app.py
9.23 kB
| """ | |
| Timur 1.6 Flash — AI Chat (Single File, Gradio 6.x Compatible) | |
| """ | |
| """ stdlib """ | |
| import os, sys, logging, time, threading | |
| from typing import Generator | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(name)s] %(levelname)s — %(message)s", | |
| ) | |
| logger = logging.getLogger(__name__) | |
| """ 1. CONFIGURATION """ | |
| MODEL_FILE = os.environ.get("TIMUR_MODEL_FILE", "timur_16_flash.pt") | |
| MODEL_DISPLAY = os.environ.get("TIMUR_MODEL_DISPLAY", "Timur 1.6 Flash") | |
| FALLBACK_MODEL = os.environ.get("TIMUR_FALLBACK_MODEL","claude-sonnet-4-20250514") | |
| ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "") | |
| MAX_NEW_TOKENS = int(os.environ.get("MAX_NEW_TOKENS", "512")) | |
| TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.7")) | |
| TOP_P = float(os.environ.get("TOP_P", "0.9")) | |
| REP_PENALTY = float(os.environ.get("REP_PENALTY", "1.1")) | |
| MAX_HISTORY_CHARS = int(os.environ.get("MAX_HISTORY_CHARS", "12000")) | |
| MAX_HISTORY_TURNS = int(os.environ.get("MAX_HISTORY_TURNS", "20")) | |
| MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3")) | |
| RETRY_BASE_DELAY = float(os.environ.get("RETRY_BASE", "1.0")) | |
| MAX_RPM = int(os.environ.get("MAX_RPM", "30")) | |
| QUANTIZATION = os.environ.get("QUANTIZATION", "none") | |
| PORT = int(os.environ.get("PORT", "7860")) | |
| AVAILABLE_MODELS = [MODEL_DISPLAY] | |
| """ 2. LOCAL MODEL LOADER """ | |
| _model = None | |
| _tokenizer = None | |
| _device = None | |
| _load_error = None | |
| _loaded = False | |
| metrics = { | |
| "total_requests": 0, "local_requests": 0, | |
| "fallback_requests": 0, "errors": 0, | |
| "total_tokens": 0, "total_latency_ms": 0, | |
| } | |
| def _detect_device() -> str: | |
| try: | |
| import torch | |
| if torch.cuda.is_available(): | |
| logger.info(f"[Timur] GPU: {torch.cuda.get_device_name(0)}") | |
| return "cuda" | |
| if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): | |
| logger.info("[Timur] GPU: Apple MPS") | |
| return "mps" | |
| except ImportError: | |
| pass | |
| logger.info("[Timur] CPU will be used") | |
| return "cpu" | |
| def _try_load_model() -> bool: | |
| global _model, _tokenizer, _device, _load_error | |
| if not os.path.exists(MODEL_FILE): | |
| _load_error = f"{MODEL_FILE} not found" | |
| logger.warning(f"[Timur] {_load_error}") | |
| return False | |
| try: | |
| import torch | |
| except ImportError: | |
| _load_error = "torch is not installed" | |
| logger.warning(f"[Timur] {_load_error}") | |
| return False | |
| _device = _detect_device() | |
| logger.info(f"[Timur] Loading {MODEL_FILE}...") | |
| """ Fallback directly to Anthropic for this simplified UI example """ | |
| _load_error = "Skipping local load for UI testing, using API fallback." | |
| return False | |
| def _ensure_loaded(): | |
| global _loaded | |
| if _loaded: | |
| return | |
| _loaded = True | |
| _try_load_model() | |
| def _is_local() -> bool: | |
| _ensure_loaded() | |
| return _model is not None and _tokenizer is not None | |
| """ 3. INFERENCE """ | |
| import anthropic as _ant | |
| _api_client = _ant.Anthropic(api_key=ANTHROPIC_API_KEY or None) | |
| _gen_lock = threading.Lock() | |
| _request_times: list = [] | |
| _rate_lock = threading.Lock() | |
| if not ANTHROPIC_API_KEY: | |
| logger.warning("[Timur] ANTHROPIC_API_KEY is missing!") | |
| def _rate_ok() -> bool: | |
| now = time.time() | |
| with _rate_lock: | |
| _request_times[:] = [t for t in _request_times if now - t < 60] | |
| if len(_request_times) >= MAX_RPM: | |
| return False | |
| _request_times.append(now) | |
| return True | |
| def _trim_history(history): | |
| if len(history) > MAX_HISTORY_TURNS: | |
| history = history[-MAX_HISTORY_TURNS:] | |
| while history: | |
| total = sum(len(u or "") + len(a or "") for u, a in history) | |
| if total <= MAX_HISTORY_CHARS: | |
| break | |
| history = history[1:] | |
| return history | |
| def _api_stream(messages, system) -> Generator[str, None, None]: | |
| last_exc = None | |
| for attempt in range(MAX_RETRIES): | |
| try: | |
| with _api_client.messages.stream( | |
| model=FALLBACK_MODEL, max_tokens=MAX_NEW_TOKENS, | |
| system=system, messages=messages, | |
| ) as s: | |
| for tok in s.text_stream: | |
| yield tok | |
| return | |
| except Exception as e: | |
| logger.error(f"[Timur] API error: {e}") | |
| last_exc = e; break | |
| raise RuntimeError(f"All attempts failed: {last_exc}") | |
| def _build_api_msgs(history, user_msg): | |
| msgs = [] | |
| for u, a in history: | |
| if u: msgs.append({"role": "user", "content": u}) | |
| if a: msgs.append({"role": "assistant", "content": a}) | |
| msgs.append({"role": "user", "content": user_msg}) | |
| return msgs | |
| def chat_stream(message, history, model_choice): | |
| if not message.strip(): | |
| yield history, ""; return | |
| if not _rate_ok(): | |
| yield history + [[message, "Too many requests. Please wait."]], ""; return | |
| if not _gen_lock.acquire(blocking=False): | |
| yield history + [[message, "Currently processing another request."]], ""; return | |
| metrics["total_requests"] += 1 | |
| t0 = time.time() | |
| try: | |
| system = f"You are {model_choice}, a helpful and thoughtful AI assistant." | |
| trimmed = _trim_history(list(history)) | |
| history = trimmed + [[message, ""]] | |
| metrics["fallback_requests"] += 1 | |
| api_msgs = _build_api_msgs(trimmed, message) | |
| for tok in _api_stream(api_msgs, system): | |
| history[-1][1] += tok | |
| yield list(history), "streaming" | |
| except Exception as e: | |
| metrics["errors"] += 1 | |
| logger.error(f"[Timur] chat_stream error: {e}") | |
| history[-1][1] = f"Error: {e}" | |
| yield list(history), "" | |
| finally: | |
| _gen_lock.release() | |
| metrics["total_latency_ms"] += int((time.time() - t0) * 1000) | |
| yield list(history), "" | |
| """ 4. CSS (Gemini Style) """ | |
| CSS = """ | |
| :root { | |
| --bg: rgb(25, 25, 28); | |
| --chat-bg: rgb(30, 31, 34); | |
| --user-bg: rgb(50, 52, 56); | |
| --txt: rgb(240, 240, 240); | |
| --accent: rgb(138, 180, 248); | |
| } | |
| body, .gradio-container { | |
| background-color: var(--bg) !important; | |
| color: var(--txt) !important; | |
| font-family: 'Google Sans', 'Sora', sans-serif !important; | |
| } | |
| [id="chat-container"] { | |
| max-width: 900px !important; | |
| margin: 0 auto !important; | |
| border: none !important; | |
| } | |
| [id="chatbot"] { | |
| background: transparent !important; | |
| border: none !important; | |
| } | |
| .message.user { | |
| background-color: var(--user-bg) !important; | |
| color: var(--txt) !important; | |
| border-radius: 20px 20px 4px 20px !important; | |
| padding: 12px 18px !important; | |
| border: none !important; | |
| } | |
| .message.bot { | |
| background-color: transparent !important; | |
| color: var(--txt) !important; | |
| border: none !important; | |
| padding: 12px 0px !important; | |
| } | |
| [id="input-container"] { | |
| max-width: 900px !important; | |
| margin: 0 auto !important; | |
| background-color: var(--chat-bg) !important; | |
| border-radius: 30px !important; | |
| padding: 8px 16px !important; | |
| border: 1px solid rgb(60, 60, 65) !important; | |
| } | |
| [id="input-container"] textarea { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| color: var(--txt) !important; | |
| font-size: 1rem !important; | |
| } | |
| [id="send-btn"] { | |
| background-color: transparent !important; | |
| border: none !important; | |
| color: var(--accent) !important; | |
| cursor: pointer !important; | |
| box-shadow: none !important; | |
| } | |
| """ | |
| """ 5. GRADIO UI """ | |
| import gradio as gr | |
| def on_submit(message, history, model_choice): | |
| if not message.strip(): | |
| yield "", history, history | |
| return | |
| final_hist = history | |
| for new_hist, st in chat_stream(message, history, model_choice): | |
| final_hist = new_hist | |
| yield "", new_hist, new_hist | |
| yield "", final_hist, final_hist | |
| def on_new_chat(): | |
| return [], [] | |
| with gr.Blocks(fill_height=True, css=CSS, theme=gr.themes.Default()) as demo: | |
| history_state = gr.State([]) | |
| with gr.Column(elem_id="chat-container", scale=1): | |
| chatbot = gr.Chatbot( | |
| elem_id="chatbot", | |
| show_label=False, | |
| scale=1, | |
| avatar_images=(None, None), | |
| render_markdown=True, | |
| sanitize_html=True, | |
| ) | |
| with gr.Row(elem_id="input-container"): | |
| msg = gr.Textbox( | |
| show_label=False, | |
| placeholder="Ask Timur...", | |
| lines=1, | |
| max_lines=5, | |
| scale=8, | |
| container=False | |
| ) | |
| model_dd = gr.Dropdown( | |
| choices=AVAILABLE_MODELS, value=MODEL_DISPLAY, | |
| show_label=False, interactive=True, | |
| scale=1, container=False | |
| ) | |
| send_btn = gr.Button("Send", elem_id="send-btn", scale=1) | |
| sub_in = [msg, history_state, model_dd] | |
| sub_out = [msg, chatbot, history_state] | |
| send_btn.click(fn=on_submit, inputs=sub_in, outputs=sub_out, queue=True) | |
| msg.submit(fn=on_submit, inputs=sub_in, outputs=sub_out, queue=True) | |
| if __name__ == "__main__": | |
| demo.queue().launch( | |
| server_name="0.0.0.0", | |
| server_port=PORT, | |
| share=False, | |
| show_api=False | |
| ) |