SeaWolf-AI commited on
Commit
0d358d3
·
verified ·
1 Parent(s): 5d5c4ab

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +307 -0
  2. index.html +565 -0
  3. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🧬 Darwin-35B-A3B-Opus — Demo Space
3
+ Single model · SGLang backend · Vision support
4
+ """
5
+ import sys
6
+ print(f"[BOOT] Python {sys.version}", flush=True)
7
+
8
+ import base64, os, re, json
9
+ from typing import Generator, Optional
10
+
11
+ try:
12
+ import gradio as gr
13
+ print(f"[BOOT] gradio {gr.__version__}", flush=True)
14
+ except ImportError as e:
15
+ print(f"[BOOT] FATAL: {e}", flush=True); sys.exit(1)
16
+
17
+ try:
18
+ import httpx, uvicorn, requests
19
+ from fastapi import FastAPI, Request
20
+ from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
21
+ print("[BOOT] All imports OK", flush=True)
22
+ except ImportError as e:
23
+ print(f"[BOOT] FATAL: {e}", flush=True); sys.exit(1)
24
+
25
+ # ══════════════════════════════════════════════════════════════════════════════
26
+ # 1. SGLANG BACKEND CONFIG
27
+ # ══════════════════════════════════════════════════════════════════════════════
28
+ SGLANG_BASE = os.getenv("SGLANG_BASE", "http://localhost:7947")
29
+ SGLANG_URL = f"{SGLANG_BASE}/v1/chat/completions"
30
+
31
+ MODEL_NAME = "Darwin-35B-A3B-Opus"
32
+ MODEL_CAP = {
33
+ "arch": "MoE", "active": "3B / 35B total",
34
+ "ctx": "262K", "thinking": True, "vision": True,
35
+ "max_tokens": 16384, "temp_max": 1.5,
36
+ }
37
+
38
+ PRESETS = {
39
+ "general": "You are Darwin-35B-A3B-Opus, a highly capable reasoning model created by VIDRAFT via evolutionary merge. Think step by step for complex questions.",
40
+ "code": "You are an expert software engineer. Write clean, efficient, well-commented code. Explain your approach before writing. Use modern best practices.",
41
+ "math": "You are a world-class mathematician. Break problems step-by-step. Show full working. Use LaTeX where helpful.",
42
+ "creative": "You are a brilliant creative writer. Be imaginative, vivid, and engaging. Adapt tone and style to the request.",
43
+ "translate": "You are a professional translator fluent in 201 languages. Provide accurate, natural-sounding translations with cultural context.",
44
+ "research": "You are a rigorous research analyst. Provide structured, well-reasoned analysis. Identify assumptions and acknowledge uncertainty.",
45
+ }
46
+
47
+ # ══════════════════════════════════════════════════════════════════════════════
48
+ # 2. THINKING MODE HELPERS
49
+ # ══════════════════════════════════════════════════════════════════════════════
50
+ def build_user_message(text: str, thinking: bool) -> str:
51
+ return ("/think\n" if thinking else "/no_think\n") + text
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 format_response(raw: str) -> str:
58
+ chain, answer = parse_think_blocks(raw)
59
+ if chain:
60
+ lines = chain.split("\n")
61
+ quoted = "\n".join(f"> {l}" for l in lines)
62
+ block = (
63
+ "<details>\n"
64
+ "<summary>🧠 Reasoning Chain — click to expand</summary>\n\n"
65
+ f"{quoted}\n\n"
66
+ "</details>\n\n"
67
+ )
68
+ return block + answer
69
+ return raw
70
+
71
+ # ══════════════════════════════════════════════════════════════════════════════
72
+ # 3. STREAMING BACKEND — SGLang OpenAI-compatible API
73
+ # ══════════════════════════════════════════════════════════════════════════════
74
+ def generate_reply(
75
+ message: str,
76
+ history: list,
77
+ thinking_mode: str,
78
+ image_input,
79
+ system_prompt: str,
80
+ max_new_tokens: int,
81
+ temperature: float,
82
+ top_p: float,
83
+ ) -> Generator[str, None, None]:
84
+
85
+ use_think = "Thinking" in thinking_mode
86
+ max_new_tokens = min(int(max_new_tokens), MODEL_CAP["max_tokens"])
87
+ temperature = min(float(temperature), MODEL_CAP["temp_max"])
88
+
89
+ messages: list[dict] = []
90
+ if system_prompt.strip():
91
+ messages.append({"role": "system", "content": system_prompt.strip()})
92
+
93
+ for turn in history:
94
+ if isinstance(turn, dict):
95
+ role = turn.get("role", "")
96
+ raw = turn.get("content") or ""
97
+ text = (" ".join(p.get("text","") for p in raw
98
+ if isinstance(p,dict) and p.get("type")=="text")
99
+ if isinstance(raw, list) else str(raw))
100
+ if role == "user":
101
+ messages.append({"role":"user","content":text})
102
+ elif role == "assistant":
103
+ _, clean = parse_think_blocks(text)
104
+ messages.append({"role":"assistant","content":clean})
105
+ else:
106
+ try:
107
+ u, a = (turn[0] or None), (turn[1] if len(turn)>1 else None)
108
+ except (IndexError, TypeError):
109
+ continue
110
+ def _txt(v):
111
+ if v is None: return None
112
+ if isinstance(v, list):
113
+ return " ".join(p.get("text","") for p in v
114
+ if isinstance(p,dict) and p.get("type")=="text")
115
+ return str(v)
116
+ if u := _txt(u): messages.append({"role":"user","content":u})
117
+ if a := _txt(a):
118
+ _, clean = parse_think_blocks(a)
119
+ messages.append({"role":"assistant","content":clean})
120
+
121
+ user_text = build_user_message(message, use_think)
122
+
123
+ # Vision: image input handling
124
+ if image_input and MODEL_CAP["vision"]:
125
+ import io
126
+ from PIL import Image as PILImage
127
+
128
+ if isinstance(image_input, str) and image_input.startswith("data:"):
129
+ header, b64_data = image_input.split(",", 1)
130
+ b64 = b64_data
131
+ else:
132
+ buf = io.BytesIO()
133
+ if not isinstance(image_input, PILImage.Image):
134
+ image_input = PILImage.fromarray(image_input)
135
+ image_input.save(buf, format="JPEG")
136
+ b64 = base64.b64encode(buf.getvalue()).decode()
137
+
138
+ content = [
139
+ {"type":"image_url","image_url":{"url":f"data:image/jpeg;base64,{b64}"}},
140
+ {"type":"text","text":user_text},
141
+ ]
142
+ else:
143
+ content = user_text
144
+ messages.append({"role":"user","content":content})
145
+
146
+ # Stream from SGLang
147
+ try:
148
+ resp = requests.post(SGLANG_URL, json={
149
+ "model": "default",
150
+ "messages": messages,
151
+ "max_tokens": max_new_tokens,
152
+ "temperature": temperature,
153
+ "top_p": float(top_p),
154
+ "stream": True,
155
+ }, stream=True, timeout=600)
156
+
157
+ raw = ""
158
+ for line in resp.iter_lines(decode_unicode=True):
159
+ if not line or not line.startswith("data: "):
160
+ continue
161
+ payload = line[6:]
162
+ if payload.strip() == "[DONE]":
163
+ break
164
+ try:
165
+ chunk = json.loads(payload)
166
+ delta = chunk.get("choices", [{}])[0].get("delta", {})
167
+ token = delta.get("content", "")
168
+ if token:
169
+ raw += token
170
+ yield format_response(raw)
171
+ except (json.JSONDecodeError, IndexError, KeyError):
172
+ continue
173
+
174
+ if raw:
175
+ yield format_response(raw)
176
+
177
+ except requests.exceptions.ConnectionError:
178
+ yield "**❌ SGLang 서버 연결 실패.** `localhost:7947`에 서버가 실행 중인지 확인하세요."
179
+ except Exception as exc:
180
+ yield f"**Error:** `{exc}`"
181
+
182
+
183
+ # ══════════════════════════════════════════════════════════════════════════════
184
+ # 4. GRADIO BLOCKS (hidden — serves API for frontend)
185
+ # ══════════════════════════════════════════════════════════════════════════════
186
+ with gr.Blocks(title="Darwin-35B-A3B-Opus") as gradio_demo:
187
+ thinking_toggle = gr.Radio(
188
+ choices=["⚡ Fast Mode (direct answer)",
189
+ "🧠 Thinking Mode (chain-of-thought reasoning)"],
190
+ value="⚡ Fast Mode (direct answer)",
191
+ visible=False,
192
+ )
193
+ image_input = gr.Textbox(value="", visible=False)
194
+ system_prompt = gr.Textbox(value=PRESETS["general"], visible=False)
195
+ max_new_tokens = gr.Slider(minimum=64, maximum=16384, value=4096, visible=False)
196
+ temperature = gr.Slider(minimum=0.0, maximum=1.5, value=0.6, visible=False)
197
+ top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.9, visible=False)
198
+
199
+ gr.ChatInterface(
200
+ fn=generate_reply,
201
+ api_name="chat",
202
+ additional_inputs=[
203
+ thinking_toggle, image_input,
204
+ system_prompt, max_new_tokens, temperature, top_p,
205
+ ],
206
+ )
207
+
208
+ # ══════════════════════════════════════════════════════════════════════════════
209
+ # 5. FASTAPI — index.html + HF OAuth + Gradio API
210
+ # ════════════════════════════════════��═════════════════════════════════════════
211
+ import pathlib, secrets
212
+
213
+ fapp = FastAPI()
214
+ SESSIONS: dict[str, dict] = {}
215
+ HTML = pathlib.Path(__file__).parent / "index.html"
216
+
217
+ CLIENT_ID = os.getenv("OAUTH_CLIENT_ID", "")
218
+ CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET", "")
219
+ SPACE_HOST = os.getenv("SPACE_HOST", "localhost:7860")
220
+ REDIRECT_URI = f"https://{SPACE_HOST}/login/callback"
221
+
222
+ print(f"[OAuth] CLIENT_ID set: {bool(CLIENT_ID)}")
223
+ print(f"[OAuth] SPACE_HOST: {SPACE_HOST}")
224
+ HF_AUTH_URL = "https://huggingface.co/oauth/authorize"
225
+ HF_TOKEN_URL = "https://huggingface.co/oauth/token"
226
+ HF_USER_URL = "https://huggingface.co/oauth/userinfo"
227
+ SCOPES = os.getenv("OAUTH_SCOPES", "openid profile")
228
+
229
+ from urllib.parse import urlencode
230
+
231
+ def _sid(req: Request) -> Optional[str]:
232
+ return req.cookies.get("mc_session")
233
+
234
+ def _user(req: Request) -> Optional[dict]:
235
+ sid = _sid(req)
236
+ return SESSIONS.get(sid) if sid else None
237
+
238
+ @fapp.get("/")
239
+ async def root(request: Request):
240
+ html = HTML.read_text(encoding="utf-8") if HTML.exists() else "<h2>index.html missing</h2>"
241
+ return HTMLResponse(html)
242
+
243
+ @fapp.get("/oauth/user")
244
+ async def oauth_user(request: Request):
245
+ u = _user(request)
246
+ return JSONResponse(u) if u else JSONResponse({"logged_in": False}, status_code=401)
247
+
248
+ @fapp.get("/oauth/login")
249
+ async def oauth_login(request: Request):
250
+ if not CLIENT_ID:
251
+ return RedirectResponse("/?oauth_error=not_configured")
252
+ state = secrets.token_urlsafe(16)
253
+ params = {"response_type":"code","client_id":CLIENT_ID,"redirect_uri":REDIRECT_URI,"scope":SCOPES,"state":state}
254
+ return RedirectResponse(f"{HF_AUTH_URL}?{urlencode(params)}", status_code=302)
255
+
256
+ @fapp.get("/login/callback")
257
+ async def oauth_callback(code: str = "", error: str = "", state: str = ""):
258
+ if error or not code:
259
+ return RedirectResponse("/?auth_error=1")
260
+ basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
261
+ async with httpx.AsyncClient() as client:
262
+ tok = await client.post(HF_TOKEN_URL, data={"grant_type":"authorization_code","code":code,"redirect_uri":REDIRECT_URI},
263
+ headers={"Accept":"application/json","Authorization":f"Basic {basic}"})
264
+ if tok.status_code != 200:
265
+ return RedirectResponse("/?auth_error=1")
266
+ access_token = tok.json().get("access_token", "")
267
+ if not access_token:
268
+ return RedirectResponse("/?auth_error=1")
269
+ uinfo = await client.get(HF_USER_URL, headers={"Authorization":f"Bearer {access_token}"})
270
+ if uinfo.status_code != 200:
271
+ return RedirectResponse("/?auth_error=1")
272
+ user = uinfo.json()
273
+
274
+ sid = secrets.token_urlsafe(32)
275
+ SESSIONS[sid] = {
276
+ "logged_in": True,
277
+ "username": user.get("preferred_username", user.get("name", "User")),
278
+ "name": user.get("name", ""),
279
+ "avatar": user.get("picture", ""),
280
+ "profile": f"https://huggingface.co/{user.get('preferred_username', '')}",
281
+ }
282
+ resp = RedirectResponse("/")
283
+ resp.set_cookie("mc_session", sid, httponly=True, samesite="lax", secure=True, max_age=60*60*24*7)
284
+ return resp
285
+
286
+ @fapp.get("/oauth/logout")
287
+ async def oauth_logout(request: Request):
288
+ sid = _sid(request)
289
+ if sid and sid in SESSIONS: del SESSIONS[sid]
290
+ resp = RedirectResponse("/")
291
+ resp.delete_cookie("mc_session")
292
+ return resp
293
+
294
+ @fapp.get("/health")
295
+ async def health():
296
+ # Check SGLang
297
+ try:
298
+ r = requests.get(f"{SGLANG_BASE}/v1/models", timeout=3)
299
+ return {"status":"ok","sglang":"connected"}
300
+ except:
301
+ return {"status":"ok","sglang":"disconnected"}
302
+
303
+ app = gr.mount_gradio_app(fapp, gradio_demo, path="/gradio")
304
+
305
+ if __name__ == "__main__":
306
+ print(f"[BOOT] Darwin-35B-A3B-Opus Demo · SGLang: {SGLANG_URL}", flush=True)
307
+ uvicorn.run(app, host="0.0.0.0", port=7860)
index.html ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-35B-A3B-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 26px;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:74%;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);}
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;}
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
+ .typing{display:flex;align-items:center;gap:5px;padding:12px 15px;}
168
+ .typing span{width:6px;height:6px;border-radius:50%;background:var(--v2);opacity:.4;animation:bounce .8s ease-in-out infinite;}
169
+ .typing span:nth-child(2){animation-delay:.15s;}
170
+ .typing span:nth-child(3){animation-delay:.3s;}
171
+ @keyframes bounce{0%,100%{transform:translateY(0);opacity:.4;}50%{transform:translateY(-5px);opacity:1;}}
172
+
173
+ .img-prev{display:none;padding:8px 26px 0;}
174
+ .img-prev.show{display:block;}
175
+ .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;}
176
+ .img-th{width:44px;height:44px;object-fit:cover;border-radius:5px;}
177
+ .img-nm{font-size:10px;font-weight:600;color:var(--ink2);}
178
+ .img-sz{font-size:9px;color:var(--ink4);}
179
+ .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;}
180
+ .img-rm:hover{background:rgba(225,29,72,.1);color:var(--rose);}
181
+
182
+ .inputbar{padding:14px 18px 16px;border-top:1px solid var(--line);background:rgba(255,255,255,.88);backdrop-filter:blur(20px);flex-shrink:0;}
183
+ .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);}
184
+ .input-wrap:focus-within{border-color:rgba(109,40,217,.32);box-shadow:var(--sh2),0 0 0 4px rgba(109,40,217,.06);}
185
+ .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;}
186
+ .chat-ta::placeholder{color:var(--ink4);}
187
+ .input-acts{display:flex;align-items:center;gap:5px;padding-bottom:3px;}
188
+ .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;}
189
+ .icon-btn:hover{background:rgba(109,40,217,.08);border-color:rgba(109,40,217,.25);color:var(--v);}
190
+ .file-btn{position:relative;}
191
+ .file-btn input{position:absolute;inset:0;opacity:0;cursor:pointer;font-size:0;}
192
+ .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);}
193
+ .send-btn:hover{transform:translateY(-2px) scale(1.06);box-shadow:0 6px 20px rgba(109,40,217,.38);}
194
+ .send-btn:active{transform:scale(.96);}
195
+ .send-btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none;}
196
+ .input-hint{font-size:10px;color:var(--ink4);padding:5px 3px 0;display:flex;align-items:center;gap:8px;}
197
+ .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);}
198
+ .model-hint{margin-left:auto;font-weight:700;color:var(--v);font-size:10px;}
199
+
200
+ .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;}
201
+ .toast.show{transform:translateY(0);opacity:1;}
202
+
203
+ @media(max-width:768px){
204
+ .shell{grid-template-columns:1fr;}
205
+ .sidebar{display:none;}
206
+ }
207
+ </style>
208
+ </head>
209
+ <body>
210
+
211
+ <div class="bg">
212
+ <div class="orb orb1"></div><div class="orb orb2"></div><div class="orb orb3"></div>
213
+ <div class="bg-grid"></div>
214
+ </div>
215
+
216
+ <div class="shell">
217
+ <!-- SIDEBAR -->
218
+ <aside class="sidebar">
219
+ <div class="logo-area">
220
+ <div class="logo-row">
221
+ <div class="logo-icon">🧬</div>
222
+ <div class="logo-text">
223
+ <div class="logo-name"><em>Darwin</em>-35B</div>
224
+ <div class="logo-sub">Evolutionary Merge</div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+
229
+ <!-- Single Model Card -->
230
+ <div class="model-card">
231
+ <div class="mc-top">
232
+ <span class="mc-name">Darwin-35B-A3B-Opus</span>
233
+ <span class="mc-arch">MoE 3B/35B</span>
234
+ </div>
235
+ <div class="mc-stats">
236
+ <span class="mc-stat mc-hl">GPQA 90.0%</span>
237
+ <span class="mc-stat mc-ok">MMMLU 85%</span>
238
+ <span class="mc-stat mc-ok">👁️ Vision</span>
239
+ <span class="mc-stat mc-ok">147.8 t/s</span>
240
+ </div>
241
+ <div class="mc-desc">MRI-guided evolutionary merge · Surpassed both parents · 201 languages · 262K context</div>
242
+ </div>
243
+
244
+ <!-- Settings -->
245
+ <div class="settings">
246
+ <div class="section-lbl">Settings</div>
247
+
248
+ <div class="toggle-row">
249
+ <div class="toggle-info"><div class="tl">🧠 Thinking Mode</div><div class="ts">Chain-of-thought reasoning</div></div>
250
+ <label class="toggle"><input type="checkbox" id="thinkToggle"><span class="t-track"></span><span class="t-thumb"></span></label>
251
+ </div>
252
+
253
+ <div class="toggle-row">
254
+ <div class="toggle-info"><div class="tl">👁️ Vision</div><div class="ts">Image understanding</div></div>
255
+ <label class="toggle"><input type="checkbox" id="visionToggle" checked><span class="t-track"></span><span class="t-thumb"></span></label>
256
+ </div>
257
+
258
+ <div class="field">
259
+ <span class="field-lbl">System Prompt</span>
260
+ <div class="presets">
261
+ <span class="chip" onclick="setPreset('general')">General</span>
262
+ <span class="chip" onclick="setPreset('code')">Code</span>
263
+ <span class="chip" onclick="setPreset('math')">Math</span>
264
+ <span class="chip" onclick="setPreset('creative')">Creative</span>
265
+ <span class="chip" onclick="setPreset('translate')">Translate</span>
266
+ <span class="chip" onclick="setPreset('research')">Research</span>
267
+ </div>
268
+ <textarea id="sysPrompt">You are Darwin-35B-A3B-Opus, a highly capable reasoning model created by VIDRAFT via evolutionary merge. Think step by step for complex questions.</textarea>
269
+ </div>
270
+
271
+ <div class="sl-wrap">
272
+ <div class="sl-meta"><span class="sl-title">Max Tokens</span><span class="sl-val" id="tokVal">4096</span></div>
273
+ <input type="range" id="tokSl" min="64" max="16384" value="4096" step="64" oninput="document.getElementById('tokVal').textContent=this.value">
274
+ </div>
275
+
276
+ <div class="sl-wrap">
277
+ <div class="sl-meta"><span class="sl-title">Temperature</span><span class="sl-val" id="tempVal">0.60</span></div>
278
+ <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)">
279
+ </div>
280
+
281
+ <div class="sl-wrap">
282
+ <div class="sl-meta"><span class="sl-title">Top-P</span><span class="sl-val" id="topPVal">0.90</span></div>
283
+ <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)">
284
+ </div>
285
+
286
+ <button class="clear-btn" onclick="clearChat()">🗑️ Clear conversation</button>
287
+ </div>
288
+ </aside>
289
+
290
+ <!-- MAIN -->
291
+ <main class="chat-main">
292
+ <header class="chat-hdr">
293
+ <div class="hdr-left">
294
+ <div class="model-pill"><div class="dot"></div><span class="model-name" id="hdrModel">Darwin-35B-A3B-Opus</span></div>
295
+ <div class="mode-tag" id="modeTag">⚡ Fast Mode</div>
296
+ </div>
297
+ <div style="display:flex;align-items:center;gap:12px;">
298
+ <a class="hf-login-btn" id="loginBtn" href="/oauth/login">
299
+ <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>
300
+ Sign in with HF
301
+ </a>
302
+ <div id="userArea" style="display:none;align-items:center;gap:8px;">
303
+ <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>
304
+ <button class="logout-btn" onclick="location.href='/oauth/logout'">Log out</button>
305
+ </div>
306
+ <div class="hdr-stats">
307
+ <div class="hstat"><div class="hstat-n" id="stMsgs">0</div><div class="hstat-l">Messages</div></div>
308
+ <div class="hstat"><div class="hstat-n" id="stTok">0</div><div class="hstat-l">Tokens</div></div>
309
+ </div>
310
+ </div>
311
+ </header>
312
+
313
+ <div class="img-prev" id="imgPrev">
314
+ <div class="img-tw">
315
+ <img class="img-th" id="imgThumb" src="">
316
+ <div><div class="img-nm" id="imgName">image.jpg</div><div class="img-sz" id="imgSize">—</div></div>
317
+ <button class="img-rm" onclick="removeImg()">✕</button>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="messages" id="msgs">
322
+ <div class="welcome" id="welcome">
323
+ <div class="welcome-icon">🧬</div>
324
+ <div class="welcome-title">Hello, I'm <em>Darwin</em></div>
325
+ <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>
326
+ <div class="welcome-badges">
327
+ <span class="welcome-badge wb-purple">GPQA 90.0%</span>
328
+ <span class="welcome-badge wb-green">MMMLU 85%</span>
329
+ <span class="welcome-badge wb-amber">147.8 tok/s</span>
330
+ </div>
331
+ <div class="ex-grid">
332
+ <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>
333
+ <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>
334
+ <div class="ex-card" onclick="sendEx('Prove that √2 is irrational using proof by contradiction. Show every step.')"><span class="ex-icon">🔢</span><div class="ex-title">Math Reasoning</div><div class="ex-desc">Deep chain-of-thought</div></div>
335
+ <div class="ex-card" onclick="sendEx('한국의 K-pop이 세계적으로 성공한 이유를 문화적, 경제적 관점에서 분석해주��요.')"><span class="ex-icon">🌐</span><div class="ex-title">201 Languages</div><div class="ex-desc">Korean, Japanese, Arabic…</div></div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <div class="inputbar">
341
+ <div class="input-wrap">
342
+ <textarea class="chat-ta" id="chatInput" placeholder="Message Darwin…" rows="1" onkeydown="handleKey(event)" oninput="autoGrow(this)"></textarea>
343
+ <div class="input-acts">
344
+ <label class="icon-btn file-btn" id="fileLabel" title="Upload image">🖼<input type="file" accept="image/*" onchange="handleImg(this)" id="fileInput"></label>
345
+ <button class="icon-btn" onclick="clearChat()" title="New chat" style="font-size:16px;">🧬</button>
346
+ <button class="send-btn" id="sendBtn" onclick="sendMsg()">➤</button>
347
+ </div>
348
+ </div>
349
+ <div class="input-hint">
350
+ <span><span class="kbd">Enter</span> send</span>
351
+ <span><span class="kbd">Shift+Enter</span> new line</span>
352
+ <span class="model-hint">35B MoE · 3B active · 262K ctx</span>
353
+ </div>
354
+ </div>
355
+ </main>
356
+ </div>
357
+
358
+ <div class="toast" id="toast"></div>
359
+
360
+ <script>
361
+ const S={thinking:false,vision:true,history:[],msgCount:0,totalTok:0,pending:null,busy:false,
362
+ presets:{
363
+ general:'You are Darwin-35B-A3B-Opus, a highly capable reasoning model created by VIDRAFT via evolutionary merge. Think step by step for complex questions.',
364
+ code:'You are an expert software engineer. Write clean, efficient, well-commented code. Explain your approach before writing.',
365
+ math:'You are a world-class mathematician. Break problems step-by-step. Show full working.',
366
+ creative:'You are a brilliant creative writer. Be imaginative, vivid, and engaging.',
367
+ translate:'You are a professional translator fluent in 201 languages. Provide accurate translations with cultural context.',
368
+ research:'You are a rigorous research analyst. Provide structured, well-reasoned analysis.',
369
+ }
370
+ };
371
+
372
+ function apiBase(){return'/gradio';}
373
+
374
+ // Toggles
375
+ document.getElementById('thinkToggle').addEventListener('change',function(){
376
+ S.thinking=this.checked;
377
+ const t=document.getElementById('modeTag');
378
+ t.textContent=this.checked?'🧠 Thinking Mode':'⚡ Fast Mode';
379
+ t.className='mode-tag'+(this.checked?' thinking':'');
380
+ });
381
+ document.getElementById('visionToggle').addEventListener('change',function(){
382
+ S.vision=this.checked;
383
+ const l=document.getElementById('fileLabel');
384
+ l.style.opacity=this.checked?'1':'.35';
385
+ l.style.pointerEvents=this.checked?'auto':'none';
386
+ if(!this.checked)removeImg();
387
+ });
388
+
389
+ function setPreset(k){document.getElementById('sysPrompt').value=S.presets[k];showToast('Preset applied ✓');}
390
+
391
+ // Image
392
+ function handleImg(input){
393
+ const f=input.files[0];if(!f)return;
394
+ const r=new FileReader();
395
+ r.onload=e=>{
396
+ S.pending={data:e.target.result,name:f.name,size:fmtSz(f.size)};
397
+ document.getElementById('imgThumb').src=e.target.result;
398
+ document.getElementById('imgName').textContent=f.name;
399
+ document.getElementById('imgSize').textContent=fmtSz(f.size);
400
+ document.getElementById('imgPrev').classList.add('show');
401
+ };
402
+ r.readAsDataURL(f);
403
+ }
404
+ function removeImg(){S.pending=null;document.getElementById('imgPrev').classList.remove('show');document.getElementById('fileInput').value='';}
405
+ function fmtSz(b){return b<1048576?`${(b/1024).toFixed(0)} KB`:`${(b/1048576).toFixed(1)} MB`;}
406
+
407
+ // Send
408
+ function handleKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}}
409
+ function autoGrow(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,150)+'px';}
410
+
411
+ async function sendMsg(){
412
+ if(S.busy)return;
413
+ const inp=document.getElementById('chatInput');
414
+ const msg=inp.value.trim();if(!msg)return;
415
+ inp.value='';inp.style.height='auto';
416
+ document.getElementById('sendBtn').disabled=true;
417
+ S.busy=true;
418
+ hideWelcome();
419
+
420
+ const imgSnap=S.pending?{...S.pending}:null;
421
+ removeImg();
422
+ appendUser(msg,imgSnap);
423
+
424
+ const thinkStr=S.thinking?'🧠 Thinking Mode (chain-of-thought reasoning)':'⚡ Fast Mode (direct answer)';
425
+
426
+ const payload=[
427
+ msg,S.history,thinkStr,
428
+ imgSnap?imgSnap.data:null,
429
+ document.getElementById('sysPrompt').value.trim()||S.presets.general,
430
+ parseInt(document.getElementById('tokSl').value),
431
+ parseFloat(document.getElementById('tempSl').value),
432
+ parseFloat(document.getElementById('topPSl').value),
433
+ ];
434
+
435
+ const botDiv=appendBot();
436
+
437
+ try{
438
+ const base=apiBase();
439
+ const postRes=await fetch(`${base}/gradio_api/call/chat`,{
440
+ method:'POST',headers:{'Content-Type':'application/json'},
441
+ body:JSON.stringify({data:payload}),
442
+ });
443
+ if(!postRes.ok)throw new Error(`HTTP ${postRes.status}: ${await postRes.text()}`);
444
+ const{event_id}=await postRes.json();
445
+ if(!event_id)throw new Error('No event_id from Gradio');
446
+
447
+ await new Promise((resolve,reject)=>{
448
+ const es=new EventSource(`${base}/gradio_api/call/chat/${event_id}`);
449
+ let raw='',done=false;
450
+
451
+ const finish=text=>{
452
+ if(done)return;done=true;es.close();
453
+ renderBot(botDiv,text);
454
+ S.history.push([msg,stripThink(text)]);
455
+ S.msgCount+=2;S.totalTok+=Math.round(text.length/4);
456
+ updateStats();
457
+ resolve();
458
+ };
459
+
460
+ es.addEventListener('generating',e=>{
461
+ try{const d=JSON.parse(e.data);if(Array.isArray(d)&&d[0]!=null){raw=String(d[0]);renderBot(botDiv,raw);}}catch{}
462
+ });
463
+ es.addEventListener('complete',e=>{try{const d=JSON.parse(e.data);finish(Array.isArray(d)?String(d[0]??raw):raw);}catch{finish(raw);}});
464
+ es.addEventListener('process_completed',e=>{try{const d=JSON.parse(e.data);finish(String(d?.output?.data?.[0]??raw));}catch{finish(raw);}});
465
+ es.onmessage=e=>{
466
+ if(done)return;
467
+ 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{}
468
+ };
469
+ es.onerror=()=>{if(done)return;if(raw&&raw.length>0){finish(raw);}else{done=true;es.close();reject(new Error('SSE stream ended'));}};
470
+ setTimeout(()=>{if(!done){done=true;es.close();reject(new Error('Request timed out'));}},300000);
471
+ });
472
+ }catch(err){
473
+ renderBot(botDiv,`**Error:** ${esc(err.message)}\n\n_Check if SGLang server is running._`);
474
+ }
475
+
476
+ S.busy=false;
477
+ document.getElementById('sendBtn').disabled=false;
478
+ scrollDown();
479
+ }
480
+
481
+ function sendEx(t){document.getElementById('chatInput').value=t;sendMsg();}
482
+ function hideWelcome(){const w=document.getElementById('welcome');if(w){w.style.transition='opacity .25s';w.style.opacity='0';setTimeout(()=>w.remove(),260);}}
483
+
484
+ function appendUser(text,img){
485
+ const t=new Date().toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
486
+ const ih=img?`<img src="${img.data}" style="max-width:180px;border-radius:9px;margin-bottom:6px;display:block;">`:'';
487
+ const el=document.createElement('div');
488
+ el.className='msg user';
489
+ el.innerHTML=`<div class="avatar">U</div><div class="body">${ih}<div class="bubble">${esc(text)}</div><div class="msg-time">${t}</div></div>`;
490
+ document.getElementById('msgs').appendChild(el);
491
+ scrollDown();
492
+ }
493
+ function appendBot(){
494
+ const el=document.createElement('div');
495
+ el.className='msg bot';
496
+ 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>';
497
+ document.getElementById('msgs').appendChild(el);
498
+ scrollDown();return el;
499
+ }
500
+ function renderBot(el,raw){
501
+ const t=new Date().toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
502
+ el.querySelector('.msg-time').textContent=t;
503
+ const b=el.querySelector('.bubble');
504
+ let thinkHtml='',ans=raw;
505
+ const tOpen=raw.indexOf('<think>'),tClose=raw.indexOf('</think>');
506
+ const dOpen=raw.indexOf('<details>'),sClose=raw.indexOf('</summary>'),dClose=raw.indexOf('</details>');
507
+ if(tOpen>=0&&tClose>tOpen){
508
+ const chain=raw.slice(tOpen+7,tClose).trim();ans=raw.slice(tClose+8).trim();thinkHtml=buildThink(chain);
509
+ }else if(tOpen>=0){
510
+ ans='';thinkHtml=buildThink(raw.slice(tOpen+7).trim()+' …');
511
+ }else if(dOpen>=0&&sClose>dOpen){
512
+ const body=dClose>sClose?raw.slice(sClose+10,dClose):raw.slice(sClose+10);
513
+ const chain=body.replace(/^> ?/gm,'').replace(/<[^>]+>/g,'').trim();
514
+ ans=dClose>0?raw.slice(dClose+10).trim():'';thinkHtml=buildThink(chain);
515
+ }
516
+ b.innerHTML=thinkHtml+md(ans);
517
+ }
518
+ function buildThink(chain){
519
+ if(!chain)return'';
520
+ 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>`;
521
+ }
522
+ function stripThink(t){return t.replace(/^<details>[\s\S]*?<\/details>\n\n/,'').replace(/^<think>[\s\S]*?<\/think>\s*/,'');}
523
+
524
+ // Marked.js
525
+ (function setupMarked(){
526
+ if(typeof marked==='undefined')return;
527
+ const renderer=new marked.Renderer();
528
+ renderer.code=(code,lang)=>{
529
+ const language=lang&&hljs.getLanguage(lang)?lang:'plaintext';
530
+ const highlighted=hljs.highlight(typeof code==='object'?code.text:code,{language}).value;
531
+ const langLabel=language!=='plaintext'?`<span style="display:block;padding:6px 14px;font-size:11px;font-weight:600;color:#8b949e;border-bottom:1px solid rgba(255,255,255,.06)">${language}</span>`:'';
532
+ return`<div style="position:relative;margin:10px 0;border-radius:10px;overflow:hidden;background:#0d1117;border:1px solid rgba(255,255,255,.08)">${langLabel}<pre><code class="hljs language-${language}">${highlighted}</code></pre></div>`;
533
+ };
534
+ renderer.codespan=code=>`<code style="background:var(--fog);padding:1px 5px;border-radius:4px;font-family:var(--fm);font-size:11.5px">${code}</code>`;
535
+ renderer.link=(href,title,text)=>`<a href="${typeof href==='object'?href.href:href}" target="_blank" rel="noopener">${text}</a>`;
536
+ marked.setOptions({renderer,breaks:true,gfm:true});
537
+ })();
538
+
539
+ function md(t){if(!t)return'';if(typeof marked!=='undefined'){try{return marked.parse(t);}catch(e){}}return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/`([^`]+)`/g,'<code>$1</code>').replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>');}
540
+ function esc(t){return t?t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'):'';}
541
+ function scrollDown(){const m=document.getElementById('msgs');m.scrollTop=m.scrollHeight;}
542
+ function updateStats(){document.getElementById('stMsgs').textContent=S.msgCount;const t=S.totalTok;document.getElementById('stTok').textContent=t>999?(t/1000).toFixed(1)+'k':t;}
543
+ function clearChat(){
544
+ S.history=[];S.msgCount=0;S.totalTok=0;updateStats();
545
+ 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>';
546
+ }
547
+ function showToast(msg,dur=2200){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur);}
548
+
549
+ // HF OAuth
550
+ 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();}
551
+ document.addEventListener('visibilitychange',()=>{if(!document.hidden)checkAuth();});
552
+ function showLoggedIn(u){
553
+ document.getElementById('loginBtn').style.display='none';
554
+ const ua=document.getElementById('userArea');ua.style.display='flex';
555
+ document.getElementById('userName').textContent=u.username||u.name||'User';
556
+ document.getElementById('userChip').href=u.profile||'https://huggingface.co';
557
+ const av=document.getElementById('userAvatar');
558
+ if(u.avatar){av.innerHTML='<img src="'+u.avatar+'" alt="av">';}
559
+ else{document.getElementById('userInitial').textContent=(u.username||'U')[0].toUpperCase();}
560
+ }
561
+ function showLoggedOut(){document.getElementById('loginBtn').style.display='inline-flex';document.getElementById('userArea').style.display='none';}
562
+ checkAuth();
563
+ </script>
564
+ </body>
565
+ </html>
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=5.0
2
+ huggingface_hub
3
+ httpx
4
+ Pillow
5
+ uvicorn
6
+ fastapi
7
+ requests