Spaces:
Running
Running
API-based serving (api.1street.ai), English-only UI; identical UI to Darwin-9B-Opus space
Browse files- .gitattributes +35 -35
- README.md +19 -6
- app.py +390 -0
- index.html +757 -0
- requirements.txt +8 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,35 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
| 1 |
---
|
| 2 |
title: Darwin 9B NEG
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Darwin 9B NEG
|
| 3 |
+
emoji: 🧬
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.10.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
short_description: Darwin-9B-NEG reasoning model — API-served chat demo
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# Darwin-9B-NEG — API Chat Demo
|
| 15 |
+
|
| 16 |
+
A lightweight chat demo for **Darwin-9B-NEG**, served over an OpenAI-compatible
|
| 17 |
+
API (no local GPU). The UI is identical to the Darwin-9B-Opus space; only the
|
| 18 |
+
backend is API-based.
|
| 19 |
+
|
| 20 |
+
- **Model**: [FINAL-Bench/Darwin-9B-NEG](https://huggingface.co/FINAL-Bench/Darwin-9B-NEG)
|
| 21 |
+
- **GPQA Diamond**: #22 globally (84.34)
|
| 22 |
+
- **Serving**: OpenAI-compatible streaming API
|
| 23 |
+
- Introduced in [Darwin Family](https://arxiv.org/abs/2605.14386).
|
| 24 |
+
|
| 25 |
+
Optional Space secrets: `VIDRAFT_API_BASE`, `VIDRAFT_API_MODEL`,
|
| 26 |
+
`VIDRAFT_API_KEY`, `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `BRAVE_API_KEY`.
|
app.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
🧬 Darwin-9B-NEG — API Serving (OpenAI-compatible)
|
| 3 |
+
No local GPU. Streams from the VIDRAFT inference API (api.1street.ai).
|
| 4 |
+
Custom frontend (index.html) preserved exactly — only the backend is API-based.
|
| 5 |
+
"""
|
| 6 |
+
import sys
|
| 7 |
+
print(f"[BOOT] Python {sys.version}", flush=True)
|
| 8 |
+
|
| 9 |
+
import base64, os, re, json
|
| 10 |
+
from typing import Generator, Optional
|
| 11 |
+
|
| 12 |
+
import gradio as gr
|
| 13 |
+
print(f"[BOOT] gradio {gr.__version__}", flush=True)
|
| 14 |
+
|
| 15 |
+
import requests, httpx, uvicorn
|
| 16 |
+
from fastapi import FastAPI, Request
|
| 17 |
+
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
|
| 18 |
+
from urllib.parse import urlencode
|
| 19 |
+
import pathlib, secrets
|
| 20 |
+
|
| 21 |
+
import urllib3
|
| 22 |
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 23 |
+
|
| 24 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 25 |
+
# 1. MODEL / API CONFIG
|
| 26 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 27 |
+
API_BASE = os.getenv("VIDRAFT_API_BASE", "https://api.1street.ai")
|
| 28 |
+
API_MODEL = os.getenv("VIDRAFT_API_MODEL", "vidraft/darwin-9b-neg")
|
| 29 |
+
API_KEY = os.getenv("VIDRAFT_API_KEY", "") # optional bearer; empty = no auth
|
| 30 |
+
|
| 31 |
+
MODEL_ID = "FINAL-Bench/Darwin-9B-NEG"
|
| 32 |
+
MODEL_NAME = "Darwin-9B-NEG"
|
| 33 |
+
MODEL_CAP = {
|
| 34 |
+
"arch": "Qwen3.5 Dense", "active": "9B",
|
| 35 |
+
"ctx": "131K", "thinking": True, "vision": False,
|
| 36 |
+
"max_tokens": 8192, "temp_max": 1.5,
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
PRESETS = {
|
| 40 |
+
"general": "You are Darwin-9B-NEG, a highly capable reasoning model created by VIDRAFT via Negentropy distillation. Think step by step for complex questions.",
|
| 41 |
+
"code": "You are an expert software engineer. Write clean, efficient, well-commented code. Explain your approach before writing. Use modern best practices.",
|
| 42 |
+
"math": "You are a world-class mathematician. Break problems step-by-step. Show full working. Use LaTeX where helpful.",
|
| 43 |
+
"creative": "You are a brilliant creative writer. Be imaginative, vivid, and engaging. Adapt tone and style to the request.",
|
| 44 |
+
"translate": "You are a professional translator. Provide accurate, natural-sounding translations with cultural context.",
|
| 45 |
+
"research": "You are a rigorous research analyst. Provide structured, well-reasoned analysis. Identify assumptions and acknowledge uncertainty.",
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
print(f"[API] base={API_BASE} model={API_MODEL} auth={'yes' if API_KEY else 'no'}", flush=True)
|
| 49 |
+
|
| 50 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 51 |
+
# 2. THINKING MODE HELPERS (unchanged — drives the reasoning-chain UI)
|
| 52 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 53 |
+
def parse_think_blocks(text: str) -> tuple[str, str]:
|
| 54 |
+
m = re.search(r"<think>(.*?)</think>\s*", text, re.DOTALL)
|
| 55 |
+
return (m.group(1).strip(), text[m.end():].strip()) if m else ("", text)
|
| 56 |
+
|
| 57 |
+
def _is_thinking_line(line: str) -> bool:
|
| 58 |
+
l = line.strip()
|
| 59 |
+
if not l:
|
| 60 |
+
return True
|
| 61 |
+
think_starts = [
|
| 62 |
+
"The user", "the user", "This is", "this is", "I should", "I need to",
|
| 63 |
+
"Let me", "let me", "My task", "my task", "I'll ", "I will",
|
| 64 |
+
"Since ", "since ", "Now,", "now,", "So,", "so,", "First,", "first,",
|
| 65 |
+
"Okay", "okay", "Alright", "Hmm", "Wait", "Actually",
|
| 66 |
+
"The question", "the question", "The input", "the input",
|
| 67 |
+
"The request", "the request", "The prompt", "the prompt",
|
| 68 |
+
"Thinking Process", "Thinking process", "**Thinking",
|
| 69 |
+
"Step ", "step ", "Approach:", "Analysis:", "Reasoning:",
|
| 70 |
+
"1. **", "2. **", "3. **", "4. **", "5. **",
|
| 71 |
+
]
|
| 72 |
+
for s in think_starts:
|
| 73 |
+
if l.startswith(s):
|
| 74 |
+
return True
|
| 75 |
+
if l.startswith(("- ", "* ", "○ ")) and any(c.isascii() and c.isalpha() for c in l[:20]):
|
| 76 |
+
if not any(ord(c) > 0x1100 for c in l[:30]):
|
| 77 |
+
return True
|
| 78 |
+
return False
|
| 79 |
+
|
| 80 |
+
def _split_thinking_answer(raw: str) -> tuple:
|
| 81 |
+
lines = raw.split("\n")
|
| 82 |
+
answer_start = -1
|
| 83 |
+
for i, line in enumerate(lines):
|
| 84 |
+
if not _is_thinking_line(line):
|
| 85 |
+
if any(ord(c) > 0x1100 for c in line.strip()[:10]):
|
| 86 |
+
answer_start = i
|
| 87 |
+
break
|
| 88 |
+
if i > 2 and not _is_thinking_line(line):
|
| 89 |
+
if all(not lines[j].strip() for j in range(max(0, i - 2), i)):
|
| 90 |
+
answer_start = i
|
| 91 |
+
break
|
| 92 |
+
if answer_start > 0:
|
| 93 |
+
return "\n".join(lines[:answer_start]).strip(), "\n".join(lines[answer_start:]).strip()
|
| 94 |
+
return "", raw
|
| 95 |
+
|
| 96 |
+
def format_response(raw: str) -> str:
|
| 97 |
+
chain, answer = parse_think_blocks(raw)
|
| 98 |
+
if chain:
|
| 99 |
+
return (
|
| 100 |
+
"<details>\n<summary>🧠 Reasoning Chain — click to expand</summary>\n\n"
|
| 101 |
+
f"{chain}\n\n</details>\n\n{answer}"
|
| 102 |
+
)
|
| 103 |
+
if "<think>" in raw and "</think>" not in raw:
|
| 104 |
+
think_len = len(raw) - raw.index("<think>") - 7
|
| 105 |
+
return f"🧠 Reasoning... ({think_len} chars)"
|
| 106 |
+
first_line = raw.strip().split("\n")[0] if raw.strip() else ""
|
| 107 |
+
if _is_thinking_line(first_line) and len(raw) > 20:
|
| 108 |
+
thinking, answer = _split_thinking_answer(raw)
|
| 109 |
+
if thinking and answer:
|
| 110 |
+
return (
|
| 111 |
+
f"<details>\n<summary>🧠 Reasoning Chain ({len(thinking)} chars)</summary>\n\n"
|
| 112 |
+
f"{thinking}\n\n</details>\n\n{answer}"
|
| 113 |
+
)
|
| 114 |
+
elif thinking and not answer:
|
| 115 |
+
return f"🧠 Reasoning... ({len(raw)} chars)"
|
| 116 |
+
return raw
|
| 117 |
+
|
| 118 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 119 |
+
# 3. GENERATION — streamed from the VIDRAFT OpenAI-compatible API
|
| 120 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 121 |
+
def generate_reply(
|
| 122 |
+
message: str,
|
| 123 |
+
history: list,
|
| 124 |
+
thinking_mode: str,
|
| 125 |
+
image_input,
|
| 126 |
+
system_prompt: str,
|
| 127 |
+
max_new_tokens: int,
|
| 128 |
+
temperature: float,
|
| 129 |
+
top_p: float,
|
| 130 |
+
) -> Generator[str, None, None]:
|
| 131 |
+
|
| 132 |
+
max_new_tokens = min(int(max_new_tokens), MODEL_CAP["max_tokens"])
|
| 133 |
+
temperature = min(float(temperature), MODEL_CAP["temp_max"])
|
| 134 |
+
|
| 135 |
+
# ── Build the message list ──
|
| 136 |
+
messages: list[dict] = []
|
| 137 |
+
if system_prompt.strip():
|
| 138 |
+
messages.append({"role": "system", "content": system_prompt.strip()})
|
| 139 |
+
|
| 140 |
+
for turn in history:
|
| 141 |
+
if isinstance(turn, dict):
|
| 142 |
+
role = turn.get("role", "")
|
| 143 |
+
raw = turn.get("content") or ""
|
| 144 |
+
text = (" ".join(p.get("text", "") for p in raw
|
| 145 |
+
if isinstance(p, dict) and p.get("type") == "text")
|
| 146 |
+
if isinstance(raw, list) else str(raw))
|
| 147 |
+
if role == "user":
|
| 148 |
+
messages.append({"role": "user", "content": text})
|
| 149 |
+
elif role == "assistant":
|
| 150 |
+
_, clean = parse_think_blocks(text)
|
| 151 |
+
messages.append({"role": "assistant", "content": clean})
|
| 152 |
+
else:
|
| 153 |
+
try:
|
| 154 |
+
u, a = (turn[0] or None), (turn[1] if len(turn) > 1 else None)
|
| 155 |
+
except (IndexError, TypeError):
|
| 156 |
+
continue
|
| 157 |
+
def _txt(v):
|
| 158 |
+
if v is None: return None
|
| 159 |
+
if isinstance(v, list):
|
| 160 |
+
return " ".join(p.get("text", "") for p in v
|
| 161 |
+
if isinstance(p, dict) and p.get("type") == "text")
|
| 162 |
+
return str(v)
|
| 163 |
+
ut, at = _txt(u), _txt(a)
|
| 164 |
+
if ut: messages.append({"role": "user", "content": ut})
|
| 165 |
+
if at:
|
| 166 |
+
_, clean = parse_think_blocks(at)
|
| 167 |
+
messages.append({"role": "assistant", "content": clean})
|
| 168 |
+
|
| 169 |
+
messages.append({"role": "user", "content": message})
|
| 170 |
+
|
| 171 |
+
payload = {
|
| 172 |
+
"model": API_MODEL,
|
| 173 |
+
"messages": messages,
|
| 174 |
+
"max_tokens": max_new_tokens,
|
| 175 |
+
"temperature": max(float(temperature), 0.0),
|
| 176 |
+
"top_p": float(top_p),
|
| 177 |
+
"stream": True,
|
| 178 |
+
"stream_options": {"include_usage": True},
|
| 179 |
+
}
|
| 180 |
+
headers = {"Content-Type": "application/json"}
|
| 181 |
+
if API_KEY:
|
| 182 |
+
headers["Authorization"] = f"Bearer {API_KEY}"
|
| 183 |
+
|
| 184 |
+
print(f"[GEN] -> {API_BASE} model={API_MODEL} max_new={max_new_tokens} temp={temperature}", flush=True)
|
| 185 |
+
|
| 186 |
+
output = ""
|
| 187 |
+
try:
|
| 188 |
+
with httpx.Client(timeout=httpx.Timeout(300.0, connect=15.0)) as client:
|
| 189 |
+
with client.stream("POST", f"{API_BASE}/v1/chat/completions",
|
| 190 |
+
json=payload, headers=headers) as r:
|
| 191 |
+
if r.status_code != 200:
|
| 192 |
+
body = r.read().decode(errors="ignore")[:300]
|
| 193 |
+
yield f"**❌ API error {r.status_code}:** `{body}`"
|
| 194 |
+
return
|
| 195 |
+
for line in r.iter_lines():
|
| 196 |
+
if not line:
|
| 197 |
+
continue
|
| 198 |
+
if line.startswith("data: "):
|
| 199 |
+
line = line[6:]
|
| 200 |
+
if line.strip() == "[DONE]":
|
| 201 |
+
break
|
| 202 |
+
try:
|
| 203 |
+
chunk = json.loads(line)
|
| 204 |
+
except Exception:
|
| 205 |
+
continue
|
| 206 |
+
choices = chunk.get("choices") or []
|
| 207 |
+
if choices:
|
| 208 |
+
delta = choices[0].get("delta") or {}
|
| 209 |
+
piece = delta.get("content") or ""
|
| 210 |
+
if piece:
|
| 211 |
+
output += piece
|
| 212 |
+
yield format_response(output)
|
| 213 |
+
except Exception as e:
|
| 214 |
+
if output:
|
| 215 |
+
yield format_response(output)
|
| 216 |
+
else:
|
| 217 |
+
yield f"**❌ Generation error:** `{e}`"
|
| 218 |
+
return
|
| 219 |
+
|
| 220 |
+
if output:
|
| 221 |
+
print(f"[GEN] Done — {len(output)} chars", flush=True)
|
| 222 |
+
yield format_response(output)
|
| 223 |
+
else:
|
| 224 |
+
yield "**⚠️ The model returned an empty response.** Please try again."
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 228 |
+
# 4. GRADIO BLOCKS (api_name="chat" — index.html calls /gradio_api/call/chat)
|
| 229 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 230 |
+
with gr.Blocks(title=MODEL_NAME) as gradio_demo:
|
| 231 |
+
thinking_toggle = gr.Radio(
|
| 232 |
+
choices=["⚡ Fast Mode (direct answer)",
|
| 233 |
+
"🧠 Thinking Mode (chain-of-thought reasoning)"],
|
| 234 |
+
value="⚡ Fast Mode (direct answer)",
|
| 235 |
+
visible=False,
|
| 236 |
+
)
|
| 237 |
+
image_input = gr.Textbox(value="", visible=False)
|
| 238 |
+
system_prompt = gr.Textbox(value=PRESETS["general"], visible=False)
|
| 239 |
+
max_new_tokens = gr.Slider(minimum=64, maximum=8192, value=4096, visible=False)
|
| 240 |
+
temperature = gr.Slider(minimum=0.0, maximum=1.5, value=0.6, visible=False)
|
| 241 |
+
top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.9, visible=False)
|
| 242 |
+
|
| 243 |
+
gr.ChatInterface(
|
| 244 |
+
fn=generate_reply,
|
| 245 |
+
api_name="chat",
|
| 246 |
+
additional_inputs=[
|
| 247 |
+
thinking_toggle, image_input,
|
| 248 |
+
system_prompt, max_new_tokens, temperature, top_p,
|
| 249 |
+
],
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 253 |
+
# 5. FASTAPI — index.html + OAuth + utility APIs (unchanged structure)
|
| 254 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 255 |
+
fapp = FastAPI()
|
| 256 |
+
SESSIONS: dict[str, dict] = {}
|
| 257 |
+
HTML = pathlib.Path(__file__).parent / "index.html"
|
| 258 |
+
|
| 259 |
+
CLIENT_ID = os.getenv("OAUTH_CLIENT_ID", "")
|
| 260 |
+
CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET", "")
|
| 261 |
+
SPACE_HOST = os.getenv("SPACE_HOST", "localhost:7860")
|
| 262 |
+
REDIRECT_URI = f"https://{SPACE_HOST}/login/callback"
|
| 263 |
+
|
| 264 |
+
print(f"[OAuth] CLIENT_ID set: {bool(CLIENT_ID)}")
|
| 265 |
+
print(f"[OAuth] SPACE_HOST: {SPACE_HOST}")
|
| 266 |
+
HF_AUTH_URL = "https://huggingface.co/oauth/authorize"
|
| 267 |
+
HF_TOKEN_URL = "https://huggingface.co/oauth/token"
|
| 268 |
+
HF_USER_URL = "https://huggingface.co/oauth/userinfo"
|
| 269 |
+
SCOPES = os.getenv("OAUTH_SCOPES", "openid profile")
|
| 270 |
+
|
| 271 |
+
def _sid(req: Request) -> Optional[str]:
|
| 272 |
+
return req.cookies.get("mc_session")
|
| 273 |
+
def _user(req: Request) -> Optional[dict]:
|
| 274 |
+
sid = _sid(req)
|
| 275 |
+
return SESSIONS.get(sid) if sid else None
|
| 276 |
+
|
| 277 |
+
@fapp.get("/")
|
| 278 |
+
async def root(request: Request):
|
| 279 |
+
html = HTML.read_text(encoding="utf-8") if HTML.exists() else "<h2>index.html missing</h2>"
|
| 280 |
+
return HTMLResponse(html)
|
| 281 |
+
|
| 282 |
+
@fapp.get("/oauth/user")
|
| 283 |
+
async def oauth_user(request: Request):
|
| 284 |
+
u = _user(request)
|
| 285 |
+
return JSONResponse(u) if u else JSONResponse({"logged_in": False}, status_code=401)
|
| 286 |
+
|
| 287 |
+
@fapp.get("/oauth/login")
|
| 288 |
+
async def oauth_login(request: Request):
|
| 289 |
+
if not CLIENT_ID:
|
| 290 |
+
return RedirectResponse("/?oauth_error=not_configured")
|
| 291 |
+
state = secrets.token_urlsafe(16)
|
| 292 |
+
params = {"response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "scope": SCOPES, "state": state}
|
| 293 |
+
return RedirectResponse(f"{HF_AUTH_URL}?{urlencode(params)}", status_code=302)
|
| 294 |
+
|
| 295 |
+
@fapp.get("/login/callback")
|
| 296 |
+
async def oauth_callback(code: str = "", error: str = "", state: str = ""):
|
| 297 |
+
if error or not code:
|
| 298 |
+
return RedirectResponse("/?auth_error=1")
|
| 299 |
+
basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
|
| 300 |
+
async with httpx.AsyncClient() as client:
|
| 301 |
+
tok = await client.post(HF_TOKEN_URL, data={"grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI},
|
| 302 |
+
headers={"Accept": "application/json", "Authorization": f"Basic {basic}"})
|
| 303 |
+
if tok.status_code != 200:
|
| 304 |
+
return RedirectResponse("/?auth_error=1")
|
| 305 |
+
access_token = tok.json().get("access_token", "")
|
| 306 |
+
if not access_token:
|
| 307 |
+
return RedirectResponse("/?auth_error=1")
|
| 308 |
+
uinfo = await client.get(HF_USER_URL, headers={"Authorization": f"Bearer {access_token}"})
|
| 309 |
+
if uinfo.status_code != 200:
|
| 310 |
+
return RedirectResponse("/?auth_error=1")
|
| 311 |
+
user = uinfo.json()
|
| 312 |
+
sid = secrets.token_urlsafe(32)
|
| 313 |
+
SESSIONS[sid] = {
|
| 314 |
+
"logged_in": True,
|
| 315 |
+
"username": user.get("preferred_username", user.get("name", "User")),
|
| 316 |
+
"name": user.get("name", ""),
|
| 317 |
+
"avatar": user.get("picture", ""),
|
| 318 |
+
"profile": f"https://huggingface.co/{user.get('preferred_username', '')}",
|
| 319 |
+
}
|
| 320 |
+
resp = RedirectResponse("/")
|
| 321 |
+
resp.set_cookie("mc_session", sid, httponly=True, samesite="lax", secure=True, max_age=60 * 60 * 24 * 7)
|
| 322 |
+
return resp
|
| 323 |
+
|
| 324 |
+
@fapp.get("/oauth/logout")
|
| 325 |
+
async def oauth_logout(request: Request):
|
| 326 |
+
sid = _sid(request)
|
| 327 |
+
if sid and sid in SESSIONS: del SESSIONS[sid]
|
| 328 |
+
resp = RedirectResponse("/")
|
| 329 |
+
resp.delete_cookie("mc_session")
|
| 330 |
+
return resp
|
| 331 |
+
|
| 332 |
+
@fapp.get("/health")
|
| 333 |
+
async def health():
|
| 334 |
+
return {"status": "ok", "model": MODEL_ID, "serving": "api", "api_base": API_BASE}
|
| 335 |
+
|
| 336 |
+
# ── Web Search API (Brave) ──
|
| 337 |
+
BRAVE_API_KEY = os.getenv("BRAVE_API_KEY", "")
|
| 338 |
+
|
| 339 |
+
@fapp.post("/api/search")
|
| 340 |
+
async def api_search(request: Request):
|
| 341 |
+
body = await request.json()
|
| 342 |
+
query = body.get("query", "").strip()
|
| 343 |
+
if not query:
|
| 344 |
+
return JSONResponse({"error": "empty query"}, status_code=400)
|
| 345 |
+
if not BRAVE_API_KEY:
|
| 346 |
+
return JSONResponse({"error": "BRAVE_API_KEY not set"}, status_code=500)
|
| 347 |
+
try:
|
| 348 |
+
r = requests.get(
|
| 349 |
+
"https://api.search.brave.com/res/v1/web/search",
|
| 350 |
+
headers={"X-Subscription-Token": BRAVE_API_KEY, "Accept": "application/json"},
|
| 351 |
+
params={"q": query, "count": 5}, timeout=10,
|
| 352 |
+
)
|
| 353 |
+
r.raise_for_status()
|
| 354 |
+
results = r.json().get("web", {}).get("results", [])
|
| 355 |
+
items = [{"title": it.get("title", ""), "desc": it.get("description", ""), "url": it.get("url", "")} for it in results[:5]]
|
| 356 |
+
return JSONResponse({"results": items})
|
| 357 |
+
except Exception as e:
|
| 358 |
+
return JSONResponse({"error": str(e)}, status_code=500)
|
| 359 |
+
|
| 360 |
+
# ── PDF Text Extraction ──
|
| 361 |
+
@fapp.post("/api/extract-pdf")
|
| 362 |
+
async def api_extract_pdf(request: Request):
|
| 363 |
+
try:
|
| 364 |
+
body = await request.json()
|
| 365 |
+
b64 = body.get("data", "")
|
| 366 |
+
if "," in b64:
|
| 367 |
+
b64 = b64.split(",", 1)[1]
|
| 368 |
+
pdf_bytes = base64.b64decode(b64)
|
| 369 |
+
text = ""
|
| 370 |
+
try:
|
| 371 |
+
import fitz
|
| 372 |
+
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
| 373 |
+
for page in doc:
|
| 374 |
+
text += page.get_text() + "\n"
|
| 375 |
+
except ImportError:
|
| 376 |
+
content = pdf_bytes.decode("utf-8", errors="ignore")
|
| 377 |
+
text = re.sub(r'[^\x20-\x7E\n\r]', '', content)
|
| 378 |
+
text = text.strip()[:8000]
|
| 379 |
+
return JSONResponse({"text": text, "chars": len(text)})
|
| 380 |
+
except Exception as e:
|
| 381 |
+
return JSONResponse({"error": str(e)}, status_code=500)
|
| 382 |
+
|
| 383 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 384 |
+
# 6. MOUNT & RUN
|
| 385 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 386 |
+
app = gr.mount_gradio_app(fapp, gradio_demo, path="/gradio")
|
| 387 |
+
|
| 388 |
+
if __name__ == "__main__":
|
| 389 |
+
print(f"[BOOT] {MODEL_NAME} · API serving · Ready", flush=True)
|
| 390 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
index.html
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>🧬 Darwin-9B-Opus</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Cabinet+Grotesk:wght@300;400;500;600;700;800&family=Geist+Mono:wght@300;400;500&display=swap" rel="stylesheet">
|
| 9 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
| 10 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 11 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
| 12 |
+
<style>
|
| 13 |
+
*{margin:0;padding:0;box-sizing:border-box;}
|
| 14 |
+
:root{
|
| 15 |
+
--cream:#faf8f5;--paper:#f5f2ed;--white:#ffffff;--fog:#ede9e3;
|
| 16 |
+
--line:#e4dfd8;--line2:#d5cfc7;
|
| 17 |
+
--ink:#1c1917;--ink2:#44403c;--ink3:#78716c;--ink4:#a8a29e;
|
| 18 |
+
--v:#6d28d9;--v2:#7c3aed;--vg:rgba(109,40,217,.08);
|
| 19 |
+
--teal:#0d9488;--tg:rgba(13,148,136,.07);
|
| 20 |
+
--rose:#e11d48;--green:#16a34a;--amber:#d97706;
|
| 21 |
+
--r:20px;--r-sm:12px;--r-xs:8px;
|
| 22 |
+
--sh:0 2px 12px rgba(28,25,23,.06),0 1px 3px rgba(28,25,23,.04);
|
| 23 |
+
--sh2:0 8px 32px rgba(28,25,23,.08),0 2px 8px rgba(28,25,23,.04);
|
| 24 |
+
--sh3:0 20px 60px rgba(28,25,23,.10),0 4px 16px rgba(28,25,23,.06);
|
| 25 |
+
--fd:'Instrument Serif',serif;
|
| 26 |
+
--fb:'Cabinet Grotesk',sans-serif;
|
| 27 |
+
--fm:'Geist Mono',monospace;
|
| 28 |
+
}
|
| 29 |
+
html,body{height:100%;overflow:hidden;background:var(--cream);color:var(--ink);font-family:var(--fb);-webkit-font-smoothing:antialiased;}
|
| 30 |
+
|
| 31 |
+
.bg{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;}
|
| 32 |
+
.orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.5;animation:drift linear infinite;}
|
| 33 |
+
.orb1{width:500px;height:500px;background:radial-gradient(circle,rgba(109,40,217,.35),transparent 70%);top:-100px;left:-100px;animation-duration:22s;}
|
| 34 |
+
.orb2{width:380px;height:380px;background:radial-gradient(circle,rgba(16,185,129,.28),transparent 70%);top:5%;right:-60px;animation-duration:28s;animation-delay:-9s;}
|
| 35 |
+
.orb3{width:320px;height:320px;background:radial-gradient(circle,rgba(252,211,77,.22),transparent 70%);bottom:-60px;left:35%;animation-duration:19s;animation-delay:-5s;}
|
| 36 |
+
@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);}}
|
| 37 |
+
.bg-grid{position:absolute;inset:0;background-image:radial-gradient(circle,rgba(28,25,23,.06) 1px,transparent 1px);background-size:28px 28px;}
|
| 38 |
+
|
| 39 |
+
.shell{position:relative;z-index:1;display:grid;grid-template-columns:280px 1fr;height:100vh;}
|
| 40 |
+
|
| 41 |
+
/* SIDEBAR */
|
| 42 |
+
.sidebar{display:flex;flex-direction:column;background:rgba(255,255,255,.75);backdrop-filter:blur(24px);border-right:1px solid var(--line);overflow:hidden;}
|
| 43 |
+
.logo-area{padding:20px 18px 14px;border-bottom:1px solid var(--line);}
|
| 44 |
+
.logo-row{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
|
| 45 |
+
.logo-icon{width:42px;height:42px;border-radius:13px;flex-shrink:0;background:linear-gradient(135deg,#6d28d9,#a78bfa,#10b981);display:flex;align-items:center;justify-content:center;font-size:22px;box-shadow:0 4px 14px rgba(109,40,217,.3);}
|
| 46 |
+
.logo-text{line-height:1.2;}
|
| 47 |
+
.logo-name{font-family:var(--fd);font-size:20px;color:var(--ink);}
|
| 48 |
+
.logo-name em{color:var(--v);font-style:italic;}
|
| 49 |
+
.logo-sub{font-size:9px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:var(--ink4);}
|
| 50 |
+
|
| 51 |
+
.model-card{margin:14px;padding:14px 16px;border-radius:var(--r-sm);border:1.5px solid rgba(109,40,217,.25);background:linear-gradient(135deg,rgba(109,40,217,.04),rgba(16,185,129,.03));box-shadow:var(--sh);}
|
| 52 |
+
.mc-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;}
|
| 53 |
+
.mc-name{font-size:13px;font-weight:700;color:var(--ink);}
|
| 54 |
+
.mc-arch{font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 8px;border-radius:10px;background:var(--vg);color:var(--v);font-family:var(--fm);}
|
| 55 |
+
.mc-stats{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;}
|
| 56 |
+
.mc-stat{font-size:9px;font-weight:600;padding:2px 7px;border-radius:6px;font-family:var(--fm);}
|
| 57 |
+
.mc-ok{background:rgba(22,163,74,.09);color:#16a34a;}
|
| 58 |
+
.mc-hl{background:rgba(109,40,217,.08);color:var(--v);}
|
| 59 |
+
.mc-desc{font-size:10px;color:var(--ink3);line-height:1.6;}
|
| 60 |
+
|
| 61 |
+
.settings{padding:14px;flex:1;overflow-y:auto;}
|
| 62 |
+
.settings::-webkit-scrollbar{width:3px;}
|
| 63 |
+
.settings::-webkit-scrollbar-thumb{background:var(--line2);border-radius:10px;}
|
| 64 |
+
.field{margin-bottom:12px;}
|
| 65 |
+
.field-lbl{font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--ink4);margin-bottom:6px;display:block;}
|
| 66 |
+
.field textarea{width:100%;background:var(--fog);border:1.5px solid var(--line);border-radius:var(--r-xs);color:var(--ink);font-family:var(--fb);font-size:12px;line-height:1.6;padding:8px 10px;resize:none;outline:none;transition:border-color .2s,box-shadow .2s;height:64px;}
|
| 67 |
+
.field textarea:focus{border-color:rgba(109,40,217,.35);box-shadow:0 0 0 3px rgba(109,40,217,.08);}
|
| 68 |
+
|
| 69 |
+
.toggle-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;}
|
| 70 |
+
.toggle-info{flex:1;}
|
| 71 |
+
.tl{font-size:11px;font-weight:600;color:var(--ink2);}
|
| 72 |
+
.ts{font-size:9px;color:var(--ink4);margin-top:1px;}
|
| 73 |
+
.toggle{position:relative;width:36px;height:20px;flex-shrink:0;}
|
| 74 |
+
.toggle input{opacity:0;width:0;height:0;position:absolute;}
|
| 75 |
+
.t-track{position:absolute;inset:0;border-radius:10px;background:var(--line2);cursor:pointer;transition:background .22s;}
|
| 76 |
+
.toggle input:checked+.t-track{background:linear-gradient(135deg,var(--v),var(--v2));}
|
| 77 |
+
.t-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--white);box-shadow:0 1px 4px rgba(0,0,0,.15);transition:transform .22s cubic-bezier(.4,0,.2,1);}
|
| 78 |
+
.toggle input:checked~.t-thumb{transform:translateX(16px);}
|
| 79 |
+
|
| 80 |
+
.presets{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px;}
|
| 81 |
+
.chip{font-size:9px;font-weight:600;padding:3px 9px;border-radius:20px;background:var(--fog);border:1px solid var(--line);color:var(--ink3);cursor:pointer;transition:all .2s;white-space:nowrap;}
|
| 82 |
+
.chip:hover{background:rgba(109,40,217,.08);border-color:rgba(109,40,217,.25);color:var(--v);}
|
| 83 |
+
|
| 84 |
+
.sl-wrap{margin-bottom:10px;}
|
| 85 |
+
.sl-meta{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;}
|
| 86 |
+
.sl-title{font-size:11px;font-weight:600;color:var(--ink2);}
|
| 87 |
+
.sl-val{font-size:10px;font-weight:700;font-family:var(--fm);background:var(--fog);color:var(--ink3);padding:1px 6px;border-radius:5px;border:1px solid var(--line);}
|
| 88 |
+
input[type=range]{width:100%;height:4px;appearance:none;background:var(--line);border-radius:2px;outline:none;cursor:pointer;}
|
| 89 |
+
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:15px;height:15px;border-radius:50%;background:linear-gradient(135deg,var(--v),var(--v2));box-shadow:0 2px 6px rgba(109,40,217,.3);border:2px solid var(--white);cursor:pointer;transition:transform .2s;}
|
| 90 |
+
input[type=range]::-webkit-slider-thumb:hover{transform:scale(1.2);}
|
| 91 |
+
|
| 92 |
+
.clear-btn{width:100%;padding:8px;border-radius:var(--r-xs);background:var(--fog);border:1.5px solid var(--line);color:var(--ink3);font-family:var(--fb);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s;margin-top:4px;}
|
| 93 |
+
.clear-btn:hover{border-color:rgba(225,29,72,.3);color:var(--rose);}
|
| 94 |
+
|
| 95 |
+
.section-lbl{font-size:9px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:var(--ink4);margin-bottom:10px;padding:0 2px;}
|
| 96 |
+
|
| 97 |
+
/* MAIN CHAT */
|
| 98 |
+
.chat-main{display:flex;flex-direction:column;background:rgba(250,248,245,.5);overflow:hidden;}
|
| 99 |
+
.chat-hdr{padding:14px 22px;border-bottom:1px solid var(--line);background:rgba(255,255,255,.82);backdrop-filter:blur(20px);display:flex;align-items:center;justify-content:space-between;flex-shrink:0;}
|
| 100 |
+
.hdr-left{display:flex;align-items:center;gap:10px;}
|
| 101 |
+
.model-pill{display:flex;align-items:center;gap:7px;padding:5px 13px;border-radius:30px;background:linear-gradient(135deg,rgba(109,40,217,.08),rgba(16,185,129,.06));border:1px solid rgba(109,40,217,.2);}
|
| 102 |
+
.dot{width:7px;height:7px;border-radius:50%;background:linear-gradient(135deg,var(--v),#10b981);animation:pulse 2s ease-in-out infinite;}
|
| 103 |
+
@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);}}
|
| 104 |
+
.model-name{font-size:12px;font-weight:700;color:var(--v);font-family:var(--fm);}
|
| 105 |
+
.mode-tag{font-size:10px;font-weight:600;padding:4px 10px;border-radius:20px;background:var(--fog);border:1px solid var(--line);color:var(--ink3);font-family:var(--fm);transition:all .3s;}
|
| 106 |
+
.mode-tag.thinking{background:rgba(13,148,136,.08);border-color:rgba(13,148,136,.25);color:var(--teal);}
|
| 107 |
+
.hdr-stats{display:flex;gap:16px;}
|
| 108 |
+
.hstat{text-align:center;}
|
| 109 |
+
.hstat-n{font-size:17px;font-weight:800;color:var(--ink);font-family:var(--fd);}
|
| 110 |
+
.hstat-l{font-size:9px;font-weight:600;letter-spacing:.8px;text-transform:uppercase;color:var(--ink4);}
|
| 111 |
+
|
| 112 |
+
.hf-login-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:22px;border:1.5px solid rgba(109,40,217,.25);background:linear-gradient(135deg,rgba(109,40,217,.1),rgba(139,92,246,.08));color:var(--v);font-family:var(--fb);font-size:12px;font-weight:700;cursor:pointer;text-decoration:none;transition:all .22s;white-space:nowrap;}
|
| 113 |
+
.hf-login-btn:hover{background:linear-gradient(135deg,rgba(109,40,217,.18),rgba(139,92,246,.14));border-color:rgba(109,40,217,.45);box-shadow:0 4px 14px rgba(109,40,217,.18);transform:translateY(-1px);}
|
| 114 |
+
.user-chip{display:inline-flex;align-items:center;gap:8px;padding:4px 12px 4px 5px;border-radius:22px;border:1.5px solid var(--line);background:rgba(255,255,255,.8);font-size:12px;font-weight:600;color:var(--ink2);cursor:pointer;text-decoration:none;transition:all .2s;}
|
| 115 |
+
.user-chip:hover{border-color:rgba(109,40,217,.3);box-shadow:var(--sh);}
|
| 116 |
+
.user-avatar{width:24px;height:24px;border-radius:50%;background:linear-gradient(135deg,var(--v),#a78bfa);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff;overflow:hidden;flex-shrink:0;}
|
| 117 |
+
.user-avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%;}
|
| 118 |
+
.logout-btn{padding:5px 11px;border-radius:18px;border:1.5px solid var(--line);background:var(--fog);color:var(--ink4);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s;font-family:var(--fb);}
|
| 119 |
+
.logout-btn:hover{border-color:rgba(225,29,72,.3);color:var(--rose);}
|
| 120 |
+
|
| 121 |
+
.messages{flex:1;overflow-y:auto;padding:22px 40px;display:flex;flex-direction:column;gap:18px;scroll-behavior:smooth;}
|
| 122 |
+
.messages::-webkit-scrollbar{width:4px;}
|
| 123 |
+
.messages::-webkit-scrollbar-thumb{background:var(--line2);border-radius:10px;}
|
| 124 |
+
|
| 125 |
+
.welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:30px 36px;gap:20px;flex:1;min-height:400px;}
|
| 126 |
+
.welcome-icon{width:72px;height:72px;border-radius:20px;background:linear-gradient(135deg,#6d28d9,#a78bfa,#10b981);display:flex;align-items:center;justify-content:center;font-size:34px;box-shadow:0 8px 30px rgba(109,40,217,.25);animation:float 4s ease-in-out infinite;}
|
| 127 |
+
@keyframes float{0%,100%{transform:translateY(0);}50%{transform:translateY(-8px);}}
|
| 128 |
+
.welcome-title{font-family:var(--fd);font-size:34px;color:var(--ink);line-height:1.2;}
|
| 129 |
+
.welcome-title em{font-style:italic;color:var(--v);}
|
| 130 |
+
.welcome-sub{font-size:13px;color:var(--ink3);line-height:1.7;max-width:450px;}
|
| 131 |
+
.welcome-badges{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;}
|
| 132 |
+
.welcome-badge{font-size:11px;font-weight:600;padding:4px 12px;border-radius:20px;font-family:var(--fm);}
|
| 133 |
+
.wb-purple{background:rgba(109,40,217,.08);color:var(--v);border:1px solid rgba(109,40,217,.15);}
|
| 134 |
+
.wb-green{background:rgba(16,185,129,.08);color:#059669;border:1px solid rgba(16,185,129,.15);}
|
| 135 |
+
.wb-amber{background:rgba(217,119,6,.08);color:var(--amber);border:1px solid rgba(217,119,6,.15);}
|
| 136 |
+
.ex-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:9px;width:100%;max-width:500px;}
|
| 137 |
+
.ex-card{background:var(--white);border:1.5px solid var(--line);border-radius:var(--r-sm);padding:12px 14px;cursor:pointer;text-align:left;transition:all .22s;}
|
| 138 |
+
.ex-card:hover{border-color:rgba(109,40,217,.3);box-shadow:var(--sh);transform:translateY(-2px);}
|
| 139 |
+
.ex-icon{font-size:16px;margin-bottom:5px;display:block;}
|
| 140 |
+
.ex-title{font-size:11px;font-weight:700;color:var(--ink);margin-bottom:2px;}
|
| 141 |
+
.ex-desc{font-size:10px;color:var(--ink3);line-height:1.5;}
|
| 142 |
+
|
| 143 |
+
.msg{display:flex;gap:10px;animation:msgIn .32s cubic-bezier(.34,1.56,.64,1) both;}
|
| 144 |
+
@keyframes msgIn{from{opacity:0;transform:translateY(10px) scale(.97);}to{opacity:1;transform:none;}}
|
| 145 |
+
.msg.user{flex-direction:row-reverse;}
|
| 146 |
+
.avatar{width:30px;height:30px;border-radius:9px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:13px;margin-top:2px;}
|
| 147 |
+
.msg.user .avatar{background:linear-gradient(135deg,var(--v),#a78bfa);color:#fff;}
|
| 148 |
+
.msg.bot .avatar{background:linear-gradient(135deg,#f0fdf4,#dcfce7);border:1px solid #d1fae5;font-size:15px;}
|
| 149 |
+
.body{max-width:82%;display:flex;flex-direction:column;gap:3px;}
|
| 150 |
+
.msg.user .body{align-items:flex-end;}
|
| 151 |
+
.bubble{padding:11px 15px;border-radius:17px;font-size:13.5px;line-height:1.7;color:var(--ink);}
|
| 152 |
+
.msg.user .bubble{background:linear-gradient(135deg,#6d28d9,#7c3aed);color:#fff;border-radius:17px 3px 17px 17px;box-shadow:0 4px 16px rgba(109,40,217,.22);}
|
| 153 |
+
.msg.bot .bubble{background:var(--white);border:1px solid var(--line);border-radius:3px 17px 17px 17px;box-shadow:var(--sh);margin-left:8px;padding:16px 22px;}
|
| 154 |
+
.msg-time{font-size:9px;color:var(--ink4);padding:0 3px;font-family:var(--fm);}
|
| 155 |
+
|
| 156 |
+
.think-blk{background:linear-gradient(135deg,rgba(13,148,136,.04),rgba(6,182,212,.04));border:1px solid rgba(13,148,136,.2);border-radius:9px;padding:8px 12px;margin-bottom:10px;}
|
| 157 |
+
.think-hdr{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;font-weight:700;color:var(--teal);letter-spacing:.4px;text-transform:uppercase;user-select:none;}
|
| 158 |
+
.think-hdr::before{content:'▶';font-size:8px;transition:transform .2s;}
|
| 159 |
+
.think-blk.open .think-hdr::before{content:'▼';}
|
| 160 |
+
.think-body{margin-top:7px;display:none;border-top:1px solid rgba(13,148,136,.15);padding-top:7px;font-size:12px;color:var(--ink3);line-height:1.6;}
|
| 161 |
+
.think-blk.open .think-body{display:block;}
|
| 162 |
+
|
| 163 |
+
.bubble pre{background:#0d1117;border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:10px 12px;margin:7px 0;overflow-x:auto;font-family:var(--fm);font-size:11.5px;line-height:1.6;position:relative;}
|
| 164 |
+
.bubble code{font-family:var(--fm);font-size:11.5px;background:var(--fog);padding:1px 5px;border-radius:4px;}
|
| 165 |
+
.bubble pre code{background:transparent;padding:0;}
|
| 166 |
+
|
| 167 |
+
/* Code copy button */
|
| 168 |
+
.code-wrap{position:relative;margin:7px 0;}
|
| 169 |
+
.code-wrap pre{margin:0;}
|
| 170 |
+
.code-header{display:flex;justify-content:space-between;align-items:center;padding:6px 14px;background:#161b22;border-radius:8px 8px 0 0;border:1px solid rgba(255,255,255,.08);border-bottom:none;}
|
| 171 |
+
.code-lang-tag{font-size:11px;font-weight:600;color:#8b949e;font-family:var(--fm);}
|
| 172 |
+
.copy-btn{padding:3px 10px;border-radius:6px;border:1px solid rgba(255,255,255,.15);background:rgba(255,255,255,.05);color:#8b949e;font-size:10px;font-weight:600;cursor:pointer;font-family:var(--fm);transition:all .2s;}
|
| 173 |
+
.copy-btn:hover{background:rgba(255,255,255,.12);color:#c9d1d9;}
|
| 174 |
+
.copy-btn.copied{background:rgba(22,163,74,.15);color:#16a34a;border-color:rgba(22,163,74,.3);}
|
| 175 |
+
.code-wrap pre{border-radius:0 0 8px 8px;}
|
| 176 |
+
|
| 177 |
+
/* Response speed badge */
|
| 178 |
+
.speed-badge{display:inline-flex;align-items:center;gap:5px;margin-top:4px;padding:2px 8px;border-radius:12px;background:rgba(109,40,217,.06);border:1px solid rgba(109,40,217,.12);font-size:9px;font-weight:600;font-family:var(--fm);color:var(--v);}
|
| 179 |
+
|
| 180 |
+
/* Reasoning depth badge */
|
| 181 |
+
.reason-badge{display:inline-flex;align-items:center;gap:4px;margin-left:6px;padding:2px 8px;border-radius:12px;background:rgba(13,148,136,.06);border:1px solid rgba(13,148,136,.15);font-size:9px;font-weight:600;font-family:var(--fm);color:var(--teal);}
|
| 182 |
+
|
| 183 |
+
/* Search bar */
|
| 184 |
+
.search-bar{display:none;padding:8px 18px 0;}
|
| 185 |
+
.search-bar.show{display:block;}
|
| 186 |
+
.search-wrap{display:flex;gap:6px;align-items:center;}
|
| 187 |
+
.search-input{flex:1;padding:7px 12px;border-radius:var(--r-xs);border:1.5px solid var(--line);background:var(--fog);font-family:var(--fb);font-size:12px;color:var(--ink);outline:none;transition:border-color .2s;}
|
| 188 |
+
.search-input:focus{border-color:rgba(109,40,217,.35);}
|
| 189 |
+
.search-go{padding:7px 14px;border-radius:var(--r-xs);border:none;background:linear-gradient(135deg,#6d28d9,#7c3aed);color:#fff;font-size:11px;font-weight:700;cursor:pointer;font-family:var(--fb);}
|
| 190 |
+
.search-results{margin-top:6px;padding:8px 10px;background:var(--white);border:1px solid var(--line);border-radius:var(--r-xs);font-size:11px;max-height:150px;overflow-y:auto;display:none;}
|
| 191 |
+
.search-results.show{display:block;}
|
| 192 |
+
.sr-item{padding:4px 0;border-bottom:1px solid var(--fog);}
|
| 193 |
+
.sr-item:last-child{border:none;}
|
| 194 |
+
.sr-title{font-weight:700;color:var(--ink);font-size:11px;}
|
| 195 |
+
.sr-desc{color:var(--ink3);font-size:10px;line-height:1.4;}
|
| 196 |
+
.sr-url{color:var(--v);font-size:9px;text-decoration:none;}
|
| 197 |
+
|
| 198 |
+
/* PDF indicator */
|
| 199 |
+
.pdf-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;background:rgba(225,29,72,.06);border:1px solid rgba(225,29,72,.15);font-size:10px;font-weight:600;color:var(--rose);}
|
| 200 |
+
|
| 201 |
+
.typing{display:flex;align-items:center;gap:5px;padding:12px 15px;}
|
| 202 |
+
.typing span{width:6px;height:6px;border-radius:50%;background:var(--v2);opacity:.4;animation:bounce .8s ease-in-out infinite;}
|
| 203 |
+
.typing span:nth-child(2){animation-delay:.15s;}
|
| 204 |
+
.typing span:nth-child(3){animation-delay:.3s;}
|
| 205 |
+
@keyframes bounce{0%,100%{transform:translateY(0);opacity:.4;}50%{transform:translateY(-5px);opacity:1;}}
|
| 206 |
+
|
| 207 |
+
.img-prev{display:none;padding:8px 26px 0;}
|
| 208 |
+
.img-prev.show{display:block;}
|
| 209 |
+
.img-tw{display:inline-flex;align-items:center;gap:8px;background:var(--white);border:1.5px solid rgba(109,40,217,.2);border-radius:var(--r-xs);padding:5px 9px 5px 5px;}
|
| 210 |
+
.img-th{width:44px;height:44px;object-fit:cover;border-radius:5px;}
|
| 211 |
+
.img-nm{font-size:10px;font-weight:600;color:var(--ink2);}
|
| 212 |
+
.img-sz{font-size:9px;color:var(--ink4);}
|
| 213 |
+
.img-rm{width:17px;height:17px;border-radius:50%;background:var(--fog);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:9px;color:var(--ink3);transition:all .2s;margin-left:4px;}
|
| 214 |
+
.img-rm:hover{background:rgba(225,29,72,.1);color:var(--rose);}
|
| 215 |
+
|
| 216 |
+
.inputbar{padding:14px 18px 16px;border-top:1px solid var(--line);background:rgba(255,255,255,.88);backdrop-filter:blur(20px);flex-shrink:0;}
|
| 217 |
+
.input-wrap{display:flex;align-items:flex-end;gap:8px;background:var(--white);border:1.5px solid var(--line2);border-radius:var(--r);padding:7px 9px 7px 13px;transition:border-color .22s,box-shadow .22s;box-shadow:var(--sh);}
|
| 218 |
+
.input-wrap:focus-within{border-color:rgba(109,40,217,.32);box-shadow:var(--sh2),0 0 0 4px rgba(109,40,217,.06);}
|
| 219 |
+
.chat-ta{flex:1;border:none;outline:none;background:transparent;font-family:var(--fb);font-size:13.5px;color:var(--ink);line-height:1.6;resize:none;max-height:150px;min-height:22px;overflow-y:auto;padding:3px 0;}
|
| 220 |
+
.chat-ta::placeholder{color:var(--ink4);}
|
| 221 |
+
.input-acts{display:flex;align-items:center;gap:5px;padding-bottom:3px;}
|
| 222 |
+
.icon-btn{width:32px;height:32px;border-radius:9px;border:1.5px solid var(--line);background:var(--fog);display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--ink3);font-size:14px;transition:all .2s;flex-shrink:0;}
|
| 223 |
+
.icon-btn:hover{background:rgba(109,40,217,.08);border-color:rgba(109,40,217,.25);color:var(--v);}
|
| 224 |
+
.file-btn{position:relative;}
|
| 225 |
+
.file-btn input{position:absolute;inset:0;opacity:0;cursor:pointer;font-size:0;}
|
| 226 |
+
.send-btn{width:36px;height:36px;border-radius:11px;border:none;background:linear-gradient(135deg,#6d28d9,#7c3aed);color:#fff;font-size:15px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 14px rgba(109,40,217,.28);transition:all .22s cubic-bezier(.4,0,.2,1);}
|
| 227 |
+
.send-btn:hover{transform:translateY(-2px) scale(1.06);box-shadow:0 6px 20px rgba(109,40,217,.38);}
|
| 228 |
+
.send-btn:active{transform:scale(.96);}
|
| 229 |
+
.send-btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none;}
|
| 230 |
+
.input-hint{font-size:10px;color:var(--ink4);padding:5px 3px 0;display:flex;align-items:center;gap:8px;}
|
| 231 |
+
.kbd{background:var(--fog);border:1px solid var(--line);border-radius:4px;padding:1px 5px;font-family:var(--fm);font-size:9px;color:var(--ink3);}
|
| 232 |
+
.model-hint{margin-left:auto;font-weight:700;color:var(--v);font-size:10px;}
|
| 233 |
+
|
| 234 |
+
.toast{position:fixed;bottom:20px;right:20px;z-index:9999;background:var(--ink);color:#fff;font-size:11px;font-weight:600;padding:9px 16px;border-radius:var(--r-sm);box-shadow:var(--sh3);transform:translateY(50px);opacity:0;transition:all .35s cubic-bezier(.34,1.56,.64,1);pointer-events:none;}
|
| 235 |
+
.toast.show{transform:translateY(0);opacity:1;}
|
| 236 |
+
|
| 237 |
+
@media(max-width:768px){
|
| 238 |
+
.shell{grid-template-columns:1fr;}
|
| 239 |
+
.sidebar{display:none;}
|
| 240 |
+
}
|
| 241 |
+
</style>
|
| 242 |
+
</head>
|
| 243 |
+
<body>
|
| 244 |
+
|
| 245 |
+
<div class="bg">
|
| 246 |
+
<div class="orb orb1"></div><div class="orb orb2"></div><div class="orb orb3"></div>
|
| 247 |
+
<div class="bg-grid"></div>
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<div class="shell">
|
| 251 |
+
<!-- SIDEBAR -->
|
| 252 |
+
<aside class="sidebar">
|
| 253 |
+
<div class="logo-area">
|
| 254 |
+
<div class="logo-row">
|
| 255 |
+
<div class="logo-icon">🧬</div>
|
| 256 |
+
<div class="logo-text">
|
| 257 |
+
<div class="logo-name"><em>Darwin</em>-9B</div>
|
| 258 |
+
<div class="logo-sub">Evolutionary Merge</div>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
</div>
|
| 262 |
+
|
| 263 |
+
<!-- Single Model Card -->
|
| 264 |
+
<div class="model-card">
|
| 265 |
+
<div class="mc-top">
|
| 266 |
+
<span class="mc-name">Darwin-9B-Opus</span>
|
| 267 |
+
<span class="mc-arch">Dense 9B</span>
|
| 268 |
+
</div>
|
| 269 |
+
<div class="mc-stats">
|
| 270 |
+
<span class="mc-stat mc-hl">Qwen3.5 Dense</span>
|
| 271 |
+
<span class="mc-stat mc-ok">Reasoning ✓</span>
|
| 272 |
+
<span class="mc-stat mc-ok">BF16</span>
|
| 273 |
+
<span class="mc-stat mc-ok">9B params</span>
|
| 274 |
+
</div>
|
| 275 |
+
<div class="mc-desc">Evolutionary merge · Qwen3.5 architecture · Reasoning · 131K context</div>
|
| 276 |
+
<a href="https://huggingface.co/FINAL-Bench/Darwin-9B-Opus" target="_blank" style="display:inline-flex;align-items:center;gap:5px;margin-top:8px;font-size:10px;font-weight:700;color:var(--v);text-decoration:none;padding:4px 10px;border-radius:16px;background:var(--vg);border:1px solid rgba(109,40,217,.15);transition:all .2s;">🤗 Model Card ↗</a>
|
| 277 |
+
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;">
|
| 278 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/Leaderboard" target="_blank" style="display:inline-flex;align-items:center;gap:3px;font-size:8px;font-weight:700;color:#059669;text-decoration:none;padding:3px 8px;border-radius:12px;background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.15);transition:all .2s;">🏆 FINAL Bench</a>
|
| 279 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/all-bench-leaderboard" target="_blank" style="display:inline-flex;align-items:center;gap:3px;font-size:8px;font-weight:700;color:var(--amber);text-decoration:none;padding:3px 8px;border-radius:12px;background:rgba(217,119,6,.08);border:1px solid rgba(217,119,6,.15);transition:all .2s;">📊 ALL Bench</a>
|
| 280 |
+
<a href="https://huggingface.co/spaces/MAYA-AI/all-leaderboard" target="_blank" style="display:inline-flex;align-items:center;gap:3px;font-size:8px;font-weight:700;color:var(--v);text-decoration:none;padding:3px 8px;border-radius:12px;background:rgba(109,40,217,.06);border:1px solid rgba(109,40,217,.12);transition:all .2s;">🌐 MAYA LB</a>
|
| 281 |
+
</div>
|
| 282 |
+
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;">
|
| 283 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/Leaderboard" target="_blank" style="font-size:8px;font-weight:700;padding:2px 7px;border-radius:10px;background:rgba(22,163,74,.08);border:1px solid rgba(22,163,74,.18);color:#16a34a;text-decoration:none;transition:all .2s;">🏆 FINAL Bench</a>
|
| 284 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/all-bench-leaderboard" target="_blank" style="font-size:8px;font-weight:700;padding:2px 7px;border-radius:10px;background:rgba(217,119,6,.08);border:1px solid rgba(217,119,6,.18);color:#d97706;text-decoration:none;transition:all .2s;">📊 ALL Bench</a>
|
| 285 |
+
<a href="https://huggingface.co/spaces/MAYA-AI/all-leaderboard" target="_blank" style="font-size:8px;font-weight:700;padding:2px 7px;border-radius:10px;background:rgba(109,40,217,.08);border:1px solid rgba(109,40,217,.18);color:var(--v);text-decoration:none;transition:all .2s;">🌐 MAYA LB</a>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<!-- Settings -->
|
| 290 |
+
<div class="settings">
|
| 291 |
+
<div class="section-lbl">Settings</div>
|
| 292 |
+
|
| 293 |
+
<div class="toggle-row">
|
| 294 |
+
<div class="toggle-info"><div class="tl">👁️ Vision</div><div class="ts">Image understanding</div></div>
|
| 295 |
+
<label class="toggle"><input type="checkbox" id="visionToggle" checked><span class="t-track"></span><span class="t-thumb"></span></label>
|
| 296 |
+
</div>
|
| 297 |
+
|
| 298 |
+
<div class="field">
|
| 299 |
+
<span class="field-lbl">System Prompt</span>
|
| 300 |
+
<div class="presets">
|
| 301 |
+
<span class="chip" onclick="setPreset('general')">General</span>
|
| 302 |
+
<span class="chip" onclick="setPreset('code')">Code</span>
|
| 303 |
+
<span class="chip" onclick="setPreset('math')">Math</span>
|
| 304 |
+
<span class="chip" onclick="setPreset('creative')">Creative</span>
|
| 305 |
+
<span class="chip" onclick="setPreset('translate')">Translate</span>
|
| 306 |
+
<span class="chip" onclick="setPreset('research')">Research</span>
|
| 307 |
+
</div>
|
| 308 |
+
<textarea id="sysPrompt">You are Darwin-9B-Opus, a highly capable reasoning model created by VIDRAFT via evolutionary merge. Think step by step for complex questions.</textarea>
|
| 309 |
+
</div>
|
| 310 |
+
|
| 311 |
+
<div class="sl-wrap">
|
| 312 |
+
<div class="sl-meta"><span class="sl-title">Max Tokens</span><span class="sl-val" id="tokVal">4096</span></div>
|
| 313 |
+
<input type="range" id="tokSl" min="64" max="16384" value="4096" step="64" oninput="document.getElementById('tokVal').textContent=this.value">
|
| 314 |
+
</div>
|
| 315 |
+
|
| 316 |
+
<div class="sl-wrap">
|
| 317 |
+
<div class="sl-meta"><span class="sl-title">Temperature</span><span class="sl-val" id="tempVal">0.60</span></div>
|
| 318 |
+
<input type="range" id="tempSl" min="0" max="1.5" value="0.6" step="0.05" oninput="document.getElementById('tempVal').textContent=parseFloat(this.value).toFixed(2)">
|
| 319 |
+
</div>
|
| 320 |
+
|
| 321 |
+
<div class="sl-wrap">
|
| 322 |
+
<div class="sl-meta"><span class="sl-title">Top-P</span><span class="sl-val" id="topPVal">0.90</span></div>
|
| 323 |
+
<input type="range" id="topPSl" min="0.1" max="1.0" value="0.9" step="0.05" oninput="document.getElementById('topPVal').textContent=parseFloat(this.value).toFixed(2)">
|
| 324 |
+
</div>
|
| 325 |
+
|
| 326 |
+
<button class="clear-btn" onclick="clearChat()">🗑️ Clear conversation</button>
|
| 327 |
+
</div>
|
| 328 |
+
</aside>
|
| 329 |
+
|
| 330 |
+
<!-- MAIN -->
|
| 331 |
+
<main class="chat-main">
|
| 332 |
+
<header class="chat-hdr">
|
| 333 |
+
<div class="hdr-left">
|
| 334 |
+
<div class="model-pill"><div class="dot"></div><span class="model-name" id="hdrModel">Darwin-9B-Opus</span></div>
|
| 335 |
+
</div>
|
| 336 |
+
<div style="display:flex;align-items:center;gap:12px;">
|
| 337 |
+
<a href="https://huggingface.co/FINAL-Bench/Darwin-9B-Opus" target="_blank" style="display:inline-flex;align-items:center;gap:6px;padding:5px 14px;border-radius:22px;background:linear-gradient(135deg,rgba(109,40,217,.1),rgba(16,185,129,.08));border:1.5px solid rgba(109,40,217,.25);color:var(--v);font-size:12px;font-weight:700;text-decoration:none;transition:all .22s;font-family:var(--fm);">🤗 Model Card ↗</a>
|
| 338 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/Leaderboard" target="_blank" style="padding:4px 9px;border-radius:14px;background:rgba(22,163,74,.08);border:1px solid rgba(22,163,74,.18);color:#16a34a;font-size:9px;font-weight:700;text-decoration:none;font-family:var(--fm);">🏆 FINAL</a>
|
| 339 |
+
<a href="https://huggingface.co/spaces/FINAL-Bench/all-bench-leaderboard" target="_blank" style="padding:4px 9px;border-radius:14px;background:rgba(217,119,6,.08);border:1px solid rgba(217,119,6,.18);color:#d97706;font-size:9px;font-weight:700;text-decoration:none;font-family:var(--fm);">📊 ALL</a>
|
| 340 |
+
<a href="https://huggingface.co/spaces/MAYA-AI/all-leaderboard" target="_blank" style="padding:4px 9px;border-radius:14px;background:rgba(109,40,217,.08);border:1px solid rgba(109,40,217,.18);color:var(--v);font-size:9px;font-weight:700;text-decoration:none;font-family:var(--fm);">🌐 MAYA</a>
|
| 341 |
+
<a class="hf-login-btn" id="loginBtn" href="/oauth/login">
|
| 342 |
+
<svg viewBox="0 0 120 120" width="16" height="16"><path fill="currentColor" d="M41.7 56.6c-5 0-8.8 4-8.8 9s3.8 9 8.8 9 8.8-4 8.8-9-3.8-9-8.8-9zm36.6 0c-5 0-8.8 4-8.8 9s3.8 9 8.8 9 8.8-4 8.8-9-3.8-9-8.8-9z"/></svg>
|
| 343 |
+
Sign in with HF
|
| 344 |
+
</a>
|
| 345 |
+
<div id="userArea" style="display:none;align-items:center;gap:8px;">
|
| 346 |
+
<a class="user-chip" id="userChip" href="#"><div class="user-avatar" id="userAvatar"><span id="userInitial">U</span></div><span id="userName">User</span></a>
|
| 347 |
+
<button class="logout-btn" onclick="location.href='/oauth/logout'">Log out</button>
|
| 348 |
+
</div>
|
| 349 |
+
<div class="hdr-stats">
|
| 350 |
+
<div class="hstat"><div class="hstat-n" id="stMsgs">0</div><div class="hstat-l">Messages</div></div>
|
| 351 |
+
<div class="hstat"><div class="hstat-n" id="stTok">0</div><div class="hstat-l">Tokens</div></div>
|
| 352 |
+
</div>
|
| 353 |
+
</div>
|
| 354 |
+
</header>
|
| 355 |
+
|
| 356 |
+
<div class="img-prev" id="imgPrev">
|
| 357 |
+
<div class="img-tw">
|
| 358 |
+
<img class="img-th" id="imgThumb" src="">
|
| 359 |
+
<div><div class="img-nm" id="imgName">image.jpg</div><div class="img-sz" id="imgSize">—</div></div>
|
| 360 |
+
<button class="img-rm" onclick="removeImg()">✕</button>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
|
| 364 |
+
<div class="messages" id="msgs">
|
| 365 |
+
<div class="welcome" id="welcome">
|
| 366 |
+
<div class="welcome-icon">🧬</div>
|
| 367 |
+
<div class="welcome-title">Hello, I'm <em>Darwin</em></div>
|
| 368 |
+
<div class="welcome-sub">The child that surpassed both parents — built by VIDRAFT with evolutionary merge + Model MRI. Upload an image or ask anything.</div>
|
| 369 |
+
<div class="welcome-badges">
|
| 370 |
+
<span class="welcome-badge wb-purple">Qwen3.5 Dense</span>
|
| 371 |
+
<span class="welcome-badge wb-green">Reasoning ✓</span>
|
| 372 |
+
<span class="welcome-badge wb-amber">147.8 tok/s</span>
|
| 373 |
+
<a href="https://huggingface.co/FINAL-Bench/Darwin-9B-Opus" target="_blank" style="font-size:11px;font-weight:600;padding:4px 12px;border-radius:20px;background:rgba(109,40,217,.08);color:var(--v);border:1px solid rgba(109,40,217,.15);text-decoration:none;transition:all .2s;">🤗 Model Card ↗</a>
|
| 374 |
+
</div>
|
| 375 |
+
<div class="ex-grid">
|
| 376 |
+
<div class="ex-card" onclick="sendEx('Explain how Darwin V5 evolutionary merge with Model MRI works, and why it surpasses both parent models.')"><span class="ex-icon">🧬</span><div class="ex-title">Darwin Architecture</div><div class="ex-desc">How evolutionary merge works</div></div>
|
| 377 |
+
<div class="ex-card" onclick="sendEx('Write a Python async web scraper with retry logic and rate limiting. Include type hints.')"><span class="ex-icon">💻</span><div class="ex-title">Code Generation</div><div class="ex-desc">Production-quality code</div></div>
|
| 378 |
+
<div class="ex-card" onclick="trySampleVision()"><span class="ex-icon">👁️</span><div class="ex-title">Vision Demo</div><div class="ex-desc">Try image analysis now</div></div>
|
| 379 |
+
<div class="ex-card" onclick="sendEx('Analyze why Korean K-pop became a global phenomenon, from both cultural and economic perspectives.')"><span class="ex-icon">🌐</span><div class="ex-title">201 Languages</div><div class="ex-desc">Korean, Japanese, Arabic…</div></div>
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
</div>
|
| 383 |
+
|
| 384 |
+
<!-- Search Bar -->
|
| 385 |
+
<div class="search-bar" id="searchBar">
|
| 386 |
+
<div class="search-wrap">
|
| 387 |
+
<input class="search-input" id="searchInput" placeholder="Search the web..." onkeydown="if(event.key==='Enter')doSearch()">
|
| 388 |
+
<button class="search-go" onclick="doSearch()">🔍 Search</button>
|
| 389 |
+
<button class="icon-btn" onclick="toggleSearch()" style="font-size:12px;">✕</button>
|
| 390 |
+
</div>
|
| 391 |
+
<div class="search-results" id="searchResults"></div>
|
| 392 |
+
</div>
|
| 393 |
+
|
| 394 |
+
<div class="inputbar">
|
| 395 |
+
<div class="input-wrap">
|
| 396 |
+
<textarea class="chat-ta" id="chatInput" placeholder="Message Darwin…" rows="1" onkeydown="handleKey(event)" oninput="autoGrow(this)"></textarea>
|
| 397 |
+
<div class="input-acts">
|
| 398 |
+
<label class="icon-btn file-btn" id="fileLabel" title="Upload image or PDF">🖼<input type="file" accept="image/*,.pdf" onchange="handleFile(this)" id="fileInput"></label>
|
| 399 |
+
<button class="icon-btn" id="searchToggleBtn" onclick="toggleSearch()" title="Web search">🔍</button>
|
| 400 |
+
<button class="icon-btn" onclick="clearChat()" title="New chat" style="font-size:16px;">🧬</button>
|
| 401 |
+
<button class="send-btn" id="sendBtn" onclick="sendMsg()">➤</button>
|
| 402 |
+
</div>
|
| 403 |
+
</div>
|
| 404 |
+
<div class="input-hint">
|
| 405 |
+
<span><span class="kbd">Enter</span> send</span>
|
| 406 |
+
<span><span class="kbd">Shift+Enter</span> new line</span>
|
| 407 |
+
<span class="model-hint" id="modelHint">9B Dense · BF16 · 131K ctx</span>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
</main>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<div class="toast" id="toast"></div>
|
| 414 |
+
|
| 415 |
+
<script>
|
| 416 |
+
const S={vision:false,history:[],msgCount:0,totalTok:0,pending:null,busy:false,searchResults:null,
|
| 417 |
+
presets:{
|
| 418 |
+
general:'You are Darwin-9B-Opus, a highly capable reasoning model created by VIDRAFT via evolutionary merge. Think step by step for complex questions.',
|
| 419 |
+
code:'You are an expert software engineer. Write clean, efficient, well-commented code. Explain your approach before writing.',
|
| 420 |
+
math:'You are a world-class mathematician. Break problems step-by-step. Show full working.',
|
| 421 |
+
creative:'You are a brilliant creative writer. Be imaginative, vivid, and engaging.',
|
| 422 |
+
translate:'You are a professional translator fluent in 201 languages. Provide accurate translations with cultural context.',
|
| 423 |
+
research:'You are a rigorous research analyst. Provide structured, well-reasoned analysis.',
|
| 424 |
+
}
|
| 425 |
+
};
|
| 426 |
+
|
| 427 |
+
function apiBase(){return'/gradio';}
|
| 428 |
+
|
| 429 |
+
// Toggles
|
| 430 |
+
document.getElementById('visionToggle').addEventListener('change',function(){
|
| 431 |
+
S.vision=false;
|
| 432 |
+
const l=document.getElementById('fileLabel');
|
| 433 |
+
l.style.opacity=this.checked?'1':'.35';
|
| 434 |
+
l.style.pointerEvents=this.checked?'auto':'none';
|
| 435 |
+
if(!this.checked)removeImg();
|
| 436 |
+
});
|
| 437 |
+
|
| 438 |
+
function setPreset(k){document.getElementById('sysPrompt').value=S.presets[k];showToast('Preset applied ✓');}
|
| 439 |
+
|
| 440 |
+
// File handling (image + PDF)
|
| 441 |
+
function handleFile(input){
|
| 442 |
+
const f=input.files[0];if(!f)return;
|
| 443 |
+
if(f.type==='application/pdf'){
|
| 444 |
+
// PDF handling
|
| 445 |
+
const r=new FileReader();
|
| 446 |
+
r.onload=async e=>{
|
| 447 |
+
S.pending={type:'pdf',data:e.target.result,name:f.name,size:fmtSz(f.size)};
|
| 448 |
+
document.getElementById('imgThumb').src='';
|
| 449 |
+
document.getElementById('imgThumb').style.display='none';
|
| 450 |
+
document.getElementById('imgName').textContent='📄 '+f.name;
|
| 451 |
+
document.getElementById('imgSize').textContent=fmtSz(f.size);
|
| 452 |
+
document.getElementById('imgPrev').classList.add('show');
|
| 453 |
+
// Extract text
|
| 454 |
+
try{
|
| 455 |
+
const res=await fetch('/api/extract-pdf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data:e.target.result})});
|
| 456 |
+
const data=await res.json();
|
| 457 |
+
if(data.text){S.pending.text=data.text;document.getElementById('imgSize').textContent=`${fmtSz(f.size)} · ${data.chars} chars extracted`;}
|
| 458 |
+
}catch(err){console.error('PDF extract failed:',err);}
|
| 459 |
+
};
|
| 460 |
+
r.readAsDataURL(f);
|
| 461 |
+
}else{
|
| 462 |
+
// Image handling
|
| 463 |
+
const r=new FileReader();
|
| 464 |
+
r.onload=e=>{
|
| 465 |
+
S.pending={type:'image',data:e.target.result,name:f.name,size:fmtSz(f.size)};
|
| 466 |
+
document.getElementById('imgThumb').src=e.target.result;
|
| 467 |
+
document.getElementById('imgThumb').style.display='block';
|
| 468 |
+
document.getElementById('imgName').textContent=f.name;
|
| 469 |
+
document.getElementById('imgSize').textContent=fmtSz(f.size);
|
| 470 |
+
document.getElementById('imgPrev').classList.add('show');
|
| 471 |
+
};
|
| 472 |
+
r.readAsDataURL(f);
|
| 473 |
+
}
|
| 474 |
+
}
|
| 475 |
+
function removeImg(){S.pending=null;document.getElementById('imgPrev').classList.remove('show');document.getElementById('fileInput').value='';document.getElementById('imgThumb').style.display='block';}
|
| 476 |
+
function fmtSz(b){return b<1048576?`${(b/1024).toFixed(0)} KB`:`${(b/1048576).toFixed(1)} MB`;}
|
| 477 |
+
|
| 478 |
+
// Vision sample
|
| 479 |
+
function trySampleVision(){ showToast('Vision not supported on 9B model'); return;
|
| 480 |
+
const sampleUrl='https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/rabbit.png';
|
| 481 |
+
S.pending={type:'image',data:sampleUrl,name:'sample_rabbit.png',size:'sample',isUrl:true};
|
| 482 |
+
document.getElementById('chatInput').value='Describe this image in detail. What story does it tell?';
|
| 483 |
+
sendMsg();
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
// Search
|
| 487 |
+
function toggleSearch(){
|
| 488 |
+
const bar=document.getElementById('searchBar');
|
| 489 |
+
bar.classList.toggle('show');
|
| 490 |
+
if(bar.classList.contains('show'))document.getElementById('searchInput').focus();
|
| 491 |
+
}
|
| 492 |
+
async function doSearch(){
|
| 493 |
+
const q=document.getElementById('searchInput').value.trim();
|
| 494 |
+
if(!q)return;
|
| 495 |
+
const res=document.getElementById('searchResults');
|
| 496 |
+
res.innerHTML='<div style="color:var(--ink4);font-size:10px;">🔍 Searching...</div>';
|
| 497 |
+
res.classList.add('show');
|
| 498 |
+
try{
|
| 499 |
+
const r=await fetch('/api/search',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({query:q})});
|
| 500 |
+
const data=await r.json();
|
| 501 |
+
if(data.error){res.innerHTML=`<div style="color:var(--rose);font-size:10px;">❌ ${data.error}</div>`;return;}
|
| 502 |
+
if(!data.results||data.results.length===0){res.innerHTML='<div style="color:var(--ink4);">No results found</div>';return;}
|
| 503 |
+
let html=data.results.map(r=>`<div class="sr-item"><div class="sr-title">${esc(r.title)}</div><div class="sr-desc">${esc(r.desc)}</div><a class="sr-url" href="${r.url}" target="_blank">${r.url}</a></div>`).join('');
|
| 504 |
+
html+=`<button style="margin-top:6px;padding:4px 10px;border-radius:6px;border:1px solid var(--line);background:var(--vg);color:var(--v);font-size:10px;font-weight:600;cursor:pointer;font-family:var(--fb);" onclick="injectSearch()">📎 Inject into chat</button>`;
|
| 505 |
+
res.innerHTML=html;
|
| 506 |
+
S.searchResults=data.results;
|
| 507 |
+
}catch(e){res.innerHTML=`<div style="color:var(--rose);">Error: ${e.message}</div>`;}
|
| 508 |
+
}
|
| 509 |
+
function injectSearch(){
|
| 510 |
+
if(!S.searchResults)return;
|
| 511 |
+
const ctx=S.searchResults.map(r=>`• ${r.title}: ${r.desc}`).join('\n');
|
| 512 |
+
const inp=document.getElementById('chatInput');
|
| 513 |
+
const q=document.getElementById('searchInput').value.trim();
|
| 514 |
+
inp.value=`Based on the following web search results for "${q}":\n\n${ctx}\n\nPlease answer: ${q}`;
|
| 515 |
+
document.getElementById('searchBar').classList.remove('show');
|
| 516 |
+
document.getElementById('searchResults').classList.remove('show');
|
| 517 |
+
autoGrow(inp);
|
| 518 |
+
showToast('Search results injected ✓');
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
// Send
|
| 522 |
+
function handleKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}}
|
| 523 |
+
function autoGrow(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,150)+'px';}
|
| 524 |
+
|
| 525 |
+
async function sendMsg(){
|
| 526 |
+
if(S.busy)return;
|
| 527 |
+
const inp=document.getElementById('chatInput');
|
| 528 |
+
const msg=inp.value.trim();if(!msg)return;
|
| 529 |
+
inp.value='';inp.style.height='auto';
|
| 530 |
+
document.getElementById('sendBtn').disabled=true;
|
| 531 |
+
S.busy=true;
|
| 532 |
+
hideWelcome();
|
| 533 |
+
|
| 534 |
+
const imgSnap=S.pending?{...S.pending}:null;
|
| 535 |
+
removeImg();
|
| 536 |
+
|
| 537 |
+
// PDF: prepend extracted text to message
|
| 538 |
+
let finalMsg=msg;
|
| 539 |
+
let imgData=null;
|
| 540 |
+
if(imgSnap&&imgSnap.type==='pdf'&&imgSnap.text){
|
| 541 |
+
finalMsg=`[Document: ${imgSnap.name}]\n\n${imgSnap.text}\n\n---\n\n${msg}`;
|
| 542 |
+
appendUser(msg,null,'📄 '+imgSnap.name);
|
| 543 |
+
}else if(imgSnap&&imgSnap.type==='image'){
|
| 544 |
+
imgData=imgSnap.isUrl?null:imgSnap.data; // URL images handled differently
|
| 545 |
+
appendUser(msg,imgSnap);
|
| 546 |
+
}else{
|
| 547 |
+
appendUser(msg);
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
const startTime=performance.now();
|
| 551 |
+
|
| 552 |
+
const payload=[
|
| 553 |
+
finalMsg,S.history,'⚡ Fast Mode (direct answer)',
|
| 554 |
+
imgData,
|
| 555 |
+
document.getElementById('sysPrompt').value.trim()||S.presets.general,
|
| 556 |
+
parseInt(document.getElementById('tokSl').value),
|
| 557 |
+
parseFloat(document.getElementById('tempSl').value),
|
| 558 |
+
parseFloat(document.getElementById('topPSl').value),
|
| 559 |
+
];
|
| 560 |
+
|
| 561 |
+
const botDiv=appendBot();
|
| 562 |
+
|
| 563 |
+
try{
|
| 564 |
+
const base=apiBase();
|
| 565 |
+
const postRes=await fetch(`${base}/gradio_api/call/chat`,{
|
| 566 |
+
method:'POST',headers:{'Content-Type':'application/json'},
|
| 567 |
+
body:JSON.stringify({data:payload}),
|
| 568 |
+
});
|
| 569 |
+
if(!postRes.ok)throw new Error(`HTTP ${postRes.status}: ${await postRes.text()}`);
|
| 570 |
+
const{event_id}=await postRes.json();
|
| 571 |
+
if(!event_id)throw new Error('No event_id from Gradio');
|
| 572 |
+
|
| 573 |
+
await new Promise((resolve,reject)=>{
|
| 574 |
+
const es=new EventSource(`${base}/gradio_api/call/chat/${event_id}`);
|
| 575 |
+
let raw='',done=false;
|
| 576 |
+
|
| 577 |
+
const finish=text=>{
|
| 578 |
+
if(done)return;done=true;es.close();
|
| 579 |
+
const elapsed=(performance.now()-startTime)/1000;
|
| 580 |
+
const tokens=Math.round(text.length/4);
|
| 581 |
+
const speed=tokens/elapsed;
|
| 582 |
+
renderBot(botDiv,text,elapsed,tokens,speed);
|
| 583 |
+
S.history.push([msg,stripThink(text)]);
|
| 584 |
+
S.msgCount+=2;S.totalTok+=tokens;
|
| 585 |
+
updateStats();
|
| 586 |
+
resolve();
|
| 587 |
+
};
|
| 588 |
+
|
| 589 |
+
es.addEventListener('generating',e=>{
|
| 590 |
+
try{const d=JSON.parse(e.data);if(Array.isArray(d)&&d[0]!=null){raw=String(d[0]);renderBot(botDiv,raw);}}catch{}
|
| 591 |
+
});
|
| 592 |
+
es.addEventListener('complete',e=>{try{const d=JSON.parse(e.data);finish(Array.isArray(d)?String(d[0]??raw):raw);}catch{finish(raw);}});
|
| 593 |
+
es.addEventListener('process_completed',e=>{try{const d=JSON.parse(e.data);finish(String(d?.output?.data?.[0]??raw));}catch{finish(raw);}});
|
| 594 |
+
es.onmessage=e=>{
|
| 595 |
+
if(done)return;
|
| 596 |
+
try{const d=JSON.parse(e.data);if(d&&d.msg==='process_completed')finish(String(d?.output?.data?.[0]??raw));else if(Array.isArray(d)&&d[0]!=null){raw=String(d[0]);renderBot(botDiv,raw);}}catch{}
|
| 597 |
+
};
|
| 598 |
+
es.onerror=()=>{if(done)return;if(raw&&raw.length>0){finish(raw);}else{done=true;es.close();reject(new Error('SSE stream ended'));}};
|
| 599 |
+
setTimeout(()=>{if(!done){done=true;es.close();reject(new Error('Request timed out'));}},300000);
|
| 600 |
+
});
|
| 601 |
+
}catch(err){
|
| 602 |
+
renderBot(botDiv,`**Error:** ${esc(err.message)}\n\n_Check if SGLang server is running._`);
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
S.busy=false;
|
| 606 |
+
document.getElementById('sendBtn').disabled=false;
|
| 607 |
+
scrollDown();
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
function sendEx(t){document.getElementById('chatInput').value=t;sendMsg();}
|
| 611 |
+
function hideWelcome(){const w=document.getElementById('welcome');if(w){w.style.transition='opacity .25s';w.style.opacity='0';setTimeout(()=>w.remove(),260);}}
|
| 612 |
+
|
| 613 |
+
function appendUser(text,img,pdfLabel){
|
| 614 |
+
const t=new Date().toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
|
| 615 |
+
let extra='';
|
| 616 |
+
if(img&&img.type==='image')extra=`<img src="${img.data}" style="max-width:180px;border-radius:9px;margin-bottom:6px;display:block;">`;
|
| 617 |
+
if(pdfLabel)extra=`<span class="pdf-badge">${esc(pdfLabel)}</span><br>`;
|
| 618 |
+
const el=document.createElement('div');
|
| 619 |
+
el.className='msg user';
|
| 620 |
+
el.innerHTML=`<div class="avatar">U</div><div class="body">${extra}<div class="bubble">${esc(text)}</div><div class="msg-time">${t}</div></div>`;
|
| 621 |
+
document.getElementById('msgs').appendChild(el);
|
| 622 |
+
scrollDown();
|
| 623 |
+
}
|
| 624 |
+
function appendBot(){
|
| 625 |
+
const el=document.createElement('div');
|
| 626 |
+
el.className='msg bot';
|
| 627 |
+
el.innerHTML='<div class="avatar">🧬</div><div class="body"><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div><div class="msg-time">—</div><div class="msg-badges"></div></div>';
|
| 628 |
+
document.getElementById('msgs').appendChild(el);
|
| 629 |
+
scrollDown();return el;
|
| 630 |
+
}
|
| 631 |
+
function isThinkLine(l){
|
| 632 |
+
l=l.trim();
|
| 633 |
+
if(!l)return true;
|
| 634 |
+
const starts=['The user','the user','This is','this is','I should','I need to','Let me','let me',
|
| 635 |
+
'My task','my task',"I'll ",'I will','Since ','since ','Now,','now,','So,','so,','First,','first,',
|
| 636 |
+
'Okay','okay','Alright','Hmm','Wait','Actually','The question','the question','The input',
|
| 637 |
+
'The request','The prompt','Thinking Process','**Thinking','Step ','step ','Approach:','Analysis:',
|
| 638 |
+
'Reasoning:','1. **','2. **','3. **','4. **','5. **'];
|
| 639 |
+
for(const s of starts)if(l.startsWith(s))return true;
|
| 640 |
+
if((l.startsWith('- ')||l.startsWith('* ')||l.startsWith('○ '))&&!/[\u1100-\uD7AF\uAC00-\uD7A3]/.test(l.slice(0,30)))return true;
|
| 641 |
+
return false;
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
function splitThinkAnswer(raw){
|
| 645 |
+
const lines=raw.split('\n');
|
| 646 |
+
for(let i=0;i<lines.length;i++){
|
| 647 |
+
const l=lines[i].trim();
|
| 648 |
+
if(!isThinkLine(lines[i])){
|
| 649 |
+
if(/[\uAC00-\uD7A3]/.test(l.slice(0,10))){return[lines.slice(0,i).join('\n').trim(),lines.slice(i).join('\n').trim()];}
|
| 650 |
+
if(i>2&&lines.slice(Math.max(0,i-2),i).every(x=>!x.trim())){return[lines.slice(0,i).join('\n').trim(),lines.slice(i).join('\n').trim()];}
|
| 651 |
+
}
|
| 652 |
+
}
|
| 653 |
+
return['',raw];
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
function renderBot(el,raw,elapsed,tokens,speed){
|
| 657 |
+
const t=new Date().toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
|
| 658 |
+
el.querySelector('.msg-time').textContent=t;
|
| 659 |
+
const b=el.querySelector('.bubble');
|
| 660 |
+
const badges=el.querySelector('.msg-badges');
|
| 661 |
+
let thinkHtml='',ans=raw,thinkChars=0;
|
| 662 |
+
const tOpen=raw.indexOf('<think>'),tClose=raw.indexOf('</think>');
|
| 663 |
+
const dOpen=raw.indexOf('<details>'),sClose=raw.indexOf('</summary>'),dClose=raw.indexOf('</details>');
|
| 664 |
+
|
| 665 |
+
if(tOpen>=0&&tClose>tOpen){
|
| 666 |
+
const chain=raw.slice(tOpen+7,tClose).trim();ans=raw.slice(tClose+8).trim();
|
| 667 |
+
thinkHtml=buildThink(chain);thinkChars=chain.length;
|
| 668 |
+
}else if(tOpen>=0){
|
| 669 |
+
const thinkLen=raw.length-tOpen-7;ans='';
|
| 670 |
+
thinkHtml=`<div class="think-blk"><div class="think-hdr" style="cursor:default;">🧠 REASONING (${thinkLen} CHARS) <span class="typing" style="display:inline-flex;gap:4px;vertical-align:middle;margin-left:6px;"><span></span><span></span><span></span></span></div></div>`;
|
| 671 |
+
}else if(dOpen>=0&&sClose>dOpen){
|
| 672 |
+
const body=dClose>sClose?raw.slice(sClose+10,dClose):raw.slice(sClose+10);
|
| 673 |
+
const chain=body.replace(/^> ?/gm,'').replace(/<[^>]+>/g,'').trim();
|
| 674 |
+
ans=dClose>0?raw.slice(dClose+10).trim():'';thinkHtml=buildThink(chain);thinkChars=chain.length;
|
| 675 |
+
}else{
|
| 676 |
+
const firstLine=raw.trim().split('\n')[0]||'';
|
| 677 |
+
if(isThinkLine(firstLine)&&raw.length>20){
|
| 678 |
+
const[think,answer]=splitThinkAnswer(raw);
|
| 679 |
+
if(think&&answer){thinkHtml=buildThink(think);ans=answer;thinkChars=think.length;}
|
| 680 |
+
else if(think&&!answer){
|
| 681 |
+
ans='';
|
| 682 |
+
thinkHtml=`<div class="think-blk"><div class="think-hdr" style="cursor:default;">🧠 REASONING (${raw.length} CHARS) <span class="typing" style="display:inline-flex;gap:4px;vertical-align:middle;margin-left:6px;"><span></span><span></span><span></span></span></div></div>`;
|
| 683 |
+
}
|
| 684 |
+
}
|
| 685 |
+
}
|
| 686 |
+
b.innerHTML=thinkHtml+md(ans);
|
| 687 |
+
|
| 688 |
+
// Speed + reasoning badges (only on final render)
|
| 689 |
+
if(elapsed&&badges){
|
| 690 |
+
let bh=`<span class="speed-badge">⚡ ${elapsed.toFixed(1)}s · ${tokens} tok · ${speed.toFixed(0)} tok/s</span>`;
|
| 691 |
+
if(thinkChars>0)bh+=`<span class="reason-badge">🧠 Deep Reasoning · ${thinkChars.toLocaleString()} chars analyzed</span>`;
|
| 692 |
+
badges.innerHTML=bh;
|
| 693 |
+
}
|
| 694 |
+
}
|
| 695 |
+
function buildThink(chain){
|
| 696 |
+
if(!chain)return'';
|
| 697 |
+
return`<div class="think-blk" onclick="this.classList.toggle('open')"><div class="think-hdr">🧠 Reasoning Chain (${chain.length} chars)</div><div class="think-body">${esc(chain)}</div></div>`;
|
| 698 |
+
}
|
| 699 |
+
function stripThink(t){
|
| 700 |
+
t=t.replace(/^<details>[\s\S]*?<\/details>\n\n/,'').replace(/^<think>[\s\S]*?<\/think>\s*/,'');
|
| 701 |
+
// Strip plain-text thinking
|
| 702 |
+
t=t.replace(/^🧠 Reasoning.*?\n?/,'');
|
| 703 |
+
return t;
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
// Marked.js + code copy
|
| 707 |
+
(function setupMarked(){
|
| 708 |
+
if(typeof marked==='undefined')return;
|
| 709 |
+
const renderer=new marked.Renderer();
|
| 710 |
+
renderer.code=(code,lang)=>{
|
| 711 |
+
const language=lang&&hljs.getLanguage(lang)?lang:'plaintext';
|
| 712 |
+
const codeText=typeof code==='object'?code.text:code;
|
| 713 |
+
const highlighted=hljs.highlight(codeText,{language}).value;
|
| 714 |
+
const langLabel=language!=='plaintext'?language:'code';
|
| 715 |
+
const escaped=codeText.replace(/'/g,"\\'").replace(/\n/g,"\\n");
|
| 716 |
+
return`<div class="code-wrap"><div class="code-header"><span class="code-lang-tag">${langLabel}</span><button class="copy-btn" onclick="copyCode(this,'${escaped}')">📋 Copy</button></div><pre style="border-radius:0 0 8px 8px;margin:0;border-top:none;"><code class="hljs language-${language}">${highlighted}</code></pre></div>`;
|
| 717 |
+
};
|
| 718 |
+
renderer.codespan=code=>`<code style="background:var(--fog);padding:1px 5px;border-radius:4px;font-family:var(--fm);font-size:11.5px">${code}</code>`;
|
| 719 |
+
renderer.link=(href,title,text)=>`<a href="${typeof href==='object'?href.href:href}" target="_blank" rel="noopener">${text}</a>`;
|
| 720 |
+
marked.setOptions({renderer,breaks:true,gfm:true});
|
| 721 |
+
})();
|
| 722 |
+
|
| 723 |
+
function copyCode(btn,code){
|
| 724 |
+
const text=code.replace(/\\n/g,'\n').replace(/\\'/g,"'");
|
| 725 |
+
navigator.clipboard.writeText(text).then(()=>{
|
| 726 |
+
btn.textContent='✅ Copied!';btn.classList.add('copied');
|
| 727 |
+
setTimeout(()=>{btn.textContent='📋 Copy';btn.classList.remove('copied');},2000);
|
| 728 |
+
});
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
function md(t){if(!t)return'';if(typeof marked!=='undefined'){try{return marked.parse(t);}catch(e){}}return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/`([^`]+)`/g,'<code>$1</code>').replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>');}
|
| 732 |
+
function esc(t){return t?t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):'';}
|
| 733 |
+
function scrollDown(){const m=document.getElementById('msgs');m.scrollTop=m.scrollHeight;}
|
| 734 |
+
function updateStats(){document.getElementById('stMsgs').textContent=S.msgCount;const t=S.totalTok;document.getElementById('stTok').textContent=t>999?(t/1000).toFixed(1)+'k':t;}
|
| 735 |
+
function clearChat(){
|
| 736 |
+
S.history=[];S.msgCount=0;S.totalTok=0;updateStats();
|
| 737 |
+
document.getElementById('msgs').innerHTML='<div class="welcome" id="welcome" style="display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;min-height:400px;text-align:center;padding:30px 36px;gap:20px;"><div class="welcome-icon" style="width:72px;height:72px;border-radius:20px;background:linear-gradient(135deg,#6d28d9,#a78bfa,#10b981);display:flex;align-items:center;justify-content:center;font-size:34px;box-shadow:0 8px 30px rgba(109,40,217,.25)">🧬</div><div class="welcome-title" style="font-family:var(--fd);font-size:34px">Hello, I\'m <em>Darwin</em></div></div>';
|
| 738 |
+
}
|
| 739 |
+
function showToast(msg,dur=2200){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur);}
|
| 740 |
+
|
| 741 |
+
// HF OAuth
|
| 742 |
+
async function checkAuth(){try{const res=await fetch('/oauth/user');if(res.ok){const u=await res.json();if(u.logged_in){showLoggedIn(u);return;}}}catch(e){}showLoggedOut();}
|
| 743 |
+
document.addEventListener('visibilitychange',()=>{if(!document.hidden)checkAuth();});
|
| 744 |
+
function showLoggedIn(u){
|
| 745 |
+
document.getElementById('loginBtn').style.display='none';
|
| 746 |
+
const ua=document.getElementById('userArea');ua.style.display='flex';
|
| 747 |
+
document.getElementById('userName').textContent=u.username||u.name||'User';
|
| 748 |
+
document.getElementById('userChip').href=u.profile||'https://huggingface.co';
|
| 749 |
+
const av=document.getElementById('userAvatar');
|
| 750 |
+
if(u.avatar){av.innerHTML='<img src="'+u.avatar+'" alt="av">';}
|
| 751 |
+
else{document.getElementById('userInitial').textContent=(u.username||'U')[0].toUpperCase();}
|
| 752 |
+
}
|
| 753 |
+
function showLoggedOut(){document.getElementById('loginBtn').style.display='inline-flex';document.getElementById('userArea').style.display='none';}
|
| 754 |
+
checkAuth();
|
| 755 |
+
</script>
|
| 756 |
+
</body>
|
| 757 |
+
</html>
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=5.0
|
| 2 |
+
huggingface_hub
|
| 3 |
+
httpx
|
| 4 |
+
Pillow
|
| 5 |
+
uvicorn
|
| 6 |
+
fastapi
|
| 7 |
+
requests
|
| 8 |
+
PyMuPDF
|