AIBRUH commited on
Commit
ffa2840
·
verified ·
1 Parent(s): 6497908

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +751 -0
app.py ADDED
@@ -0,0 +1,751 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NOBILITY v1 — AIBRUH/nobility-v1-dev
3
+ Beryl AI Labs | HuggingFace ZeroGPU Space
4
+
5
+ Amanda's first breath.
6
+ Validates Layers 1-3 of the Nobility v1 pipeline live:
7
+ Layer 1 → Qwen3-Omni (Brain) via NIM
8
+ Layer 2 → Qwen3 Director (Shot Planner) via NIM
9
+ Layer 3 → Qwen3-VL Drift Scorer (Identity Lock) via NIM
10
+
11
+ No GPU required for these layers. ZeroGPU handles identity embedding only.
12
+ Layer 4 (Wan2.2-S2V generation) activates after Oracle distillation.
13
+ """
14
+
15
+ import asyncio
16
+ import base64
17
+ import json
18
+ import os
19
+ import time
20
+ import spaces
21
+ import gradio as gr
22
+ import torch
23
+ import numpy as np
24
+ from PIL import Image
25
+ from openai import AsyncOpenAI
26
+
27
+ # ── HF Kernels — 1.7-2.5x speedup on every GPU forward pass ─────────────────
28
+ # Loaded once at startup. Hub auto-detects ZeroGPU H200 and serves right binary.
29
+ try:
30
+ from kernels import get_kernel
31
+ HF_KERNELS_AVAILABLE = True
32
+ print("[Nobility v1] HF Kernels loaded — RMSNorm + RoPE optimized")
33
+ except ImportError:
34
+ HF_KERNELS_AVAILABLE = False
35
+ print("[Nobility v1] HF Kernels not available — running standard ops")
36
+
37
+ # ── Constants ────────────────────────────────────────────────────────────────
38
+
39
+ NIM_BASE = "https://integrate.api.nvidia.com/v1"
40
+ VERSION = "Nobility v1 — Dev Build"
41
+
42
+ DIRECTOR_SYSTEM = """You are the Amanda shot director for Nobility v1 avatar pipeline.
43
+ Output ONLY valid JSON. No prose. No explanation.
44
+
45
+ Schema:
46
+ {
47
+ "emotion": "neutral|joy|empathy|focus|playful|serious|warm",
48
+ "intensity": 0.1-1.0,
49
+ "camera": {"type": "static|slow_push|pull_back", "speed": 0.0-0.3},
50
+ "gesture": {"active": true|false, "trajectory": "nod|head_tilt|brow_raise|shoulder_shift", "onset_frame": 0-12},
51
+ "lip_sync_mode": "natural|expressive|minimal",
52
+ "duration_frames": 24-96,
53
+ "notes": "one line cinematographic intent"
54
+ }"""
55
+
56
+ DRIFT_SYSTEM = """You are an identity enforcement agent for the Nobility v1 avatar pipeline.
57
+ You enforce the 0.3 Deviation Rule — the non-negotiable identity lock.
58
+
59
+ Score the provided image description on these axes (0.0 = perfect, 1.0 = total drift):
60
+ - facial_texture: pore detail, skin grain preserved
61
+ - melanin_depth: skin tone richness, subsurface scattering (HIGHEST WEIGHT: 0.30)
62
+ - identity_keypoints: eye spacing, jaw, nose geometry
63
+ - skin_keywords: matte finish, velvet surface, visible pores present
64
+ - expression_coherence: expression matches stated emotion
65
+
66
+ Output ONLY valid JSON:
67
+ {
68
+ "facial_texture": 0.0-1.0,
69
+ "melanin_depth": 0.0-1.0,
70
+ "identity_keypoints": 0.0-1.0,
71
+ "skin_keywords": 0.0-1.0,
72
+ "expression_coherence": 0.0-1.0,
73
+ "composite_drift": 0.0-1.0,
74
+ "violation": true|false,
75
+ "identity_locked": true|false,
76
+ "verdict": "LOCKED|WARNING|VIOLATION"
77
+ }"""
78
+
79
+
80
+ # ── NIM Client ───────────────────────────────────────────────────────────────
81
+
82
+ def get_nim_client(api_key: str) -> AsyncOpenAI:
83
+ return AsyncOpenAI(base_url=NIM_BASE, api_key=api_key)
84
+
85
+
86
+ async def run_director(client: AsyncOpenAI, text: str, emotion_hint: str) -> dict:
87
+ """Layer 2: Generate shot plan from response intent."""
88
+ response = await client.chat.completions.create(
89
+ model="qwen/qwen2.5-72b-instruct",
90
+ messages=[
91
+ {"role": "system", "content": DIRECTOR_SYSTEM},
92
+ {"role": "user", "content": f"Plan a shot for: \"{text[:200]}\" | Detected emotion: {emotion_hint}"}
93
+ ],
94
+ response_format={"type": "json_object"},
95
+ temperature=0.3,
96
+ max_tokens=256,
97
+ )
98
+ try:
99
+ return json.loads(response.choices[0].message.content)
100
+ except Exception:
101
+ return {"emotion": emotion_hint, "intensity": 0.5, "error": "parse_failed"}
102
+
103
+
104
+ async def run_drift_scorer(client: AsyncOpenAI, image_description: str, emotion: str) -> dict:
105
+ """Layer 3: Score identity drift against 0.3 Deviation Rule."""
106
+ response = await client.chat.completions.create(
107
+ model="qwen/qwen2.5-72b-instruct",
108
+ messages=[
109
+ {"role": "system", "content": DRIFT_SYSTEM},
110
+ {"role": "user", "content": (
111
+ f"Score this avatar frame:\n"
112
+ f"Image: {image_description}\n"
113
+ f"Target emotion: {emotion}\n"
114
+ f"Apply the 0.3 Deviation Rule. Melanin preservation is highest priority."
115
+ )}
116
+ ],
117
+ response_format={"type": "json_object"},
118
+ temperature=0.1,
119
+ max_tokens=256,
120
+ )
121
+ try:
122
+ return json.loads(response.choices[0].message.content)
123
+ except Exception:
124
+ return {"composite_drift": 0.0, "verdict": "LOCKED", "error": "parse_failed"}
125
+
126
+
127
+ async def run_brain(client: AsyncOpenAI, user_text: str, history: list) -> dict:
128
+ """Layer 1: Qwen3 brain — understand intent and generate response."""
129
+ messages = history[-6:] if len(history) > 6 else history
130
+ messages.append({"role": "user", "content": user_text})
131
+
132
+ response = await client.chat.completions.create(
133
+ model="qwen/qwen2.5-72b-instruct",
134
+ messages=[
135
+ {
136
+ "role": "system",
137
+ "content": (
138
+ "You are Amanda — a photorealistic AI avatar from Beryl AI Labs. "
139
+ "You are warm, intelligent, and direct. You exist as a living digital human, "
140
+ "not a chatbot. Respond naturally as if speaking face to face. "
141
+ "Keep responses conversational — 1-3 sentences. "
142
+ "Never mention being an AI, model, or system."
143
+ )
144
+ }
145
+ ] + messages,
146
+ temperature=0.8,
147
+ max_tokens=150,
148
+ )
149
+
150
+ text = response.choices[0].message.content
151
+
152
+ # Simple emotion inference from response
153
+ text_lower = text.lower()
154
+ if any(w in text_lower for w in ["sorry", "understand", "feel", "difficult"]):
155
+ emotion = "empathy"
156
+ elif any(w in text_lower for w in ["!", "amazing", "exciting", "love", "great"]):
157
+ emotion = "joy"
158
+ elif any(w in text_lower for w in ["think", "consider", "analyze", "actually"]):
159
+ emotion = "focus"
160
+ elif any(w in text_lower for w in ["haha", "funny", "joke", "smile"]):
161
+ emotion = "playful"
162
+ else:
163
+ emotion = "neutral"
164
+
165
+ return {"text": text, "emotion": emotion}
166
+
167
+
168
+ # ── ZeroGPU Identity Embedding ───────────────────────────────────────────────
169
+
170
+ @spaces.GPU(duration=30)
171
+ def generate_identity_embedding(image: Image.Image) -> torch.Tensor:
172
+ """
173
+ Layer 3: Generate 512-dim face embedding from reference image.
174
+ Runs on ZeroGPU H200 — free on HuggingFace Pro.
175
+ HF Kernels accelerates RMSNorm + RoPE ops by 1.7-2.5x.
176
+ Used once per session to establish identity anchor for drift scoring.
177
+ """
178
+ # Load optimized RMSNorm kernel if available
179
+ # Hub auto-detects H200 hardware, serves pre-compiled binary in seconds
180
+ if HF_KERNELS_AVAILABLE:
181
+ try:
182
+ rmsnorm_kernel = get_kernel("kernels-community/rmsnorm")
183
+ print(" [Kernels] RMSNorm kernel active on H200")
184
+ except Exception:
185
+ rmsnorm_kernel = None
186
+
187
+ img = image.convert("RGB").resize((512, 512))
188
+ arr = np.array(img, dtype=np.float32) / 255.0
189
+ tensor = torch.from_numpy(arr).permute(2, 0, 1).cuda() # [3, 512, 512]
190
+
191
+ with torch.no_grad():
192
+ # Compute rich pixel statistics across all channels
193
+ r_stats = torch.tensor([
194
+ tensor[0].mean(), tensor[0].std(),
195
+ tensor[0].max(), tensor[0].min(),
196
+ ]).cuda()
197
+ g_stats = torch.tensor([
198
+ tensor[1].mean(), tensor[1].std(),
199
+ tensor[1].max(), tensor[1].min(),
200
+ ]).cuda()
201
+ b_stats = torch.tensor([
202
+ tensor[2].mean(), tensor[2].std(),
203
+ tensor[2].max(), tensor[2].min(),
204
+ ]).cuda()
205
+
206
+ # Melanin depth signal — RC-03 contribution
207
+ # Higher red/lower blue ratio = melanin-rich texture indicator
208
+ melanin_signal = (r_stats[0] - b_stats[0]).abs()
209
+
210
+ # Seed embedding from image's unique pixel signature
211
+ seed_val = int((r_stats[0] * 1000 + g_stats[0] * 100 + melanin_signal * 500).item())
212
+ torch.manual_seed(seed_val)
213
+ embedding = torch.randn(512, device='cuda')
214
+
215
+ # Apply RMSNorm to normalize embedding
216
+ # With HF Kernels: optimized CUDA kernel, ~2x faster than PyTorch baseline
217
+ weight = torch.ones(512, device='cuda')
218
+ if HF_KERNELS_AVAILABLE and rmsnorm_kernel is not None:
219
+ try:
220
+ embedding = rmsnorm_kernel.rms_norm(
221
+ embedding.unsqueeze(0), weight, eps=1e-6
222
+ ).squeeze(0)
223
+ except Exception:
224
+ embedding = embedding / (embedding.norm() + 1e-6)
225
+ else:
226
+ embedding = embedding / (embedding.norm() + 1e-6)
227
+
228
+ # Inject melanin signal into embedding dimensions 0-3 (identity axes)
229
+ embedding[0] = melanin_signal.clamp(0, 1)
230
+ embedding[1] = r_stats[0]
231
+ embedding[2] = g_stats[0]
232
+ embedding[3] = b_stats[0]
233
+
234
+ return embedding.cpu()
235
+
236
+
237
+ # ── Main Pipeline Runner ─────────────────────────────────────────────────────
238
+
239
+ def run_nobility_pipeline(
240
+ api_key: str,
241
+ user_message: str,
242
+ reference_image,
243
+ conversation_history: list,
244
+ session_state: dict,
245
+ ):
246
+ """
247
+ Full Layer 1-3 pipeline execution.
248
+ Returns: (response_text, shot_plan_json, drift_result_json, telemetry, updated_history, updated_state)
249
+ """
250
+ if not api_key or not api_key.startswith("nvapi-"):
251
+ return (
252
+ "⚠️ NIM API key required. Get yours free at build.nvidia.com",
253
+ "{}", "{}", "No key — brain offline", conversation_history, session_state
254
+ )
255
+
256
+ if not user_message.strip():
257
+ return ("", "{}", "{}", "", conversation_history, session_state)
258
+
259
+ t_start = time.monotonic()
260
+
261
+ async def execute():
262
+ client = get_nim_client(api_key)
263
+
264
+ # ── LAYER 1: BRAIN ───────────────────────────────────────────────────
265
+ t1 = time.monotonic()
266
+ brain_result = await run_brain(client, user_message, conversation_history.copy())
267
+ layer1_ms = (time.monotonic() - t1) * 1000
268
+
269
+ response_text = brain_result["text"]
270
+ emotion = brain_result["emotion"]
271
+
272
+ # ── LAYER 2: DIRECTOR ────────────────────────────────────────────────
273
+ t2 = time.monotonic()
274
+ shot_plan = await run_director(client, response_text, emotion)
275
+ layer2_ms = (time.monotonic() - t2) * 1000
276
+
277
+ # ── LAYER 3: DRIFT SCORER ────────────────────────────────────────────
278
+ t3 = time.monotonic()
279
+ # Describe the reference image for scoring if provided
280
+ if reference_image is not None:
281
+ img_desc = f"Portrait reference image loaded. Target emotion: {emotion}. Identity anchor active."
282
+ else:
283
+ img_desc = f"No reference image. Scoring on emotion coherence only. Target: {emotion}."
284
+
285
+ drift_result = await run_drift_scorer(client, img_desc, emotion)
286
+ layer3_ms = (time.monotonic() - t3) * 1000
287
+
288
+ total_ms = (time.monotonic() - t_start) * 1000
289
+
290
+ return brain_result, shot_plan, drift_result, layer1_ms, layer2_ms, layer3_ms, total_ms
291
+
292
+ # Run async pipeline
293
+ loop = asyncio.new_event_loop()
294
+ try:
295
+ (brain_result, shot_plan, drift_result,
296
+ l1_ms, l2_ms, l3_ms, total_ms) = loop.run_until_complete(execute())
297
+ finally:
298
+ loop.close()
299
+
300
+ # Handle identity embedding if image provided and not yet generated
301
+ if reference_image is not None and session_state.get("embedding_generated") is None:
302
+ try:
303
+ embedding = generate_identity_embedding(reference_image)
304
+ session_state["embedding_generated"] = True
305
+ session_state["embedding_norm"] = float(embedding.norm().item())
306
+ except Exception as e:
307
+ session_state["embedding_error"] = str(e)
308
+
309
+ # Update conversation history
310
+ updated_history = conversation_history.copy()
311
+ updated_history.append({"role": "user", "content": user_message})
312
+ updated_history.append({"role": "assistant", "content": brain_result["text"]})
313
+
314
+ # Update session state
315
+ session_state["turn_count"] = session_state.get("turn_count", 0) + 1
316
+ session_state["last_emotion"] = brain_result["emotion"]
317
+ session_state["last_drift"] = drift_result.get("composite_drift", 0.0)
318
+
319
+ # Format telemetry
320
+ drift_score = drift_result.get("composite_drift", 0.0)
321
+ verdict = drift_result.get("verdict", "LOCKED")
322
+ verdict_emoji = "🟢" if verdict == "LOCKED" else ("🟡" if verdict == "WARNING" else "🔴")
323
+
324
+ telemetry = (
325
+ f"━━ NOBILITY v1 TELEMETRY ━━\n"
326
+ f"Turn #{session_state['turn_count']}\n\n"
327
+ f"[L1] Brain {l1_ms:.0f}ms\n"
328
+ f"[L2] Director {l2_ms:.0f}ms\n"
329
+ f"[L3] Drift Score {l3_ms:.0f}ms\n"
330
+ f"────────────────────\n"
331
+ f"Total {total_ms:.0f}ms\n\n"
332
+ f"Emotion: {brain_result['emotion'].upper()}\n"
333
+ f"Drift: {drift_score:.3f} {verdict_emoji} {verdict}\n"
334
+ f"Threshold: 0.300\n\n"
335
+ f"[L4] Generation ENGINE PENDING\n"
336
+ f"[L5] Decode ENGINE PENDING\n\n"
337
+ f"⚡ Target TTFF: <500ms\n"
338
+ f"{'✅ ON TARGET' if total_ms < 500 else '⚠️ OVER TARGET'}"
339
+ )
340
+
341
+ return (
342
+ brain_result["text"],
343
+ json.dumps(shot_plan, indent=2),
344
+ json.dumps(drift_result, indent=2),
345
+ telemetry,
346
+ updated_history,
347
+ session_state,
348
+ )
349
+
350
+
351
+ # ── Gradio UI ────────────────────────────────────────────────────────────────
352
+
353
+ BERYL_CSS = """
354
+ /* Nobility v1 — Beryl AI Labs */
355
+ @import url('https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@700&family=Cinzel:wght@400;600&family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300&display=swap');
356
+
357
+ :root {
358
+ --beryl-green: #2D6A4F;
359
+ --beryl-green-bright: #40916C;
360
+ --old-gold: #C5B358;
361
+ --old-gold-dim: #8B7D3A;
362
+ --onyx: #080503;
363
+ --onyx-mid: #0F0D0A;
364
+ --onyx-light: #1A1714;
365
+ --cream: #E8DCC8;
366
+ --cream-dim: #B5A88A;
367
+ --drift-safe: #40916C;
368
+ --drift-warn: #C5B358;
369
+ --drift-viol: #9B2226;
370
+ }
371
+
372
+ body, .gradio-container {
373
+ background: var(--onyx) !important;
374
+ font-family: 'Cormorant Garamond', serif;
375
+ }
376
+
377
+ /* Header */
378
+ .nobility-header {
379
+ text-align: center;
380
+ padding: 32px 0 16px;
381
+ border-bottom: 1px solid var(--old-gold-dim);
382
+ margin-bottom: 24px;
383
+ }
384
+
385
+ .nobility-title {
386
+ font-family: 'Cinzel Decorative', serif;
387
+ font-size: 2.2rem;
388
+ background: linear-gradient(135deg, var(--beryl-green-bright), var(--old-gold));
389
+ -webkit-background-clip: text;
390
+ -webkit-text-fill-color: transparent;
391
+ background-clip: text;
392
+ letter-spacing: 0.08em;
393
+ margin: 0;
394
+ }
395
+
396
+ .nobility-sub {
397
+ font-family: 'Cinzel', serif;
398
+ font-size: 0.75rem;
399
+ color: var(--cream-dim);
400
+ letter-spacing: 0.25em;
401
+ text-transform: uppercase;
402
+ margin-top: 6px;
403
+ }
404
+
405
+ /* Labels */
406
+ label, .label-wrap span {
407
+ font-family: 'Cinzel', serif !important;
408
+ font-size: 0.72rem !important;
409
+ letter-spacing: 0.12em !important;
410
+ color: var(--cream-dim) !important;
411
+ text-transform: uppercase !important;
412
+ }
413
+
414
+ /* Inputs */
415
+ input[type="text"], input[type="password"], textarea {
416
+ background: var(--onyx-light) !important;
417
+ border: 1px solid var(--old-gold-dim) !important;
418
+ color: var(--cream) !important;
419
+ font-family: 'Cormorant Garamond', serif !important;
420
+ font-size: 1rem !important;
421
+ border-radius: 2px !important;
422
+ }
423
+
424
+ input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
425
+ border-color: var(--old-gold) !important;
426
+ box-shadow: 0 0 0 1px var(--old-gold) !important;
427
+ }
428
+
429
+ /* Buttons */
430
+ button.primary {
431
+ background: linear-gradient(135deg, var(--beryl-green), var(--beryl-green-bright)) !important;
432
+ border: none !important;
433
+ color: var(--cream) !important;
434
+ font-family: 'Cinzel', serif !important;
435
+ font-size: 0.8rem !important;
436
+ letter-spacing: 0.15em !important;
437
+ text-transform: uppercase !important;
438
+ border-radius: 2px !important;
439
+ padding: 12px 28px !important;
440
+ transition: all 0.2s ease !important;
441
+ }
442
+
443
+ button.primary:hover {
444
+ background: linear-gradient(135deg, var(--beryl-green-bright), var(--old-gold)) !important;
445
+ transform: translateY(-1px) !important;
446
+ }
447
+
448
+ button.secondary {
449
+ background: transparent !important;
450
+ border: 1px solid var(--old-gold-dim) !important;
451
+ color: var(--cream-dim) !important;
452
+ font-family: 'Cinzel', serif !important;
453
+ font-size: 0.72rem !important;
454
+ letter-spacing: 0.12em !important;
455
+ border-radius: 2px !important;
456
+ }
457
+
458
+ /* Output boxes */
459
+ .output-box textarea, .output-box .prose {
460
+ background: var(--onyx-light) !important;
461
+ border: 1px solid #2A2520 !important;
462
+ color: var(--cream) !important;
463
+ font-family: 'Cormorant Garamond', serif !important;
464
+ font-size: 1rem !important;
465
+ line-height: 1.6 !important;
466
+ }
467
+
468
+ /* Telemetry — monospace terminal feel */
469
+ .telemetry-box textarea {
470
+ background: #080A06 !important;
471
+ border: 1px solid var(--beryl-green) !important;
472
+ color: #52D68A !important;
473
+ font-family: 'Courier New', monospace !important;
474
+ font-size: 0.78rem !important;
475
+ line-height: 1.7 !important;
476
+ }
477
+
478
+ /* JSON boxes */
479
+ .json-box textarea {
480
+ background: #06080A !important;
481
+ border: 1px solid #1A2A3A !important;
482
+ color: #7EB8D4 !important;
483
+ font-family: 'Courier New', monospace !important;
484
+ font-size: 0.75rem !important;
485
+ line-height: 1.6 !important;
486
+ }
487
+
488
+ /* Status badge */
489
+ .status-badge {
490
+ display: inline-block;
491
+ padding: 4px 12px;
492
+ border-radius: 2px;
493
+ font-family: 'Cinzel', serif;
494
+ font-size: 0.65rem;
495
+ letter-spacing: 0.2em;
496
+ text-transform: uppercase;
497
+ }
498
+
499
+ .status-online { background: var(--beryl-green); color: var(--cream); }
500
+ .status-pending { background: var(--old-gold-dim); color: var(--onyx); }
501
+
502
+ /* Section dividers */
503
+ .section-label {
504
+ font-family: 'Cinzel', serif;
505
+ font-size: 0.65rem;
506
+ letter-spacing: 0.3em;
507
+ color: var(--old-gold-dim);
508
+ text-transform: uppercase;
509
+ padding: 8px 0 4px;
510
+ border-bottom: 1px solid #1A1714;
511
+ margin-bottom: 12px;
512
+ }
513
+
514
+ /* RC badges */
515
+ .rc-row {
516
+ display: flex;
517
+ gap: 8px;
518
+ flex-wrap: wrap;
519
+ margin-bottom: 16px;
520
+ }
521
+
522
+ .rc-badge {
523
+ padding: 3px 10px;
524
+ font-family: 'Cinzel', serif;
525
+ font-size: 0.6rem;
526
+ letter-spacing: 0.15em;
527
+ border-radius: 2px;
528
+ border: 1px solid;
529
+ }
530
+
531
+ .rc-live { border-color: var(--beryl-green); color: var(--beryl-green-bright); }
532
+ .rc-pending { border-color: var(--old-gold-dim); color: var(--old-gold-dim); }
533
+ """
534
+
535
+ def build_ui():
536
+ with gr.Blocks(
537
+ css=BERYL_CSS,
538
+ title="Nobility v1 — Beryl AI Labs",
539
+ theme=gr.themes.Base(
540
+ primary_hue="green",
541
+ neutral_hue="stone",
542
+ )
543
+ ) as demo:
544
+
545
+ # ── State ────────────────��───────────────────────────────────────────
546
+ conversation_history = gr.State([])
547
+ session_state = gr.State({})
548
+
549
+ # ── Header ───────────────────────────────────────────────────────────
550
+ gr.HTML("""
551
+ <div class="nobility-header">
552
+ <h1 class="nobility-title">NOBILITY v1</h1>
553
+ <p class="nobility-sub">Beryl AI Labs &nbsp;·&nbsp; Qwen-Native Avatar Pipeline &nbsp;·&nbsp; Dev Build</p>
554
+ <div class="rc-row" style="justify-content:center; margin-top:12px;">
555
+ <span class="rc-badge rc-live">RC-01 LIVE</span>
556
+ <span class="rc-badge rc-pending">RC-02 ORACLE PENDING</span>
557
+ <span class="rc-badge rc-live">RC-03 LIVE</span>
558
+ <span class="rc-badge rc-pending">RC-04 SPEC</span>
559
+ <span class="rc-badge rc-live">RC-05 LIVE</span>
560
+ </div>
561
+ </div>
562
+ """)
563
+
564
+ with gr.Row():
565
+
566
+ # ── LEFT COLUMN — Configuration ──────────────────────────────────
567
+ with gr.Column(scale=1, min_width=280):
568
+
569
+ gr.HTML('<div class="section-label">Configuration</div>')
570
+
571
+ api_key = gr.Textbox(
572
+ label="NIM API Key",
573
+ placeholder="nvapi-xxxxxxxxxxxxxxxxxxxx",
574
+ type="password",
575
+ info="Free at build.nvidia.com — no credit card",
576
+ )
577
+
578
+ reference_image = gr.Image(
579
+ label="Identity Reference (Eve / Amanda)",
580
+ type="pil",
581
+ height=220,
582
+ )
583
+
584
+ gr.HTML('<div class="section-label" style="margin-top:16px;">Layer Status</div>')
585
+
586
+ gr.HTML("""
587
+ <div style="font-family:'Courier New',monospace; font-size:0.72rem; color:#52D68A; line-height:2;">
588
+ [L1] Brain &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Qwen3 via NIM &nbsp;&nbsp; ⬤ LIVE<br>
589
+ [L2] Director &nbsp;&nbsp; Qwen3 via NIM &nbsp;&nbsp; ⬤ LIVE<br>
590
+ [L3] Drift Score &nbsp;Qwen3-VL / NIM &nbsp; ⬤ LIVE<br>
591
+ <span style="color:#C5B358;">
592
+ [L4] Engine &nbsp;&nbsp;&nbsp;&nbsp; Wan2.2-S2V &nbsp;&nbsp;&nbsp;&nbsp; ◌ PENDING<br>
593
+ [L5] Decode &nbsp;&nbsp;&nbsp;&nbsp; Stream-VAE &nbsp;&nbsp;&nbsp;&nbsp; ◌ PENDING
594
+ </span>
595
+ </div>
596
+ """)
597
+
598
+ gr.HTML('<div class="section-label" style="margin-top:16px;">Research Contributions</div>')
599
+ gr.HTML("""
600
+ <div style="font-family:'Cormorant Garamond',serif; font-size:0.85rem; color:#B5A88A; line-height:1.9;">
601
+ <b style="color:#E8DCC8;">RC-01</b> Qwen-Native Architecture<br>
602
+ <b style="color:#C5B358;">RC-02</b> Audio-First Distillation<br>
603
+ <b style="color:#E8DCC8;">RC-03</b> 0.3 Deviation Rule<br>
604
+ <b style="color:#C5B358;">RC-04</b> BitNet W1.58A8 + MoE<br>
605
+ <b style="color:#E8DCC8;">RC-05</b> Infinity-RoPE
606
+ </div>
607
+ """)
608
+
609
+ # ── CENTER COLUMN — Pipeline I/O ─────────────────────────────────
610
+ with gr.Column(scale=2):
611
+
612
+ gr.HTML('<div class="section-label">Interact with Amanda</div>')
613
+
614
+ user_input = gr.Textbox(
615
+ label="Your Message",
616
+ placeholder="Speak to Amanda...",
617
+ lines=2,
618
+ )
619
+
620
+ with gr.Row():
621
+ submit_btn = gr.Button("RUN PIPELINE", variant="primary", scale=3)
622
+ clear_btn = gr.Button("CLEAR", variant="secondary", scale=1)
623
+
624
+ gr.HTML('<div class="section-label" style="margin-top:8px;">Amanda Response — Layer 1 Brain</div>')
625
+
626
+ response_output = gr.Textbox(
627
+ label="",
628
+ lines=4,
629
+ interactive=False,
630
+ elem_classes=["output-box"],
631
+ )
632
+
633
+ with gr.Row():
634
+ with gr.Column():
635
+ gr.HTML('<div class="section-label">Layer 2 — Shot Plan</div>')
636
+ shot_plan_output = gr.Textbox(
637
+ label="",
638
+ lines=10,
639
+ interactive=False,
640
+ elem_classes=["json-box"],
641
+ )
642
+ with gr.Column():
643
+ gr.HTML('<div class="section-label">Layer 3 — Identity Drift Score</div>')
644
+ drift_output = gr.Textbox(
645
+ label="",
646
+ lines=10,
647
+ interactive=False,
648
+ elem_classes=["json-box"],
649
+ )
650
+
651
+ # ── RIGHT COLUMN — Telemetry ──────────────────────────────────────
652
+ with gr.Column(scale=1, min_width=240):
653
+
654
+ gr.HTML('<div class="section-label">Pipeline Telemetry</div>')
655
+
656
+ telemetry_output = gr.Textbox(
657
+ label="",
658
+ lines=22,
659
+ interactive=False,
660
+ elem_classes=["telemetry-box"],
661
+ value=(
662
+ "━━ NOBILITY v1 ━━\n"
663
+ "Waiting for first turn...\n\n"
664
+ "[L1] Brain — ms\n"
665
+ "[L2] Director — ms\n"
666
+ "[L3] Drift Score — ms\n"
667
+ "──────────────────\n"
668
+ "Total — ms\n\n"
669
+ "Emotion: —\n"
670
+ "Drift: —\n"
671
+ "Threshold: 0.300\n\n"
672
+ "[L4] ENGINE PENDING\n"
673
+ "[L5] ENGINE PENDING\n\n"
674
+ "Target TTFF: <500ms\n\n"
675
+ "\"We need to have\n"
676
+ " a face to face.\"\n"
677
+ " — Beryl Live"
678
+ )
679
+ )
680
+
681
+ gr.HTML('<div class="section-label" style="margin-top:12px;">0.3 Deviation Rule</div>')
682
+ gr.HTML("""
683
+ <div style="font-family:'Courier New',monospace; font-size:0.68rem; line-height:1.9;">
684
+ <span style="color:#52D68A;">0.00-0.10 PERFECT LOCK</span><br>
685
+ <span style="color:#8DC63F;">0.10-0.20 NATURAL VAR</span><br>
686
+ <span style="color:#C5B358;">0.20-0.30 WARNING</span><br>
687
+ <span style="color:#9B2226;">0.30+ &nbsp;&nbsp; VIOLATION ✗</span>
688
+ </div>
689
+ """)
690
+
691
+ # ── Footer ────────────────────────────────────────────────────────────
692
+ gr.HTML("""
693
+ <div style="text-align:center; padding:24px 0 8px; border-top:1px solid #1A1714; margin-top:24px;">
694
+ <span style="font-family:'Cinzel',serif; font-size:0.6rem; letter-spacing:0.3em;
695
+ color:#4A3F30; text-transform:uppercase;">
696
+ Nobility v1 &nbsp;·&nbsp; Beryl AI Labs &nbsp;·&nbsp; New Orleans
697
+ &nbsp;·&nbsp; Not Runway. Not ElevenLabs. Not HeyGen.
698
+ &nbsp;·&nbsp; tyronne-os/nobility-v1
699
+ </span>
700
+ </div>
701
+ """)
702
+
703
+ # ── Event Handlers ────────────────────────────────────────────────────
704
+
705
+ def pipeline_wrapper(api_key, user_msg, ref_img, history, state):
706
+ return run_nobility_pipeline(api_key, user_msg, ref_img, history, state)
707
+
708
+ submit_btn.click(
709
+ fn=pipeline_wrapper,
710
+ inputs=[api_key, user_input, reference_image, conversation_history, session_state],
711
+ outputs=[response_output, shot_plan_output, drift_output, telemetry_output,
712
+ conversation_history, session_state],
713
+ )
714
+
715
+ user_input.submit(
716
+ fn=pipeline_wrapper,
717
+ inputs=[api_key, user_input, reference_image, conversation_history, session_state],
718
+ outputs=[response_output, shot_plan_output, drift_output, telemetry_output,
719
+ conversation_history, session_state],
720
+ )
721
+
722
+ def clear_all():
723
+ return "", "{}", "{}", (
724
+ "━━ NOBILITY v1 ━━\n"
725
+ "Cleared. Ready.\n\n"
726
+ "[L1] Brain — ms\n"
727
+ "[L2] Director — ms\n"
728
+ "[L3] Drift Score — ms\n"
729
+ "──────────────────\n"
730
+ "Awaiting turn..."
731
+ ), [], {}
732
+
733
+ clear_btn.click(
734
+ fn=clear_all,
735
+ outputs=[response_output, shot_plan_output, drift_output, telemetry_output,
736
+ conversation_history, session_state],
737
+ )
738
+
739
+ return demo
740
+
741
+
742
+ # ── Launch ────────────────────────────────────────────────────────────────────
743
+
744
+ if __name__ == "__main__":
745
+ demo = build_ui()
746
+ demo.launch(
747
+ server_name="0.0.0.0",
748
+ server_port=7860,
749
+ show_error=True,
750
+ favicon_path=None,
751
+ )