realruneet commited on
Commit
8f3e429
·
1 Parent(s): 2d5f620

feat: SAGE-PRO Gradio frontend with tier-based model routing

Browse files
Files changed (5) hide show
  1. Dockerfile +12 -9
  2. README.md +19 -7
  3. app.py +282 -1963
  4. requirements.txt +5 -1
  5. validate.py +83 -0
Dockerfile CHANGED
@@ -1,21 +1,24 @@
1
- # SAGE-PRO Frontend Lightweight Gradio container
2
- # Runs independently from the backend; connects via SAGE_BACKEND_URL
3
-
4
  FROM python:3.11-slim
5
 
6
  WORKDIR /app
7
 
8
- # Install deps
 
 
 
 
 
9
  COPY requirements.txt .
10
  RUN pip install --no-cache-dir -r requirements.txt
11
 
12
- # Copy app
13
  COPY app.py .
14
- COPY README.md .
15
 
16
- # Gradio needs this for HF Spaces compatibility
17
- ENV GRADIO_SERVER_NAME=0.0.0.0
18
- ENV GRADIO_SERVER_PORT=7860
 
19
 
20
  EXPOSE 7860
21
 
 
1
+ # SAGE-PRO Deployment Image for AMD MI300X (ROCm)
 
 
2
  FROM python:3.11-slim
3
 
4
  WORKDIR /app
5
 
6
+ # System deps
7
+ RUN apt-get update && apt-get install -y \
8
+ git curl build-essential \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Python deps
12
  COPY requirements.txt .
13
  RUN pip install --no-cache-dir -r requirements.txt
14
 
15
+ # Copy SAGE-PRO source
16
  COPY app.py .
 
17
 
18
+ # Environment
19
+ ENV SAGE_MODE=pro
20
+ ENV ROCM_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
21
+ ENV HSA_OVERRIDE_GFX_VERSION=9.4.2
22
 
23
  EXPOSE 7860
24
 
README.md CHANGED
@@ -1,12 +1,24 @@
1
  ---
2
- title: Sage
3
- emoji: ⚔️
4
- colorFrom: red
5
- colorTo: gray
6
  sdk: gradio
 
7
  app_file: app.py
8
- python_version: "3.11"
9
  pinned: true
 
10
  ---
11
- # SAGE-PRO: Strategic Adversarial Generative Engine
12
- 4-Agent ensemble powered by AMD MI300X (192GB HBM3)
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SAGE — Strategic Adversarial Generative Engine
3
+ emoji: 🧠
4
+ colorFrom: orange
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 5.29.0
8
  app_file: app.py
 
9
  pinned: true
10
+ license: mit
11
  ---
12
+
13
+ # SAGE-PRO Strategic Adversarial Generative Engine
14
+
15
+ Multi-agent AI system running on AMD Instinct MI300X with tier-based model routing.
16
+
17
+ ## Tiers
18
+ - **Simple** — Direct response, ~0.2s
19
+ - **Medium** — Full pipeline, qwen2.5-coder:32b
20
+ - **Complex** — Adversarial debate, deepseek-r1:32b vs qwen2.5-coder:32b
21
+ - **Boardroom** — Full council with 72b model (say "boardroom:" to trigger)
22
+
23
+ ## Demo Mode
24
+ Set `SAGE_MODE=demo` for zero-GPU demo on HF Spaces.
app.py CHANGED
@@ -1,1998 +1,317 @@
1
- """
2
- ╔══════════════════════════════════════════════════════════════════════════════════╗
3
- ║ SAGE-PRO · Strategic Adversarial Generative Engine ║
4
- ║ 4-Agent Co-Resident Ensemble on AMD Instinct MI300X (192 GB HBM3) ║
5
- ║ ───────────────────────────────────────────────────────────────────────── ║
6
- ║ Agents: Architect · Implementer · Red-Team · Synthesizer ║
7
- ║ Deployment: Hugging Face Spaces · app.py (single-file) ║
8
- ╚══════════════════════════════════════════════════════════════════════════════════╝
9
- """
10
-
11
- import asyncio
12
- import json
13
- import math
14
- import os
15
- import random
16
- import time
17
  from datetime import datetime
18
- from typing import Generator
19
-
20
  import httpx
21
-
22
  import gradio as gr
23
 
24
- # ─────────────────────────────────────────────────────────────────────────────
25
- # 0. CONSTANTS & CONFIGURATION
26
- # ─────────────────────────────────────────────────────────────────────────────
27
-
28
- # Backend URL — set via env var in HF Space secrets or docker-compose.
29
- # When empty / unreachable, the UI falls back to mock-mode automatically.
30
- SAGE_BACKEND_URL = os.environ.get("SAGE_BACKEND_URL", "").rstrip("/")
31
-
32
- VALID_USERS = {
33
- "admin": "sage2024",
34
- "engineer": "mi300x",
35
- "demo": "crucible",
36
- }
37
-
38
- AGENT_COLORS = {
39
- "Architect": "#4a90ff",
40
- "Implementer": "#3dffa0",
41
- "Red-Team": "#ffb347",
42
- "Synthesizer": "#c084fc",
43
- "SYSTEM": "#94a3b8",
44
- "COUNCIL": "#e2e8f0",
45
- }
46
-
47
- # ─────────────────────────────────────────────────────────────────────────────
48
- # 1. MOCK BACKEND — async streaming deliberation engine
49
- # ─────────────────────────────────────────────────────────────────────────────
50
-
51
- PHASE_SCRIPTS = {
52
- "Architect": [
53
- "Bootstrapping topology resolver …",
54
- "Scoping void boundaries in solution space …",
55
- "Modeling distributed-system constraints …",
56
- "Defining interface contracts & invariants …",
57
- "Topology locked — emitting design token stream …",
58
- ],
59
- "Implementer": [
60
- "Receiving design tokens from Architect …",
61
- "Selecting optimal data-structure primitives …",
62
- "Drafting core algorithmic skeleton …",
63
- "Injecting concurrency primitives (asyncio / trio) …",
64
- "Optimizing hot paths via torsion-aware profiling …",
65
- "Code draft complete — forwarding to Red-Team …",
66
- ],
67
- "Red-Team": [
68
- "Scanning for race conditions …",
69
- "Probing boundary-condition edge cases …",
70
- "⚠ Vulnerability found: unchecked integer overflow on line 47",
71
- "⚠ Potential deadlock in mutex acquisition order",
72
- "Fuzzing input sanitisation layer …",
73
- "Generating adversarial test corpus (512 cases) …",
74
- "Red-Team report sealed — routing to Synthesizer …",
75
- ],
76
- "Synthesizer": [
77
- "Ingesting all agent outputs …",
78
- "Initialising Nash Equilibrium search (cycle 1/4) …",
79
- "Resolving Architect ↔ Red-Team conflict on mutex order …",
80
- "Nash cycle 2/4 — divergence index: 0.34 …",
81
- "Nash cycle 3/4 — divergence index: 0.11 …",
82
- "Nash cycle 4/4 — divergence index: 0.02 (converged) ✓",
83
- "Applying hardened patch-set from Red-Team …",
84
- "Synthesising final artifact …",
85
- "Council consensus achieved — artifact sealed ✓",
86
- ],
87
- }
88
-
89
- MOCK_CODE_TEMPLATES = {
90
- "rate limiter": '''"""
91
- SAGE-PRO · Final Artifact
92
- Agent: Synthesizer (Nash-Hardened) · Nash Cycles: 4 · Divergence: 0.02
93
- """
94
-
95
- import asyncio
96
- import time
97
- from collections import defaultdict, deque
98
- from dataclasses import dataclass, field
99
- from typing import Optional
100
-
101
-
102
- @dataclass
103
- class RateLimiterConfig:
104
- max_requests: int = 100 # requests per window
105
- window_seconds: float = 60.0 # rolling-window size
106
- burst_allowance: int = 10 # short-burst headroom
107
- penalty_seconds: float = 30.0 # back-off on violation
108
-
109
-
110
- class SlidingWindowRateLimiter:
111
- """
112
- Thread-safe, asyncio-compatible sliding-window rate limiter.
113
- Hardened by SAGE Red-Team against:
114
- · Race conditions (asyncio.Lock per client)
115
- · Integer overflow (deque bounded by max_requests)
116
- · Clock skew (monotonic clock, not wall-clock)
117
- """
118
-
119
- def __init__(self, config: Optional[RateLimiterConfig] = None):
120
- self.cfg = config or RateLimiterConfig()
121
- self._windows: dict[str, deque[float]] = defaultdict(
122
- lambda: deque(maxlen=self.cfg.max_requests + self.cfg.burst_allowance)
123
- )
124
- self._penalties: dict[str, float] = {}
125
- self._locks: dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
126
-
127
- async def is_allowed(self, client_id: str) -> tuple[bool, dict]:
128
- async with self._locks[client_id]:
129
- now = time.monotonic()
130
-
131
- # Check active penalty
132
- if (expiry := self._penalties.get(client_id, 0)) > now:
133
- return False, {
134
- "allowed": False,
135
- "reason": "penalty_active",
136
- "retry_after": round(expiry - now, 2),
137
- }
138
-
139
- window = self._windows[client_id]
140
-
141
- # Evict timestamps outside the rolling window
142
- cutoff = now - self.cfg.window_seconds
143
- while window and window[0] <= cutoff:
144
- window.popleft()
145
-
146
- if len(window) >= self.cfg.max_requests:
147
- # Impose penalty
148
- self._penalties[client_id] = now + self.cfg.penalty_seconds
149
- return False, {
150
- "allowed": False,
151
- "reason": "rate_exceeded",
152
- "retry_after": self.cfg.penalty_seconds,
153
- "requests_in_window": len(window),
154
- }
155
-
156
- window.append(now)
157
- remaining = self.cfg.max_requests - len(window)
158
- return True, {
159
- "allowed": True,
160
- "remaining": remaining,
161
- "window_resets_in": round(
162
- self.cfg.window_seconds - (now - window[0]), 2
163
- ) if window else self.cfg.window_seconds,
164
- }
165
-
166
- async def reset(self, client_id: str) -> None:
167
- async with self._locks[client_id]:
168
- self._windows[client_id].clear()
169
- self._penalties.pop(client_id, None)
170
-
171
-
172
- # ── Quick demo ────────────────────────────────────────────────────────────────
173
- async def _demo():
174
- limiter = SlidingWindowRateLimiter(
175
- RateLimiterConfig(max_requests=5, window_seconds=10)
176
- )
177
- client = "user-42"
178
- for i in range(8):
179
- ok, meta = await limiter.is_allowed(client)
180
- status = "✅ ALLOWED" if ok else "🚫 BLOCKED"
181
- print(f" Request {i+1:02d} {status} {meta}")
182
- await asyncio.sleep(0.1)
183
-
184
-
185
- if __name__ == "__main__":
186
- asyncio.run(_demo())
187
- ''',
188
- "default": '''"""
189
- SAGE-PRO · Final Artifact
190
- Agent: Synthesizer (Nash-Hardened)
191
- """
192
-
193
- import asyncio
194
- from typing import Any
195
-
196
-
197
- class SAGEArtifact:
198
- """
199
- Nash-hardened solution generated by the 4-agent SAGE council.
200
- Verified by Red-Team adversarial probe suite.
201
- """
202
-
203
- def __init__(self, query: str):
204
- self.query = query
205
- self.metadata = {
206
- "vram_peak_gb": round(random.uniform(178, 190), 1),
207
- "nash_cycles": 4,
208
- "divergence_index": 0.02,
209
- "agents": ["Architect", "Implementer", "Red-Team", "Synthesizer"],
210
- }
211
-
212
- async def execute(self, *args: Any, **kwargs: Any) -> dict:
213
- """Execute the artifact logic."""
214
- raise NotImplementedError(
215
- "Wire this to your FastAPI backend at /v1/sage/generate"
216
- )
217
-
218
- def __repr__(self) -> str:
219
- return (
220
- f"SAGEArtifact(query={self.query!r}, "
221
- f"nash_cycles={self.metadata['nash_cycles']}, "
222
- f"divergence={self.metadata['divergence_index']})"
223
- )
224
-
225
-
226
- # ── Stub entry point ──────────────────────────────────────────────────────────
227
- async def main():
228
- artifact = SAGEArtifact(query="your query here")
229
- print(artifact)
230
-
231
-
232
- if __name__ == "__main__":
233
- asyncio.run(main())
234
- ''',
235
- }
236
-
237
- def _pick_code(query: str) -> str:
238
- q = query.lower()
239
- for key, code in MOCK_CODE_TEMPLATES.items():
240
- if key != "default" and key in q:
241
- return code
242
- return MOCK_CODE_TEMPLATES["default"]
243
-
244
-
245
- # ─────────────────────────────────────────────────────────────────────────────
246
- # 2. CUSTOM CSS — Deep-space glassmorphism IDE aesthetic
247
- # ─────────────────────────────────────────────────────────────────────────────
248
-
249
- custom_css = """
250
- /* ── Google Fonts ── */
251
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;500;700&family=Orbitron:wght@400;700;900&display=swap');
252
-
253
- /* ── CSS Variables ── */
254
- :root {
255
- --bg-void: #07080f;
256
- --bg-deep: #0b0d17;
257
- --bg-card: rgba(255,255,255,0.028);
258
- --bg-card-hover: rgba(255,255,255,0.05);
259
- --border: rgba(255,255,255,0.07);
260
- --border-bright: rgba(255,255,255,0.14);
261
- --text-primary: #e8eaf0;
262
- --text-secondary: #8892a4;
263
- --text-muted: #4a5568;
264
- --accent-blue: #4a90ff;
265
- --accent-green: #3dffa0;
266
- --accent-amber: #ffb347;
267
- --accent-purple: #c084fc;
268
- --accent-cyan: #22d3ee;
269
- --glow-blue: rgba(74,144,255,0.18);
270
- --glow-green: rgba(61,255,160,0.15);
271
- --radius-sm: 8px;
272
- --radius-md: 12px;
273
- --radius-lg: 18px;
274
- --font-ui: 'Space Grotesk', sans-serif;
275
- --font-mono: 'JetBrains Mono', monospace;
276
- --font-display: 'Orbitron', sans-serif;
277
- --transition: 0.22s cubic-bezier(0.4,0,0.2,1);
278
- }
279
-
280
- /* ── Global Reset ── */
281
- *, *::before, *::after { box-sizing: border-box; }
282
-
283
- body,
284
- .gradio-container,
285
- #root,
286
- .main,
287
- .wrap,
288
- footer {
289
- background: var(--bg-void) !important;
290
- font-family: var(--font-ui) !important;
291
- color: var(--text-primary) !important;
292
- }
293
-
294
- /* hide default gradio footer */
295
- footer { display: none !important; }
296
-
297
- /* ── Scrollbar ── */
298
- ::-webkit-scrollbar { width: 4px; height: 4px; }
299
- ::-webkit-scrollbar-track { background: transparent; }
300
- ::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 99px; }
301
-
302
- /* ── Background grid ── */
303
- .gradio-container::before {
304
- content: '';
305
- position: fixed;
306
- inset: 0;
307
- background-image:
308
- linear-gradient(rgba(74,144,255,0.03) 1px, transparent 1px),
309
- linear-gradient(90deg, rgba(74,144,255,0.03) 1px, transparent 1px);
310
- background-size: 48px 48px;
311
- pointer-events: none;
312
- z-index: 0;
313
- }
314
-
315
- /* ── Animated top border ── */
316
- .gradio-container::after {
317
- content: '';
318
- position: fixed;
319
- top: 0; left: 0; right: 0;
320
- height: 2px;
321
- background: linear-gradient(90deg, transparent, var(--accent-blue), var(--accent-green), var(--accent-purple), transparent);
322
- background-size: 200% 100%;
323
- animation: scanline 4s linear infinite;
324
- z-index: 9999;
325
- }
326
-
327
- @keyframes scanline {
328
- 0% { background-position: -100% 0; }
329
- 100% { background-position: 200% 0; }
330
- }
331
-
332
- /* ── Page wrapper ── */
333
- #sage-app { position: relative; z-index: 1; }
334
-
335
- /* ── Login page: Gradio group override for flex-row split layout ── */
336
- #login-page > .form,
337
- #login-page > div:first-child {
338
- display: flex !important;
339
- flex-direction: row !important;
340
- width: 100% !important;
341
- min-height: 100vh !important;
342
- gap: 0 !important;
343
- padding: 0 !important;
344
- border: none !important;
345
- background: transparent !important;
346
- }
347
- #login-auth {
348
- width: 420px !important;
349
- min-width: 360px !important;
350
- flex-shrink: 0 !important;
351
- }
352
-
353
- /* ── Header ── */
354
- #sage-header {
355
- text-align: center;
356
- padding: 32px 0 20px;
357
- border-bottom: 1px solid var(--border);
358
- margin-bottom: 24px;
359
- position: relative;
360
- }
361
-
362
- #sage-header .sage-logo {
363
- font-family: var(--font-display);
364
- font-size: 2.6rem;
365
- font-weight: 900;
366
- letter-spacing: 0.12em;
367
- background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-cyan) 45%, var(--accent-green) 100%);
368
- -webkit-background-clip: text;
369
- -webkit-text-fill-color: transparent;
370
- background-clip: text;
371
- text-shadow: none;
372
- animation: logoPulse 3s ease-in-out infinite;
373
- }
374
-
375
- @keyframes logoPulse {
376
- 0%, 100% { filter: brightness(1); }
377
- 50% { filter: brightness(1.15); }
378
- }
379
-
380
- #sage-header .sage-subtitle {
381
- font-family: var(--font-mono);
382
- font-size: 0.72rem;
383
- font-weight: 400;
384
- color: var(--text-secondary);
385
- letter-spacing: 0.22em;
386
- text-transform: uppercase;
387
- margin-top: 6px;
388
- }
389
-
390
- #sage-header .agent-badges {
391
- display: flex;
392
- justify-content: center;
393
- gap: 10px;
394
- margin-top: 14px;
395
- flex-wrap: wrap;
396
- }
397
-
398
- .agent-badge {
399
- font-family: var(--font-mono);
400
- font-size: 0.65rem;
401
- font-weight: 500;
402
- letter-spacing: 0.1em;
403
- padding: 3px 10px;
404
- border-radius: 99px;
405
- border: 1px solid;
406
- opacity: 0.85;
407
- }
408
-
409
- /* ── Nav Bar ── */
410
- #nav-bar {
411
- display: flex;
412
- align-items: center;
413
- gap: 4px;
414
- padding: 6px 8px;
415
- background: var(--bg-card);
416
- border: 1px solid var(--border);
417
- border-radius: var(--radius-md);
418
- margin-bottom: 20px;
419
- }
420
-
421
- /* ── Glass Card ── */
422
- .glass-card {
423
- background: var(--bg-card);
424
- border: 1px solid var(--border);
425
- border-radius: var(--radius-lg);
426
- padding: 20px;
427
- transition: border-color var(--transition);
428
- }
429
- .glass-card:hover { border-color: var(--border-bright); }
430
-
431
- /* ── Section Labels ── */
432
- .section-label {
433
- font-family: var(--font-mono);
434
- font-size: 0.65rem;
435
- font-weight: 500;
436
- letter-spacing: 0.2em;
437
- text-transform: uppercase;
438
- color: var(--text-muted);
439
- margin-bottom: 10px;
440
- display: flex;
441
- align-items: center;
442
- gap: 8px;
443
- }
444
- .section-label::before {
445
- content: '';
446
- display: inline-block;
447
- width: 6px; height: 6px;
448
- border-radius: 50%;
449
- background: var(--accent-blue);
450
- box-shadow: 0 0 6px var(--accent-blue);
451
- }
452
-
453
- /* ── Inputs ── */
454
- .gr-textbox textarea,
455
- .gr-textbox input,
456
- textarea, input[type="text"], input[type="password"] {
457
- background: rgba(255,255,255,0.04) !important;
458
- border: 1px solid var(--border) !important;
459
- border-radius: var(--radius-sm) !important;
460
- color: var(--text-primary) !important;
461
- font-family: var(--font-ui) !important;
462
- font-size: 0.9rem !important;
463
- transition: border-color var(--transition), box-shadow var(--transition) !important;
464
- }
465
- textarea:focus, input[type="text"]:focus, input[type="password"]:focus {
466
- border-color: var(--accent-blue) !important;
467
- box-shadow: 0 0 0 2px var(--glow-blue) !important;
468
- outline: none !important;
469
- }
470
-
471
- /* ── Labels ── */
472
- label, .gr-form label, span.svelte-1b6s6s {
473
- color: var(--text-secondary) !important;
474
- font-family: var(--font-ui) !important;
475
- font-size: 0.8rem !important;
476
- letter-spacing: 0.05em;
477
- }
478
-
479
- /* ── Primary Button ── */
480
- #submit-btn, button#submit-btn {
481
- background: linear-gradient(135deg, #1a3a6b 0%, #0f2447 100%) !important;
482
- border: 1px solid var(--accent-blue) !important;
483
- color: var(--accent-blue) !important;
484
- font-family: var(--font-mono) !important;
485
- font-size: 0.82rem !important;
486
- font-weight: 600 !important;
487
- letter-spacing: 0.15em !important;
488
- text-transform: uppercase !important;
489
- border-radius: var(--radius-sm) !important;
490
- padding: 12px 20px !important;
491
- cursor: pointer !important;
492
- transition: all var(--transition) !important;
493
- width: 100% !important;
494
- position: relative;
495
- overflow: hidden;
496
- }
497
- #submit-btn:hover {
498
- background: linear-gradient(135deg, #234d8f 0%, #162f5c 100%) !important;
499
- box-shadow: 0 0 20px var(--glow-blue), 0 0 40px rgba(74,144,255,0.08) !important;
500
- transform: translateY(-1px) !important;
501
- }
502
- #submit-btn:active { transform: translateY(0) !important; }
503
-
504
- /* ── Secondary / Nav Buttons ── */
505
- .nav-btn button {
506
- background: transparent !important;
507
- border: 1px solid transparent !important;
508
- color: var(--text-secondary) !important;
509
- font-family: var(--font-ui) !important;
510
- font-size: 0.8rem !important;
511
- letter-spacing: 0.05em !important;
512
- border-radius: var(--radius-sm) !important;
513
- padding: 7px 14px !important;
514
- cursor: pointer !important;
515
- transition: all var(--transition) !important;
516
- }
517
- .nav-btn button:hover {
518
- background: var(--bg-card-hover) !important;
519
- border-color: var(--border-bright) !important;
520
- color: var(--text-primary) !important;
521
- }
522
- .nav-btn.active button {
523
- background: rgba(74,144,255,0.12) !important;
524
- border-color: rgba(74,144,255,0.35) !important;
525
- color: var(--accent-blue) !important;
526
- }
527
-
528
- /* ── Danger / Logout button ── */
529
- .danger-btn button {
530
- background: rgba(239,68,68,0.08) !important;
531
- border: 1px solid rgba(239,68,68,0.25) !important;
532
- color: #f87171 !important;
533
- font-family: var(--font-mono) !important;
534
- font-size: 0.72rem !important;
535
- letter-spacing: 0.12em !important;
536
- text-transform: uppercase !important;
537
- border-radius: var(--radius-sm) !important;
538
- padding: 6px 14px !important;
539
- cursor: pointer !important;
540
- transition: all var(--transition) !important;
541
- }
542
- .danger-btn button:hover {
543
- background: rgba(239,68,68,0.18) !important;
544
- box-shadow: 0 0 12px rgba(239,68,68,0.15) !important;
545
- }
546
-
547
- /* ── Tabs ── */
548
- .tabs > .tab-nav {
549
- background: rgba(255,255,255,0.02) !important;
550
- border-bottom: 1px solid var(--border) !important;
551
- padding: 0 4px !important;
552
- gap: 2px !important;
553
- }
554
- .tabs > .tab-nav button {
555
- font-family: var(--font-mono) !important;
556
- font-size: 0.72rem !important;
557
- letter-spacing: 0.12em !important;
558
- text-transform: uppercase !important;
559
- color: var(--text-muted) !important;
560
- background: transparent !important;
561
- border: none !important;
562
- border-bottom: 2px solid transparent !important;
563
- padding: 10px 16px !important;
564
- border-radius: 0 !important;
565
- transition: all var(--transition) !important;
566
- }
567
- .tabs > .tab-nav button.selected {
568
- color: var(--accent-blue) !important;
569
- border-bottom-color: var(--accent-blue) !important;
570
- background: transparent !important;
571
- }
572
- .tabs > .tab-nav button:hover:not(.selected) {
573
- color: var(--text-primary) !important;
574
- }
575
-
576
- /* ── Telemetry panel ── */
577
- #telemetry-panel {
578
- background: rgba(0,0,0,0.35) !important;
579
- border: 1px solid var(--border) !important;
580
- border-radius: var(--radius-md) !important;
581
- padding: 16px !important;
582
- }
583
-
584
- .telem-grid {
585
- display: grid;
586
- grid-template-columns: 1fr 1fr;
587
- gap: 12px;
588
- }
589
-
590
- .telem-cell {
591
- padding: 10px 12px;
592
- background: rgba(255,255,255,0.025);
593
- border: 1px solid var(--border);
594
- border-radius: var(--radius-sm);
595
- }
596
-
597
- .telem-label {
598
- font-family: var(--font-mono);
599
- font-size: 0.6rem;
600
- letter-spacing: 0.18em;
601
- text-transform: uppercase;
602
- color: var(--text-muted);
603
- margin-bottom: 4px;
604
- }
605
-
606
- .telem-value {
607
- font-family: var(--font-mono);
608
- font-size: 1.1rem;
609
- font-weight: 700;
610
- color: var(--accent-cyan);
611
- }
612
-
613
- .telem-value.warn { color: var(--accent-amber); }
614
- .telem-value.good { color: var(--accent-green); }
615
- .telem-value.alert { color: #f87171; }
616
-
617
- /* ── XAI / Terminal ── */
618
- #xai-terminal textarea {
619
- font-family: var(--font-mono) !important;
620
- font-size: 0.78rem !important;
621
- line-height: 1.7 !important;
622
- background: rgba(0,0,0,0.5) !important;
623
- border: 1px solid var(--border) !important;
624
- border-radius: var(--radius-md) !important;
625
- color: var(--accent-green) !important;
626
- padding: 14px !important;
627
- resize: none !important;
628
- }
629
-
630
- /* ── Code block ── */
631
- #artifact-code .codemirror-wrapper,
632
- #artifact-code pre,
633
- #artifact-code code {
634
- font-family: var(--font-mono) !important;
635
- font-size: 0.8rem !important;
636
- background: rgba(0,0,0,0.45) !important;
637
- border-radius: var(--radius-md) !important;
638
- border: 1px solid var(--border) !important;
639
- }
640
-
641
- /* ── Chat History ── */
642
- #chat-history-list {
643
- display: flex;
644
- flex-direction: column;
645
- gap: 6px;
646
- }
647
-
648
- .hist-item {
649
- padding: 10px 14px;
650
- background: var(--bg-card);
651
- border: 1px solid var(--border);
652
- border-radius: var(--radius-sm);
653
- cursor: pointer;
654
- transition: all var(--transition);
655
- font-size: 0.82rem;
656
- color: var(--text-secondary);
657
- }
658
- .hist-item:hover {
659
- background: var(--bg-card-hover);
660
- border-color: var(--border-bright);
661
- color: var(--text-primary);
662
- }
663
- .hist-item .hist-ts {
664
- font-family: var(--font-mono);
665
- font-size: 0.6rem;
666
- color: var(--text-muted);
667
- margin-top: 2px;
668
- }
669
-
670
- /* ── Status badge ── */
671
- .status-dot {
672
- display: inline-block;
673
- width: 7px; height: 7px;
674
- border-radius: 50%;
675
- background: var(--accent-green);
676
- box-shadow: 0 0 6px var(--accent-green);
677
- animation: blink 2s ease-in-out infinite;
678
- }
679
-
680
- @keyframes blink {
681
- 0%, 100% { opacity: 1; }
682
- 50% { opacity: 0.35; }
683
- }
684
-
685
- /* ══════════════════════════════════════════════════════
686
- LOGIN — Full-screen split panel
687
- ══════════════════════════════════════════════════════ */
688
-
689
- /* Full-viewport host */
690
- #login-page {
691
- position: fixed !important;
692
- inset: 0 !important;
693
- z-index: 1000 !important;
694
- display: flex !important;
695
- overflow: hidden !important;
696
- background: var(--bg-void) !important;
697
- }
698
-
699
- /* ── Left showcase panel ── */
700
- #login-showcase {
701
- flex: 1 1 55%;
702
- position: relative;
703
- display: flex;
704
- flex-direction: column;
705
- justify-content: center;
706
- padding: 60px 64px;
707
- overflow: hidden;
708
- border-right: 1px solid var(--border);
709
- }
710
-
711
- /* animated mesh background */
712
- #login-showcase::before {
713
- content: '';
714
- position: absolute;
715
- inset: 0;
716
- background:
717
- radial-gradient(ellipse 70% 60% at 20% 40%, rgba(74,144,255,0.09) 0%, transparent 70%),
718
- radial-gradient(ellipse 50% 50% at 75% 70%, rgba(192,132,252,0.07) 0%, transparent 65%),
719
- radial-gradient(ellipse 40% 40% at 60% 20%, rgba(34,211,238,0.05) 0%, transparent 60%);
720
- pointer-events: none;
721
- }
722
-
723
- /* grid lines on showcase */
724
- #login-showcase::after {
725
- content: '';
726
- position: absolute;
727
- inset: 0;
728
- background-image:
729
- linear-gradient(rgba(74,144,255,0.045) 1px, transparent 1px),
730
- linear-gradient(90deg, rgba(74,144,255,0.045) 1px, transparent 1px);
731
- background-size: 52px 52px;
732
- pointer-events: none;
733
- }
734
-
735
- .showcase-content { position: relative; z-index: 1; }
736
-
737
- /* AMD badge */
738
- .amd-badge {
739
- display: inline-flex;
740
- align-items: center;
741
- gap: 8px;
742
- padding: 5px 12px 5px 8px;
743
- background: rgba(237,28,36,0.10);
744
- border: 1px solid rgba(237,28,36,0.28);
745
- border-radius: 99px;
746
- font-family: var(--font-mono);
747
- font-size: 0.64rem;
748
- font-weight: 600;
749
- letter-spacing: 0.18em;
750
- text-transform: uppercase;
751
- color: #f87171;
752
- margin-bottom: 32px;
753
- }
754
-
755
- .amd-badge .amd-dot {
756
- width: 7px; height: 7px;
757
- border-radius: 50%;
758
- background: #ed1c24;
759
- box-shadow: 0 0 8px rgba(237,28,36,0.7);
760
- animation: blink 1.8s ease-in-out infinite;
761
- }
762
-
763
- /* Showcase heading */
764
- .showcase-title {
765
- font-family: var(--font-display);
766
- font-size: clamp(2.4rem, 4vw, 3.4rem);
767
- font-weight: 900;
768
- letter-spacing: 0.08em;
769
- line-height: 1.05;
770
- margin-bottom: 6px;
771
- background: linear-gradient(130deg, #e8eaf0 0%, var(--accent-cyan) 55%, var(--accent-blue) 100%);
772
- -webkit-background-clip: text;
773
- -webkit-text-fill-color: transparent;
774
- background-clip: text;
775
- }
776
-
777
- .showcase-sub {
778
- font-family: var(--font-mono);
779
- font-size: 0.72rem;
780
- letter-spacing: 0.2em;
781
- text-transform: uppercase;
782
- color: var(--text-muted);
783
- margin-bottom: 44px;
784
- }
785
-
786
- /* Agent grid */
787
- .agent-grid {
788
- display: grid;
789
- grid-template-columns: 1fr 1fr;
790
- gap: 12px;
791
- margin-bottom: 44px;
792
- }
793
-
794
- .agent-card {
795
- padding: 14px 16px;
796
- background: rgba(255,255,255,0.025);
797
- border: 1px solid var(--border);
798
- border-radius: var(--radius-md);
799
- transition: border-color var(--transition), background var(--transition);
800
- }
801
-
802
- .agent-card:hover {
803
- background: rgba(255,255,255,0.045);
804
- border-color: var(--border-bright);
805
- }
806
-
807
- .agent-card .ac-role {
808
- font-family: var(--font-mono);
809
- font-size: 0.6rem;
810
- letter-spacing: 0.2em;
811
- text-transform: uppercase;
812
- margin-bottom: 3px;
813
- }
814
-
815
- .agent-card .ac-desc {
816
- font-size: 0.76rem;
817
- color: var(--text-muted);
818
- line-height: 1.4;
819
- }
820
-
821
- /* Hardware strip */
822
- .hw-strip {
823
- display: flex;
824
- gap: 28px;
825
- padding-top: 28px;
826
- border-top: 1px solid var(--border);
827
- }
828
-
829
- .hw-stat .hw-val {
830
- font-family: var(--font-mono);
831
- font-size: 1.1rem;
832
- font-weight: 700;
833
- color: var(--accent-cyan);
834
- }
835
-
836
- .hw-stat .hw-lbl {
837
- font-family: var(--font-mono);
838
- font-size: 0.58rem;
839
- letter-spacing: 0.18em;
840
- text-transform: uppercase;
841
- color: var(--text-muted);
842
- margin-top: 2px;
843
- }
844
-
845
- /* ── Right auth panel ── */
846
- #login-auth {
847
- flex: 0 0 420px;
848
- display: flex;
849
- flex-direction: column;
850
- justify-content: center;
851
- padding: 60px 48px;
852
- position: relative;
853
- }
854
-
855
- /* subtle inner glow */
856
- #login-auth::before {
857
- content: '';
858
- position: absolute;
859
- top: -80px; right: -80px;
860
- width: 320px; height: 320px;
861
- border-radius: 50%;
862
- background: radial-gradient(circle, rgba(74,144,255,0.06) 0%, transparent 70%);
863
- pointer-events: none;
864
- }
865
-
866
- .auth-eyebrow {
867
- font-family: var(--font-mono);
868
- font-size: 0.6rem;
869
- letter-spacing: 0.25em;
870
- text-transform: uppercase;
871
- color: var(--text-muted);
872
- margin-bottom: 6px;
873
- display: flex;
874
- align-items: center;
875
- gap: 8px;
876
- }
877
-
878
- .auth-eyebrow::before {
879
- content: '';
880
- display: block;
881
- width: 20px; height: 1px;
882
- background: var(--accent-blue);
883
- }
884
-
885
- .auth-heading {
886
- font-family: var(--font-display);
887
- font-size: 1.55rem;
888
- font-weight: 700;
889
- letter-spacing: 0.06em;
890
- color: var(--text-primary);
891
- margin-bottom: 6px;
892
- }
893
-
894
- .auth-tagline {
895
- font-size: 0.8rem;
896
- color: var(--text-muted);
897
- margin-bottom: 36px;
898
- line-height: 1.5;
899
- }
900
-
901
- /* field wrappers */
902
- .auth-field-wrap {
903
- margin-bottom: 16px;
904
- position: relative;
905
- }
906
-
907
- .auth-field-label {
908
- font-family: var(--font-mono);
909
- font-size: 0.62rem;
910
- letter-spacing: 0.18em;
911
- text-transform: uppercase;
912
- color: var(--text-muted);
913
- margin-bottom: 7px;
914
- }
915
-
916
- /* ── Login button ── */
917
- #login-btn button {
918
- background: linear-gradient(135deg, #1c4ed8 0%, #1e40af 100%) !important;
919
- border: 1px solid rgba(74,144,255,0.4) !important;
920
- color: #fff !important;
921
- font-family: var(--font-mono) !important;
922
- font-size: 0.8rem !important;
923
- font-weight: 600 !important;
924
- letter-spacing: 0.2em !important;
925
- text-transform: uppercase !important;
926
- border-radius: var(--radius-sm) !important;
927
- padding: 13px !important;
928
- width: 100% !important;
929
- cursor: pointer !important;
930
- transition: all var(--transition) !important;
931
- margin-top: 8px !important;
932
- position: relative !important;
933
- }
934
- #login-btn button:hover {
935
- background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%) !important;
936
- box-shadow: 0 0 28px rgba(74,144,255,0.3), 0 4px 24px rgba(0,0,0,0.4) !important;
937
- transform: translateY(-1px) !important;
938
- }
939
- #login-btn button:active {
940
- transform: translateY(0) !important;
941
- }
942
-
943
- /* ── Error flash ── */
944
- #login-error {
945
- font-family: var(--font-mono);
946
- font-size: 0.72rem;
947
- color: #fca5a5;
948
- background: rgba(239,68,68,0.07);
949
- border: 1px solid rgba(239,68,68,0.22);
950
- border-radius: var(--radius-sm);
951
- padding: 10px 14px;
952
- text-align: center;
953
- margin-top: 14px;
954
- letter-spacing: 0.05em;
955
- }
956
-
957
- /* footer line */
958
- .auth-footer {
959
- margin-top: 32px;
960
- padding-top: 20px;
961
- border-top: 1px solid var(--border);
962
- font-family: var(--font-mono);
963
- font-size: 0.6rem;
964
- color: var(--text-muted);
965
- letter-spacing: 0.12em;
966
- text-align: center;
967
- }
968
-
969
- /* responsive: stack on narrow viewports */
970
- @media (max-width: 900px) {
971
- #login-page { flex-direction: column; }
972
- #login-showcase { flex: 0 0 auto; padding: 36px 32px 28px; border-right: none; border-bottom: 1px solid var(--border); }
973
- #login-auth { flex: 0 0 auto; padding: 36px 32px; }
974
- .agent-grid { grid-template-columns: 1fr 1fr; }
975
- .hw-strip { gap: 18px; }
976
- }
977
-
978
- /* ── Settings page ── */
979
- .settings-row {
980
- display: flex;
981
- align-items: center;
982
- justify-content: space-between;
983
- padding: 14px 0;
984
- border-bottom: 1px solid var(--border);
985
- }
986
- .settings-row:last-child { border-bottom: none; }
987
- .settings-key {
988
- font-size: 0.85rem;
989
- color: var(--text-primary);
990
- }
991
- .settings-desc {
992
- font-size: 0.73rem;
993
- color: var(--text-muted);
994
- margin-top: 2px;
995
- }
996
-
997
- /* ── About page ── */
998
- .about-grid {
999
- display: grid;
1000
- grid-template-columns: 1fr 1fr;
1001
- gap: 16px;
1002
- margin-top: 20px;
1003
- }
1004
-
1005
- .about-card {
1006
- padding: 20px;
1007
- background: var(--bg-card);
1008
- border: 1px solid var(--border);
1009
- border-radius: var(--radius-md);
1010
- }
1011
-
1012
- .about-card .ac-icon {
1013
- font-size: 1.6rem;
1014
- margin-bottom: 10px;
1015
- }
1016
-
1017
- .about-card .ac-title {
1018
- font-family: var(--font-mono);
1019
- font-size: 0.75rem;
1020
- letter-spacing: 0.15em;
1021
- text-transform: uppercase;
1022
- color: var(--accent-blue);
1023
- margin-bottom: 6px;
1024
- }
1025
-
1026
- .about-card .ac-body {
1027
- font-size: 0.82rem;
1028
- color: var(--text-secondary);
1029
- line-height: 1.6;
1030
- }
1031
-
1032
- /* ── Progress bar ── */
1033
- .progress-bar-wrap {
1034
- width: 100%;
1035
- height: 3px;
1036
- background: rgba(255,255,255,0.05);
1037
- border-radius: 99px;
1038
- overflow: hidden;
1039
- margin-top: 6px;
1040
- }
1041
-
1042
- .progress-bar-fill {
1043
- height: 100%;
1044
- background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
1045
- border-radius: 99px;
1046
- transition: width 0.4s ease;
1047
- }
1048
-
1049
- /* ── Chatbot messages ── */
1050
- .message.user .message-bubble-border {
1051
- border-color: rgba(74,144,255,0.3) !important;
1052
- background: rgba(74,144,255,0.06) !important;
1053
- }
1054
- .message.bot .message-bubble-border {
1055
- border-color: rgba(61,255,160,0.2) !important;
1056
- background: rgba(61,255,160,0.04) !important;
1057
- }
1058
-
1059
- /* ensure all text nodes inside gradio use our fonts */
1060
- p, span, div, h1, h2, h3, h4, h5 {
1061
- font-family: var(--font-ui);
1062
- }
1063
- code, pre, .code { font-family: var(--font-mono); }
1064
-
1065
- /* ── Markdown blocks ── */
1066
- .prose p { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.7; }
1067
- .prose h1,
1068
- .prose h2,
1069
- .prose h3 { color: var(--text-primary); font-family: var(--font-display); }
1070
- .prose code { color: var(--accent-green); background: rgba(61,255,160,0.07); padding: 2px 5px; border-radius: 4px; }
1071
- .prose pre { background: rgba(0,0,0,0.45); border: 1px solid var(--border); border-radius: var(--radius-md); }
1072
 
1073
- /* ── Accordion ── */
1074
- details > summary {
1075
- font-family: var(--font-mono);
1076
- font-size: 0.72rem;
1077
- letter-spacing: 0.15em;
1078
- text-transform: uppercase;
1079
- color: var(--text-secondary);
1080
- cursor: pointer;
1081
- padding: 10px 0;
1082
- list-style: none;
1083
  }
1084
- details[open] > summary { color: var(--accent-blue); }
1085
-
1086
- /* ── Responsive ── */
1087
- @media (max-width: 768px) {
1088
- #sage-header .sage-logo { font-size: 1.8rem; }
1089
- .about-grid { grid-template-columns: 1fr; }
1090
- .telem-grid { grid-template-columns: 1fr; }
1091
- }
1092
- """
1093
-
1094
- # ─────────────────────────────────────────────────────────────────────────────
1095
- # 3. HTML HELPERS
1096
- # ─────────────────────────────────────────────────────────────────────────────
1097
-
1098
- def make_header_html() -> str:
1099
- badges = [
1100
- ("#4a90ff", "◆ Architect"),
1101
- ("#3dffa0", "◆ Implementer"),
1102
- ("#ffb347", "◆ Red-Team"),
1103
- ("#c084fc", "◆ Synthesizer"),
1104
- ]
1105
- badge_html = "".join(
1106
- f'<span class="agent-badge" style="color:{c};border-color:{c}40;">{l}</span>'
1107
- for c, l in badges
1108
- )
1109
- return f"""
1110
- <div id="sage-header">
1111
- <div class="sage-logo">SAGE-PRO</div>
1112
- <div class="sage-subtitle">
1113
- Strategic Adversarial Generative Engine
1114
- &nbsp;·&nbsp; 4-Agent Ensemble
1115
- &nbsp;·&nbsp; AMD Instinct MI300X (192 GB HBM3)
1116
- </div>
1117
- <div class="agent-badges">{badge_html}</div>
1118
- </div>
1119
- """
1120
-
1121
-
1122
- def make_telemetry_html(vram: float, nash: int, diverge: float, status: str = "STANDBY") -> str:
1123
- vram_cls = "warn" if vram > 185 else ("good" if vram > 0 else "")
1124
- div_cls = "alert" if diverge > 0.2 else ("warn" if diverge > 0.05 else "good" if diverge > 0 else "")
1125
- stat_color = "#3dffa0" if status == "CONVERGED" else ("#ffb347" if status == "RUNNING" else "#4a90ff")
1126
- vram_bar = min(int(vram / 192 * 100), 100)
1127
-
1128
- return f"""
1129
- <div id="telemetry-panel">
1130
- <div class="section-label">⬡ Live Telemetry</div>
1131
- <div class="telem-grid">
1132
- <div class="telem-cell">
1133
- <div class="telem-label">VRAM Peak</div>
1134
- <div class="telem-value {vram_cls}">{vram:.1f} <span style="font-size:0.65rem;opacity:.6;">GB</span></div>
1135
- <div class="progress-bar-wrap">
1136
- <div class="progress-bar-fill" style="width:{vram_bar}%;"></div>
1137
- </div>
1138
- </div>
1139
- <div class="telem-cell">
1140
- <div class="telem-label">Nash Cycles</div>
1141
- <div class="telem-value good">{nash}</div>
1142
- </div>
1143
- <div class="telem-cell">
1144
- <div class="telem-label">Divergence Idx</div>
1145
- <div class="telem-value {div_cls}">{diverge:.3f}</div>
1146
- </div>
1147
- <div class="telem-cell">
1148
- <div class="telem-label">Council Status</div>
1149
- <div class="telem-value" style="color:{stat_color};font-size:0.82rem;">{status}</div>
1150
- </div>
1151
- </div>
1152
- </div>
1153
- """
1154
-
1155
-
1156
- def make_login_html(error_msg: str = "") -> str:
1157
- err = f'<div id="login-error">⚠ {error_msg}</div>' if error_msg else ""
1158
- return f"""
1159
- <div id="login-wrapper" style="display:none">
1160
- <div class="login-title">SAGE-PRO</div>
1161
- <div class="login-sub">Clearance Required · Authorised Access Only</div>
1162
- {err}
1163
- </div>
1164
- """
1165
-
1166
-
1167
- def make_history_html(sessions: list[dict]) -> str:
1168
- if not sessions:
1169
- return '<div style="color:var(--text-muted);font-size:0.8rem;font-family:var(--font-mono);padding:16px 0;">No sessions yet. Submit your first query to begin.</div>'
1170
-
1171
- items = ""
1172
- for s in reversed(sessions):
1173
- items += f"""
1174
- <div class="hist-item">
1175
- <div style="display:flex;justify-content:space-between;align-items:center;">
1176
- <span>{s['query'][:52]}{'…' if len(s['query'])>52 else ''}</span>
1177
- <span style="font-family:var(--font-mono);font-size:0.62rem;color:var(--accent-blue);">N={s['nash']}</span>
1178
- </div>
1179
- <div class="hist-ts">{s['ts']}</div>
1180
- </div>
1181
- """
1182
- return f'<div id="chat-history-list">{items}</div>'
1183
-
1184
 
1185
- def make_about_html() -> str:
1186
- return """
1187
- <div style="padding: 8px 0;">
1188
- <div class="section-label">⬡ SAGE-PRO Intelligence</div>
1189
- <div style="font-size:0.9rem;color:var(--text-secondary);line-height:1.75;margin-bottom:20px;">
1190
- SAGE-PRO (Strategic Adversarial Generative Engine) is a next-generation AI coding
1191
- engine designed to run a co-resident 4-agent ensemble on the AMD Instinct MI300X
1192
- GPU with 192 GB of unified HBM3 memory — enabling model sizes and context windows
1193
- impossible on conventional hardware.
1194
- </div>
1195
- <div class="about-grid">
1196
- <div class="about-card">
1197
- <div class="ac-icon">🏛️</div>
1198
- <div class="ac-title" style="color:#4a90ff;">Architect</div>
1199
- <div class="ac-body">System topology design, interface contracts, and distributed-system constraint modelling.</div>
1200
- </div>
1201
- <div class="about-card">
1202
- <div class="ac-icon">⚙️</div>
1203
- <div class="ac-title" style="color:#3dffa0;">Implementer</div>
1204
- <div class="ac-body">High-performance code synthesis using torsion-aware profiling and optimal primitive selection.</div>
1205
- </div>
1206
- <div class="about-card">
1207
- <div class="ac-icon">🔴</div>
1208
- <div class="ac-title" style="color:#ffb347;">Red-Team</div>
1209
- <div class="ac-body">Adversarial probe: race conditions, fuzzing, boundary-case exploitation, CVE pattern matching.</div>
1210
- </div>
1211
- <div class="about-card">
1212
- <div class="ac-icon">⚖️</div>
1213
- <div class="ac-title" style="color:#c084fc;">Synthesizer</div>
1214
- <div class="ac-body">Nash Equilibrium Crucible — resolves multi-agent conflicts and seals the hardened final artifact.</div>
1215
- </div>
1216
- </div>
1217
- <div class="about-card" style="margin-top:16px;">
1218
- <div class="ac-title">Hardware Specification</div>
1219
- <div class="ac-body" style="font-family:var(--font-mono);">
1220
- <table style="width:100%;border-collapse:collapse;font-size:0.78rem;">
1221
- <tr><td style="padding:5px 0;color:var(--text-muted);">GPU</td><td>AMD Instinct MI300X</td></tr>
1222
- <tr><td style="padding:5px 0;color:var(--text-muted);">VRAM</td><td>192 GB HBM3</td></tr>
1223
- <tr><td style="padding:5px 0;color:var(--text-muted);">Bandwidth</td><td>5.3 TB/s</td></tr>
1224
- <tr><td style="padding:5px 0;color:var(--text-muted);">Agents</td><td>4 co-resident models</td></tr>
1225
- <tr><td style="padding:5px 0;color:var(--text-muted);">Context</td><td>512 K tokens (unified pool)</td></tr>
1226
- <tr><td style="padding:5px 0;color:var(--text-muted);">Nash Avg</td><td>4 cycles / Δ 0.02</td></tr>
1227
- </table>
1228
- </div>
1229
- </div>
1230
- </div>
1231
- """
1232
-
1233
-
1234
- def make_settings_html() -> str:
1235
- mode_label = "LIVE" if SAGE_BACKEND_URL else "MOCK / DEMO"
1236
- mode_color = "var(--accent-green)" if SAGE_BACKEND_URL else "var(--accent-amber)"
1237
- backend_display = SAGE_BACKEND_URL if SAGE_BACKEND_URL else "Not configured"
1238
- backend_color = "var(--accent-cyan)" if SAGE_BACKEND_URL else "var(--accent-amber)"
1239
-
1240
- return f"""
1241
- <div style="padding: 8px 0;">
1242
- <div class="section-label">⬡ Engine Configuration</div>
1243
- <div class="glass-card" style="margin-top:14px;">
1244
- <div class="settings-row">
1245
- <div>
1246
- <div class="settings-key">Backend Endpoint</div>
1247
- <div class="settings-desc">FastAPI URL for the SAGE inference cluster</div>
1248
- </div>
1249
- <code style="font-size:0.72rem;color:{backend_color};">{backend_display}</code>
1250
- </div>
1251
- <div class="settings-row">
1252
- <div>
1253
- <div class="settings-key">Nash Max Cycles</div>
1254
- <div class="settings-desc">Maximum equilibrium search iterations</div>
1255
- </div>
1256
- <code style="font-size:0.72rem;color:var(--accent-cyan);">8</code>
1257
- </div>
1258
- <div class="settings-row">
1259
- <div>
1260
- <div class="settings-key">Divergence Threshold</div>
1261
- <div class="settings-desc">Convergence cutoff (lower = stricter)</div>
1262
- </div>
1263
- <code style="font-size:0.72rem;color:var(--accent-cyan);">0.02</code>
1264
- </div>
1265
- <div class="settings-row">
1266
- <div>
1267
- <div class="settings-key">Stream Tokens</div>
1268
- <div class="settings-desc">Enable real-time token streaming to XAI trace</div>
1269
- </div>
1270
- <span style="color:var(--accent-green);font-family:var(--font-mono);font-size:0.78rem;">ENABLED</span>
1271
- </div>
1272
- <div class="settings-row">
1273
- <div>
1274
- <div class="settings-key">VRAM Soft-Cap</div>
1275
- <div class="settings-desc">Alert threshold before OOM risk</div>
1276
- </div>
1277
- <code style="font-size:0.72rem;color:var(--accent-amber);">188 GB</code>
1278
- </div>
1279
- <div class="settings-row">
1280
- <div>
1281
- <div class="settings-key">Red-Team Intensity</div>
1282
- <div class="settings-desc">Adversarial probe depth</div>
1283
- </div>
1284
- <code style="font-size:0.72rem;color:var(--accent-cyan);">HIGH (512 cases)</code>
1285
- </div>
1286
- <div class="settings-row">
1287
- <div>
1288
- <div class="settings-key">Mode</div>
1289
- <div class="settings-desc">Current execution mode</div>
1290
- </div>
1291
- <span style="color:{mode_color};font-family:var(--font-mono);font-size:0.78rem;">{mode_label}</span>
1292
- </div>
1293
- </div>
1294
- <div style="margin-top:16px;padding:12px;background:rgba(74,144,255,0.06);border:1px solid rgba(74,144,255,0.18);border-radius:8px;font-size:0.78rem;color:var(--text-secondary);">
1295
- <strong style="color:var(--accent-blue);">ℹ️ Backend Integration:</strong> Set the
1296
- <code>SAGE_BACKEND_URL</code> environment variable (e.g. <code>http://your-amd-droplet:8000</code>)
1297
- to connect to the live SAGE-PRO inference cluster. When not set, the UI runs in demo mode
1298
- with simulated agent responses.
1299
- </div>
1300
- </div>
1301
- """
1302
-
1303
-
1304
- # ─────────────────────────────────────────────────────────────────────────────
1305
- # 4. STREAMING ENGINE — real backend SSE + mock fallback
1306
- # ─────────────────────────────────────────────────────────────────────────────
1307
-
1308
-
1309
- async def _check_backend_health() -> bool:
1310
- """Return True if the SAGE backend is reachable."""
1311
- if not SAGE_BACKEND_URL:
1312
- return False
1313
  try:
1314
- async with httpx.AsyncClient(timeout=3.0) as client:
1315
- resp = await client.get(f"{SAGE_BACKEND_URL}/healthz")
1316
- return resp.status_code == 200
 
1317
  except Exception:
1318
- return False
1319
-
1320
-
1321
- async def _run_live_engine(query: str, sessions: list[dict]):
1322
- """
1323
- Stream from the real SAGE-PRO backend via SSE.
1324
- Yields (xai_log, telemetry_html, code_output, sessions) tuples.
1325
- """
1326
- log_buf = ""
1327
- vram = 0.0
1328
- nash = 0
1329
- diverge = 1.0
1330
- code_out = ""
1331
-
1332
- def _append(agent: str, msg: str) -> str:
1333
- nonlocal log_buf
1334
- line = f"[{datetime.now().strftime('%H:%M:%S')}] [{agent:12s}] {msg}"
1335
- log_buf = log_buf + line + "\n"
1336
- return log_buf
1337
-
1338
- _append("SYSTEM", "Connected to SAGE-PRO backend")
1339
- _append("SYSTEM", f"Endpoint: {SAGE_BACKEND_URL}/v1/sage/stream")
1340
- _append("SYSTEM", f"Query: {query!r}")
1341
- yield log_buf, make_telemetry_html(vram, nash, diverge, "CONNECTING"), code_out, sessions
1342
 
 
1343
  try:
1344
- async with httpx.AsyncClient(timeout=httpx.Timeout(180.0, connect=10.0)) as client:
1345
- async with client.stream(
1346
- "POST",
1347
- f"{SAGE_BACKEND_URL}/v1/sage/stream",
1348
- json={"query": query},
1349
- ) as resp:
1350
- resp.raise_for_status()
1351
-
1352
- async for raw_line in resp.aiter_lines():
1353
- # SSE format: lines starting with "data: "
1354
- line = raw_line.strip()
1355
- if not line or line.startswith(":"):
1356
- continue
1357
- if line.startswith("data: "):
1358
- line = line[6:]
1359
- elif line.startswith("data:"):
1360
- line = line[5:]
1361
- else:
1362
- continue
1363
-
1364
- try:
1365
- evt = json.loads(line)
1366
- except json.JSONDecodeError:
1367
- continue
1368
-
1369
- event_type = evt.get("event", "")
1370
- agent = evt.get("agent", "SYSTEM")
1371
- content = evt.get("content", "")
1372
- meta = evt.get("meta", {})
1373
-
1374
- # Update telemetry from meta
1375
- vram = meta.get("vram_gb", vram)
1376
- nash = meta.get("nash_cycle", nash)
1377
- diverge = meta.get("divergence", diverge)
1378
- status = meta.get("status", "RUNNING")
1379
-
1380
- if event_type == "error":
1381
- _append("ERROR", content)
1382
- yield log_buf, make_telemetry_html(vram, nash, diverge, "ERROR"), code_out, sessions
1383
- return
1384
-
1385
- if event_type in ("agent_start", "agent_token", "agent_done"):
1386
- _append(agent, content)
1387
- yield log_buf, make_telemetry_html(vram, nash, diverge, status), code_out, sessions
1388
-
1389
- if event_type == "pipeline_done":
1390
- code_out = content
1391
- _append("COUNCIL", "Artifact sealed by Nash Equilibrium Crucible ✓")
1392
- _append("SYSTEM", f"Session complete · VRAM {vram:.1f} GB · {nash} Nash cycles · Δ={diverge:.3f}")
1393
-
1394
- sessions = sessions + [{
1395
- "query": query,
1396
- "nash": nash,
1397
- "vram": round(vram, 1),
1398
- "ts": datetime.now().strftime("%Y-%m-%d %H:%M"),
1399
- "code": code_out,
1400
- }]
1401
- yield log_buf, make_telemetry_html(vram, nash, diverge, "CONVERGED"), code_out, sessions
1402
- return
1403
-
1404
- except httpx.HTTPStatusError as e:
1405
- _append("ERROR", f"Backend returned HTTP {e.response.status_code}")
1406
- yield log_buf, make_telemetry_html(vram, nash, diverge, "ERROR"), code_out, sessions
1407
- except Exception as e:
1408
- _append("ERROR", f"Backend connection failed: {e}")
1409
- yield log_buf, make_telemetry_html(vram, nash, diverge, "ERROR"), code_out, sessions
1410
-
1411
-
1412
- async def _run_mock_engine(query: str, sessions: list[dict]):
1413
- """
1414
- Mock fallback: simulates 4-agent deliberation locally.
1415
- Used when SAGE_BACKEND_URL is not set or backend is unreachable.
1416
- Yields (xai_log, telemetry_html, code_output, sessions) tuples.
1417
- """
1418
- log_buf = ""
1419
- vram = 0.0
1420
- nash = 0
1421
- diverge = 1.0
1422
- code_out = ""
1423
-
1424
- def _append(agent: str, msg: str) -> str:
1425
- nonlocal log_buf
1426
- line = f"[{datetime.now().strftime('%H:%M:%S')}] [{agent:12s}] {msg}"
1427
- log_buf = log_buf + line + "\n"
1428
- return log_buf
1429
-
1430
- # ── SYSTEM BOOT ──────────────────────────────────────────────────────────
1431
- _append("SYSTEM", "Initialising SAGE-PRO council (DEMO MODE) …")
1432
- _append("SYSTEM", f"Query received: {query!r}")
1433
- _append("SYSTEM", "Allocating HBM3 pool — dispatching agents …")
1434
- vram = random.uniform(12, 28)
1435
- yield log_buf, make_telemetry_html(vram, nash, diverge, "BOOTING"), code_out, sessions
1436
- await asyncio.sleep(0.5)
1437
-
1438
- # ── ARCHITECT ────────────────────────────────────────────────────────────
1439
- for step in PHASE_SCRIPTS["Architect"]:
1440
- _append("Architect", step)
1441
- vram = min(vram + random.uniform(18, 35), 192)
1442
- yield log_buf, make_telemetry_html(vram, nash, diverge, "RUNNING"), code_out, sessions
1443
- await asyncio.sleep(random.uniform(0.35, 0.65))
1444
-
1445
- # ── IMPLEMENTER ──────────────────────────────────────────────────────────
1446
- for step in PHASE_SCRIPTS["Implementer"]:
1447
- _append("Implementer", step)
1448
- vram = min(vram + random.uniform(8, 22), 192)
1449
- yield log_buf, make_telemetry_html(vram, nash, diverge, "RUNNING"), code_out, sessions
1450
- await asyncio.sleep(random.uniform(0.3, 0.55))
1451
-
1452
- # ── RED-TEAM ─────────────────────────────────────────────────────────────
1453
- for step in PHASE_SCRIPTS["Red-Team"]:
1454
- _append("Red-Team", step)
1455
- vram = min(vram + random.uniform(4, 12), 192)
1456
- yield log_buf, make_telemetry_html(vram, nash, diverge, "RUNNING"), code_out, sessions
1457
- await asyncio.sleep(random.uniform(0.28, 0.52))
1458
-
1459
- # ── SYNTHESIZER (Nash cycles) ─────────────────────────────────────────────
1460
- for i, step in enumerate(PHASE_SCRIPTS["Synthesizer"]):
1461
- _append("Synthesizer", step)
1462
- if i >= 1 and nash < 4:
1463
- nash += 1
1464
- diverge = max(round(diverge * random.uniform(0.28, 0.42), 3), 0.02)
1465
- vram = min(vram + random.uniform(2, 8), 192)
1466
- yield log_buf, make_telemetry_html(vram, nash, diverge, "CONVERGING"), code_out, sessions
1467
- await asyncio.sleep(random.uniform(0.4, 0.75))
1468
-
1469
- # ── FINAL ARTIFACT ────────────────────────────────────────────────────────
1470
- code_out = _pick_code(query)
1471
- vram = round(vram, 1)
1472
- _append("COUNCIL", "Artifact sealed and signed by Nash Equilibrium Crucible ✓")
1473
- _append("SYSTEM", f"Session complete · VRAM peak {vram:.1f} GB · {nash} Nash cycles · Δ={diverge:.3f}")
1474
-
1475
- sessions = sessions + [{
1476
- "query": query,
1477
- "nash": nash,
1478
- "vram": vram,
1479
- "ts": datetime.now().strftime("%Y-%m-%d %H:%M"),
1480
- "code": code_out,
1481
- }]
1482
-
1483
- yield log_buf, make_telemetry_html(vram, nash, diverge, "CONVERGED"), code_out, sessions
1484
-
1485
-
1486
- async def run_sage_engine(query: str, sessions: list[dict]):
1487
- """
1488
- Main entry point — tries the real backend first, falls back to mock.
1489
- Yields (xai_log, telemetry_html, code_output, sessions) tuples.
1490
- """
1491
- backend_live = await _check_backend_health()
1492
-
1493
- if backend_live:
1494
- async for result in _run_live_engine(query, sessions):
1495
- yield result
1496
- else:
1497
- async for result in _run_mock_engine(query, sessions):
1498
- yield result
1499
-
1500
-
1501
- # ─────────────────────────────────────────────────────────────────────────────
1502
- # 5. GRADIO APPLICATION
1503
- # ─────────────────────────────────────────────────────────────────────────────
1504
-
1505
- with gr.Blocks(
1506
- css=custom_css,
1507
- title="SAGE-PRO Crucible",
1508
- theme=gr.themes.Base(
1509
- primary_hue="blue",
1510
- neutral_hue="slate",
1511
- font=gr.themes.GoogleFont("Space Grotesk"),
1512
- ),
1513
- ) as demo:
1514
-
1515
- # ── Shared State ─────────────────────────────────────────────────────────
1516
- # True when user is authenticated
1517
- is_logged_in = gr.State(False)
1518
- # Logged-in username
1519
- current_user = gr.State("")
1520
- # List of session dicts
1521
- session_history = gr.State([])
1522
- # Active page: "crucible" | "history" | "settings" | "about"
1523
- active_page = gr.State("crucible")
1524
-
1525
- # ── LOGIN PAGE ───────────────────────────────────────────────────────────
1526
- with gr.Group(visible=False, elem_id="login-page") as login_page:
1527
-
1528
- # Left showcase column
1529
- gr.HTML("""
1530
- <div id="login-showcase">
1531
- <div class="showcase-content">
1532
-
1533
- <!-- AMD hackathon badge -->
1534
- <div class="amd-badge">
1535
- <span class="amd-dot"></span>
1536
- AMD Instinct MI300X &nbsp;·&nbsp; Developer Hackathon 2024
1537
- </div>
1538
 
1539
- <!-- Title -->
1540
- <div class="showcase-title">SAGE&#8209;PRO</div>
1541
- <div class="showcase-sub">
1542
- Strategic Adversarial Generative Engine
1543
- &nbsp;·&nbsp; 4&#8209;Agent Ensemble
1544
- </div>
1545
 
1546
- <!-- Agent grid -->
1547
- <div class="agent-grid">
1548
- <div class="agent-card">
1549
- <div class="ac-role" style="color:#4a90ff;">Architect</div>
1550
- <div class="ac-desc">System topology &amp; interface contract design</div>
1551
- </div>
1552
- <div class="agent-card">
1553
- <div class="ac-role" style="color:#3dffa0;">Implementer</div>
1554
- <div class="ac-desc">Torsion-aware code synthesis &amp; optimisation</div>
1555
- </div>
1556
- <div class="agent-card">
1557
- <div class="ac-role" style="color:#ffb347;">Red&#8209;Team</div>
1558
- <div class="ac-desc">Adversarial probing &amp; CVE-pattern fuzzing</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1559
  </div>
1560
- <div class="agent-card">
1561
- <div class="ac-role" style="color:#c084fc;">Synthesizer</div>
1562
- <div class="ac-desc">Nash Equilibrium Crucible artifact sealing</div>
1563
  </div>
1564
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1565
 
1566
- <!-- Hardware stats -->
1567
- <div class="hw-strip">
1568
- <div class="hw-stat">
1569
- <div class="hw-val">192 GB</div>
1570
- <div class="hw-lbl">HBM3 Pool</div>
1571
- </div>
1572
- <div class="hw-stat">
1573
- <div class="hw-val">5.3 TB/s</div>
1574
- <div class="hw-lbl">Bandwidth</div>
1575
- </div>
1576
- <div class="hw-stat">
1577
- <div class="hw-val">512 K</div>
1578
- <div class="hw-lbl">Context Tokens</div>
1579
- </div>
1580
- <div class="hw-stat">
1581
- <div class="hw-val">4</div>
1582
- <div class="hw-lbl">Co-Resident Agents</div>
 
 
 
 
 
 
 
 
 
 
 
 
1583
  </div>
1584
  </div>
 
 
 
1585
 
1586
- </div>
1587
- </div>
1588
- """)
1589
 
1590
- # Right auth column
1591
- with gr.Group(elem_id="login-auth"):
1592
- gr.HTML("""
1593
- <div class="auth-eyebrow">Secure Access Portal</div>
1594
- <div class="auth-heading">Welcome Back</div>
1595
- <div class="auth-tagline">
1596
- Authenticate to access the SAGE&#8209;PRO Crucible.<br>
1597
- Authorised personnel only.
1598
- </div>
1599
- """)
1600
- login_user = gr.Textbox(
1601
- label="USERNAME",
1602
- placeholder="Enter your username",
1603
- elem_classes=["auth-field-wrap"],
1604
- )
1605
- login_pass = gr.Textbox(
1606
- label="PASSWORD",
1607
- type="password",
1608
- placeholder="Enter your password",
1609
- elem_classes=["auth-field-wrap"],
1610
- )
1611
- login_btn = gr.Button(
1612
- "AUTHENTICATE →",
1613
- elem_id="login-btn",
1614
- variant="primary",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1615
  )
1616
- login_error = gr.HTML("")
1617
- gr.HTML("""
1618
- <div class="auth-footer">
1619
- SAGE-PRO &nbsp;·&nbsp; AMD Instinct MI300X &nbsp;·&nbsp; 192 GB HBM3<br>
1620
- <span style="color:#2d3748;">Unauthorised access is prohibited and monitored.</span>
1621
- </div>
1622
- """)
1623
-
1624
- # ── MAIN APPLICATION (hidden until login) ─────────────────────────────────
1625
- with gr.Group(visible=True, elem_id="main-app") as main_app:
1626
 
1627
- # ── Header ───────────────────────────────────────────────────────────
1628
- gr.HTML(make_header_html())
1629
-
1630
- # ── Navigation Bar ───────────────────────────────────────────────────
1631
- with gr.Row(elem_id="nav-bar"):
1632
- with gr.Column(scale=1, min_width=0):
1633
- with gr.Row():
1634
- nav_crucible = gr.Button("⚡ Crucible", elem_classes=["nav-btn", "active"])
1635
- nav_history = gr.Button("📋 Session History", elem_classes=["nav-btn"])
1636
- nav_settings = gr.Button("⚙️ Settings", elem_classes=["nav-btn"])
1637
- nav_about = gr.Button("ℹ️ About", elem_classes=["nav-btn"])
1638
- with gr.Column(scale=0, min_width=160):
1639
  with gr.Row():
1640
- user_display = gr.HTML('<span style="font-family:\'JetBrains Mono\',monospace;font-size:0.72rem;color:#8892a4;">● guest</span>')
1641
- logout_btn = gr.Button("Logout", elem_classes=["danger-btn"])
1642
-
1643
- # ════════════════════════════════════════════════════════════════════
1644
- # PAGE: CRUCIBLE
1645
- # ════════════════════════════════════════════════════════════════════
1646
- with gr.Group(visible=True) as page_crucible:
1647
- with gr.Row(equal_height=False):
1648
-
1649
- # ── LEFT: Command & Telemetry (40%) ──────────────────────────
1650
- with gr.Column(scale=4, min_width=300, elem_classes=["glass-card"]):
1651
-
1652
- gr.HTML('<div class="section-label">⬡ Strategic Query</div>')
1653
-
1654
- query_box = gr.Textbox(
1655
- label="",
1656
- placeholder=(
1657
- "Describe your engineering challenge …\n\n"
1658
- "e.g. 'Build a distributed rate limiter with Redis back-pressure'"
1659
- ),
1660
- lines=5,
1661
- elem_id="query-box",
1662
  )
 
 
 
1663
 
1664
- submit_btn = gr.Button(
1665
- "⚡ SUBMIT TO COUNCIL",
1666
- elem_id="submit-btn",
1667
- variant="primary",
1668
- )
1669
-
1670
- gr.HTML("<br>")
1671
-
1672
- # Example prompts
1673
- gr.HTML('<div class="section-label">⬡ Quick Examples</div>')
1674
- with gr.Row():
1675
- ex1 = gr.Button("Rate Limiter", size="sm", elem_classes=["nav-btn"])
1676
- ex2 = gr.Button("Async Job Queue", size="sm", elem_classes=["nav-btn"])
1677
- ex3 = gr.Button("Circuit Breaker", size="sm", elem_classes=["nav-btn"])
1678
- with gr.Row():
1679
- ex4 = gr.Button("LRU Cache", size="sm", elem_classes=["nav-btn"])
1680
- ex5 = gr.Button("Pub/Sub Engine", size="sm", elem_classes=["nav-btn"])
1681
- ex6 = gr.Button("Load Balancer", size="sm", elem_classes=["nav-btn"])
1682
-
1683
- gr.HTML("<br>")
1684
-
1685
- # Telemetry
1686
- telemetry_html = gr.HTML(
1687
- make_telemetry_html(0.0, 0, 1.0, "STANDBY"),
1688
- elem_id="telemetry-panel",
1689
- )
1690
-
1691
- # ── RIGHT: Workspace (60%) ────────────────────────────────────
1692
- with gr.Column(scale=6, min_width=400):
1693
-
1694
- workspace_tabs = gr.Tabs(elem_id="workspace-tabs")
1695
-
1696
- with workspace_tabs:
1697
-
1698
- # Tab 1: Final Artifact
1699
- with gr.Tab("🏺 Final Artifact", id=0):
1700
- artifact_code = gr.Code(
1701
- label="",
1702
- language="python",
1703
- value="# Awaiting council deliberation …\n# Submit a query to activate SAGE-PRO.\n",
1704
- interactive=False,
1705
- elem_id="artifact-code",
1706
- lines=34,
1707
- )
1708
-
1709
- # Tab 2: XAI Trace
1710
- with gr.Tab("📡 XAI Trace", id=1):
1711
- xai_trace = gr.Textbox(
1712
- label="",
1713
- value="System standing by. Submit a query to begin deliberation.\n",
1714
- lines=34,
1715
- interactive=False,
1716
- elem_id="xai-terminal",
1717
-
1718
- )
1719
-
1720
- # Tab 3: Council Summary (appears after run)
1721
- with gr.Tab("📊 Council Summary", id=2):
1722
- summary_md = gr.Markdown(
1723
- value="""
1724
- > *Council has not yet convened. Submit a query to generate a summary.*
1725
- """,
1726
- elem_id="summary-panel",
1727
- )
1728
-
1729
- # ════════════════════════════════════════════════════════════════════
1730
- # PAGE: SESSION HISTORY
1731
- # ════════════════════════════════════════════════════════════════════
1732
- with gr.Group(visible=False) as page_history:
1733
- gr.HTML('<div class="section-label" style="margin-bottom:16px;">⬡ Session History</div>')
1734
- with gr.Row():
1735
- with gr.Column(scale=3):
1736
- history_html = gr.HTML(make_history_html([]))
1737
- clear_hist_btn = gr.Button("🗑 Clear History", elem_classes=["danger-btn"])
1738
- with gr.Column(scale=7):
1739
- history_detail = gr.Markdown(
1740
- value="> *Select a session from the list to view its artifact.*"
1741
- )
1742
-
1743
- # ════════════════════════════════════════════════════════════════════
1744
- # PAGE: SETTINGS
1745
- # ════════════════════════════════════════════════════════════════════
1746
- with gr.Group(visible=False) as page_settings:
1747
- gr.HTML(make_settings_html())
1748
-
1749
- # ════════════════════════════════════════════════════════════════════
1750
- # PAGE: ABOUT
1751
- # ════════════════════════════════════════════════════════════════════
1752
- with gr.Group(visible=False) as page_about:
1753
- gr.HTML(make_about_html())
1754
-
1755
- # ── Status Bar ───────────────────────────────────────────────────────
1756
- gr.HTML("""
1757
- <div style="margin-top:24px;padding:10px 16px;
1758
- background:rgba(0,0,0,0.3);
1759
- border:1px solid rgba(255,255,255,0.05);
1760
- border-radius:8px;
1761
- display:flex;justify-content:space-between;align-items:center;">
1762
- <div style="display:flex;align-items:center;gap:8px;">
1763
- <span class="status-dot"></span>
1764
- <span style="font-family:'JetBrains Mono',monospace;font-size:0.65rem;color:#4a5568;letter-spacing:0.15em;">
1765
- AMD Instinct MI300X · 192 GB HBM3 · 4-Agent Ensemble · HF Space
1766
- </span>
1767
- </div>
1768
- <span style="font-family:'JetBrains Mono',monospace;font-size:0.62rem;color:#2d3748;">
1769
- SAGE-PRO v2.0 · github.com/realruneett/Sage
1770
- </span>
1771
- </div>
1772
- """)
1773
-
1774
- # ─────────────────────────────────────────────────────────────────────
1775
- # 6. EVENT HANDLERS
1776
- # ─────────────────────────────────────────────────────────────────────
1777
-
1778
- # ── Login ────────────────────────────────────────────────────────────────
1779
- def handle_login(username: str, password: str):
1780
- username = (username or "").strip()
1781
- password = (password or "").strip()
1782
-
1783
- if username in VALID_USERS and VALID_USERS[username] == password:
1784
- user_html = (
1785
- f'<span style="font-family:\'JetBrains Mono\',monospace;font-size:0.72rem;'
1786
- f'color:#3dffa0;">● {username}</span>'
1787
- )
1788
- return (
1789
- gr.update(visible=False), # login_page
1790
- gr.update(visible=True), # main_app
1791
- True, # is_logged_in
1792
- username, # current_user
1793
- gr.update(value=user_html), # user_display
1794
- gr.update(value=""), # login_error
1795
- )
1796
- else:
1797
- err = (
1798
- '<div id="login-error">'
1799
- '⚠&nbsp; Invalid credentials. Access denied.'
1800
- '</div>'
1801
- )
1802
- return (
1803
- gr.update(visible=True),
1804
- gr.update(visible=False),
1805
- False,
1806
- "",
1807
- gr.update(),
1808
- gr.update(value=err),
1809
- )
1810
-
1811
- login_btn.click(
1812
- fn=handle_login,
1813
- inputs=[login_user, login_pass],
1814
- outputs=[login_page, main_app, is_logged_in, current_user, user_display, login_error],
1815
- )
1816
- # Also submit on Enter in password field
1817
- login_pass.submit(
1818
- fn=handle_login,
1819
- inputs=[login_user, login_pass],
1820
- outputs=[login_page, main_app, is_logged_in, current_user, user_display, login_error],
1821
- )
1822
-
1823
- # ── Logout ───────────────────────────────────────────────────────────────
1824
- def handle_logout():
1825
- guest_html = '<span style="font-family:\'JetBrains Mono\',monospace;font-size:0.72rem;color:#8892a4;">● guest</span>'
1826
- return (
1827
- gr.update(visible=True), # login_page
1828
- gr.update(visible=False), # main_app
1829
- False, # is_logged_in
1830
- "", # current_user
1831
- gr.update(value=guest_html),# user_display
1832
- gr.update(value=""), # login_error
1833
- )
1834
-
1835
- logout_btn.click(
1836
- fn=handle_logout,
1837
- outputs=[login_page, main_app, is_logged_in, current_user, user_display, login_error],
1838
- )
1839
-
1840
- # ── Navigation ───────────────────────────────────────────────────────────
1841
- def _nav(target: str):
1842
- return (
1843
- gr.update(visible=(target == "crucible")),
1844
- gr.update(visible=(target == "history")),
1845
- gr.update(visible=(target == "settings")),
1846
- gr.update(visible=(target == "about")),
1847
- target,
1848
  )
1849
-
1850
- nav_outputs = [page_crucible, page_history, page_settings, page_about, active_page]
1851
-
1852
- nav_crucible.click(fn=lambda: _nav("crucible"), outputs=nav_outputs)
1853
- nav_history.click( fn=lambda: _nav("history"), outputs=nav_outputs)
1854
- nav_settings.click(fn=lambda: _nav("settings"), outputs=nav_outputs)
1855
- nav_about.click( fn=lambda: _nav("about"), outputs=nav_outputs)
1856
-
1857
- # ── Quick-example filler ─────────────────────────────────────────────────
1858
- for btn, txt in [
1859
- (ex1, "Build a distributed sliding-window rate limiter with Redis back-pressure"),
1860
- (ex2, "Implement an async job queue with priority lanes and dead-letter handling"),
1861
- (ex3, "Design a circuit breaker with exponential back-off and half-open probing"),
1862
- (ex4, "Create a thread-safe LRU cache with TTL eviction and O(1) operations"),
1863
- (ex5, "Build a pub/sub engine supporting wildcard topics and backpressure"),
1864
- (ex6, "Implement a consistent-hashing load balancer with health-check failover"),
1865
- ]:
1866
- btn.click(fn=lambda t=txt: t, outputs=[query_box])
1867
-
1868
- # ── Clear history ─────────────────────────────────────────────────────────
1869
- def clear_history():
1870
- return [], make_history_html([])
1871
-
1872
- clear_hist_btn.click(fn=clear_history, outputs=[session_history, history_html])
1873
-
1874
- # ── Main streaming handler ─────────────────────────────────────────────────
1875
- async def handle_submit(query: str, sessions: list[dict]):
1876
- """
1877
- Async streaming generator that drives all UI updates simultaneously.
1878
- Yields:
1879
- 0 xai_trace — terminal log textbox
1880
- 1 telemetry_html — live telemetry panel
1881
- 2 artifact_code — final code output
1882
- 3 session_history — updated sessions list
1883
- 4 history_html — rendered history sidebar
1884
- 5 summary_md — council summary markdown
1885
- 6 workspace_tabs — switch to XAI tab on start, artifact on finish
1886
- """
1887
- query = (query or "").strip()
1888
- if not query:
1889
- yield (
1890
- "⚠ Please enter a query before submitting.\n",
1891
- make_telemetry_html(0, 0, 1.0, "STANDBY"),
1892
- gr.update(), sessions,
1893
- make_history_html(sessions),
1894
- gr.update(), gr.update(),
1895
- )
1896
- return
1897
-
1898
- # Immediately switch to XAI Trace tab so user sees live logs
1899
- yield (
1900
- "Connecting to SAGE-PRO council …\n",
1901
- make_telemetry_html(0, 0, 1.0, "BOOTING"),
1902
- gr.update(),
1903
- sessions,
1904
- make_history_html(sessions),
1905
- gr.update(),
1906
- gr.update(selected=1), # switch to tab id=1 (XAI Trace)
1907
  )
1908
-
1909
- final_sessions = sessions
1910
-
1911
- async for log, telem, code, new_sessions in run_sage_engine(query, sessions):
1912
- final_sessions = new_sessions
1913
- yield (
1914
- log,
1915
- telem,
1916
- code if code else gr.update(),
1917
- final_sessions,
1918
- make_history_html(final_sessions),
1919
- gr.update(),
1920
- gr.update(selected=1),
1921
- )
1922
-
1923
- # Switch to Final Artifact tab and populate summary
1924
- last = final_sessions[-1] if final_sessions else {}
1925
- summary = f"""
1926
- ## 🏺 Council Summary
1927
-
1928
- | Metric | Value |
1929
- |--------|-------|
1930
- | Query | `{query[:80]}` |
1931
- | VRAM Peak | **{last.get('vram', 0):.1f} GB** / 192 GB |
1932
- | Nash Cycles | **{last.get('nash', 0)}** |
1933
- | Divergence Index | **0.020** (converged ✓) |
1934
- | Agents Engaged | Architect · Implementer · Red-Team · Synthesizer |
1935
- | Timestamp | {last.get('ts', '—')} |
1936
-
1937
- ### Agent Contributions
1938
-
1939
- - 🏛️ **Architect** — Topology locked, interface contracts defined
1940
- - ⚙️ **Implementer** — Core algorithm drafted, torsion-optimised
1941
- - 🔴 **Red-Team** — 2 vulnerabilities found & patched (overflow, deadlock)
1942
- - ⚖️ **Synthesizer** — Nash equilibrium achieved in 4 cycles (Δ=0.020)
1943
-
1944
- > **Artifact Status:** Hardened · Signed · Ready for production deployment
1945
- """
1946
- yield (
1947
- log,
1948
- telem,
1949
- code,
1950
- final_sessions,
1951
- make_history_html(final_sessions),
1952
- summary,
1953
- gr.update(selected=0), # switch to Final Artifact tab
1954
  )
1955
 
1956
- submit_btn.click(
1957
- fn=handle_submit,
1958
- inputs=[query_box, session_history],
1959
- outputs=[
1960
- xai_trace,
1961
- telemetry_html,
1962
- artifact_code,
1963
- session_history,
1964
- history_html,
1965
- summary_md,
1966
- workspace_tabs,
1967
- ],
1968
- )
1969
-
1970
- # Also trigger on Enter in query box
1971
- query_box.submit(
1972
- fn=handle_submit,
1973
- inputs=[query_box, session_history],
1974
- outputs=[
1975
- xai_trace,
1976
- telemetry_html,
1977
- artifact_code,
1978
- session_history,
1979
- history_html,
1980
- summary_md,
1981
- workspace_tabs,
1982
- ],
1983
- )
1984
-
1985
 
1986
- # ─────────────────────────────────────────────────────────────────────────────
1987
- # 7. LAUNCH
1988
- # ──────────────────────────────────────────────────��──────────────────────────
1989
 
1990
  if __name__ == "__main__":
1991
- demo.queue(max_size=20)
1992
- demo.launch(
1993
- server_name="0.0.0.0", # required for HF Spaces
1994
- server_port=7860, # default HF Spaces port
1995
-
 
1996
  show_error=True,
1997
  favicon_path=None,
1998
  )
 
1
+ """SAGE — Strategic Adversarial Generative Engine · Chat Interface"""
2
+ from __future__ import annotations
3
+ import os, json, re, asyncio
4
+ from pathlib import Path
 
 
 
 
 
 
 
 
 
 
 
 
5
  from datetime import datetime
 
 
6
  import httpx
 
7
  import gradio as gr
8
 
9
+ SAGE_API_URL = os.environ.get("SAGE_API_URL", "http://localhost:8000").rstrip("/")
10
+ HISTORY_FILE = Path(os.environ.get("HISTORY_FILE", "/data/sage_history.json"))
11
+ MAX_CONTEXT = 20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # ── Agent display styles ──────────────────────────────────────────────────────
14
+ AGENT_STYLES = {
15
+ "SYSTEM": {"icon": "○", "color": "#444444"},
16
+ "Architect": {"icon": "◈", "color": "#FF6B1A"},
17
+ "Implementer": {"icon": "◆", "color": "#FF8C42"},
18
+ "Red-Team": {"icon": "◉", "color": "#FF4444"},
19
+ "Synthesizer": {"icon": "◇", "color": "#FFA040"},
20
+ "COUNCIL": {"icon": "◎", "color": "#22C55E"},
21
+ "ERROR": {"icon": "✕", "color": "#FF4444"},
 
22
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ # ── Persistent history ────────────────────────────────────────────────────────
25
+ def load_history():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  try:
27
+ HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
28
+ if HISTORY_FILE.exists():
29
+ data = json.loads(HISTORY_FILE.read_text())
30
+ return data if isinstance(data, list) else []
31
  except Exception:
32
+ pass
33
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ def save_history(history):
36
  try:
37
+ HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
38
+ HISTORY_FILE.write_text(json.dumps(history, indent=2, ensure_ascii=False))
39
+ except Exception:
40
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ def backend_live():
43
+ try:
44
+ r = httpx.get(f"{SAGE_API_URL}/healthz", timeout=3.0)
45
+ return r.status_code == 200
46
+ except Exception:
47
+ return False
48
 
49
+ # ── HTML builders ─────────────────────────────────────────────────────────────
50
+ def format_answer(text):
51
+ text = re.sub(r'```(\w*)\n(.*?)```',
52
+ lambda m: f'<pre><code class="lang-{m.group(1)}">{m.group(2)}</code></pre>',
53
+ text, flags=re.DOTALL)
54
+ text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
55
+ text = text.replace('\n', '<br>')
56
+ return text
57
+
58
+ def render_history(history):
59
+ if not history:
60
+ return '''<div id="empty-state">
61
+ <div style="font-size:48px;color:#181818;margin-bottom:16px;">◎</div>
62
+ <div style="font-size:20px;font-weight:700;color:#fff;margin-bottom:8px;">Ask SAGE anything</div>
63
+ <div style="font-size:13px;color:#333;max-width:440px;margin:0 auto;line-height:1.8;">
64
+ Simple queries load one fast model. Complex problems trigger the full council.
65
+ </div>
66
+ </div>'''
67
+ html = ""
68
+ for msg in history:
69
+ role = msg.get("role")
70
+ text = msg.get("text", "")
71
+ deliberation = msg.get("deliberation", [])
72
+ if role == "user":
73
+ html += f'''<div class="msg-user">
74
+ <div class="msg-user-bubble">{text}</div>
75
+ </div>'''
76
+ elif role == "sage":
77
+ delib_html = ""
78
+ if deliberation:
79
+ lines = ""
80
+ for agent, content in deliberation:
81
+ st = AGENT_STYLES.get(agent, {"icon":"○","color":"#444"})
82
+ lines += f'''<div class="d-line">
83
+ <span class="d-agent" style="color:{st["color"]};">{st["icon"]} {agent}</span>
84
+ <span class="d-msg">{content[:120]}</span>
85
+ </div>'''
86
+ delib_html = f'<div class="deliberation">{lines}</div>'
87
+ html += f'''<div class="msg-sage">
88
+ <div class="msg-sage-avatar">◎</div>
89
+ <div class="msg-sage-content">
90
+ {delib_html}
91
+ <div class="msg-sage-bubble">{format_answer(text)}</div>
92
+ </div>
93
+ </div>'''
94
+ return html + '<div id="chat-bottom"></div>'
95
+
96
+ def navbar_html(live):
97
+ color = "#22C55E" if live else "#818CF8"
98
+ mode = "LIVE" if live else "DEMO"
99
+ return f'''<div id="sage-navbar">
100
+ <div style="display:flex;align-items:center;gap:10px;">
101
+ <span style="font-size:20px;font-weight:800;color:#fff;letter-spacing:-0.03em;">SAGE</span>
102
+ <span style="width:5px;height:5px;border-radius:50%;background:#FF6B1A;"></span>
103
+ <span style="font-family:monospace;font-size:10px;color:#2A2A2A;letter-spacing:0.1em;">STRATEGIC ADVERSARIAL GENERATIVE ENGINE</span>
104
  </div>
105
+ <div style="display:flex;align-items:center;gap:10px;">
106
+ <span style="font-family:monospace;font-size:10px;background:rgba(255,107,26,0.08);border:1px solid rgba(255,107,26,0.2);color:{color};padding:3px 10px;border-radius:4px;">{mode}</span>
107
+ <span style="font-size:11px;color:#2A2A2A;">AMD MI300X · 192 GB HBM3</span>
108
  </div>
109
+ </div>'''
110
+
111
+ CSS = """
112
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@300;400;500&display=swap');
113
+ :root{--bg:#080808;--bg2:#0D0D0D;--card:#111111;--bd:#1E1E1E;--bd2:#2A2A2A;--tx:#BBBBBB;--txd:#333333;--txh:#FFFFFF;--ora:#FF6B1A;--ora2:#FF8C42;--green:#22C55E;--red:#FF4444;--F:'Inter',sans-serif;--M:'JetBrains Mono',monospace;}
114
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
115
+ body,.gradio-container{background:var(--bg)!important;font-family:var(--F)!important;color:var(--tx)!important;}
116
+ .gradio-container{max-width:100%!important;padding:0!important;}
117
+ footer,.built-with{display:none!important;}
118
+ #sage-navbar{background:rgba(8,8,8,0.97);border-bottom:1px solid var(--bd);padding:0 32px;height:56px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:999;backdrop-filter:blur(12px);}
119
+ #chat-wrap{max-width:860px;margin:0 auto;padding:24px 16px 160px;}
120
+ #empty-state{text-align:center;padding:80px 24px;}
121
+ .msg-user{display:flex;justify-content:flex-end;margin:16px 0 4px;}
122
+ .msg-user-bubble{background:var(--ora);color:#000;border-radius:18px 18px 4px 18px;padding:12px 18px;max-width:72%;font-size:14px;line-height:1.65;font-weight:500;}
123
+ .msg-sage{display:flex;gap:12px;margin:4px 0 16px;align-items:flex-start;}
124
+ .msg-sage-avatar{width:32px;height:32px;border-radius:50%;background:rgba(255,107,26,0.12);border:1px solid rgba(255,107,26,0.25);display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;margin-top:2px;color:#FF6B1A;}
125
+ .msg-sage-content{flex:1;max-width:calc(100% - 44px);}
126
+ .deliberation{padding:6px 0 6px 12px;border-left:2px solid #1A1A1A;margin-bottom:8px;}
127
+ .d-line{display:flex;gap:8px;align-items:baseline;margin:3px 0;opacity:0.35;font-family:var(--M);font-size:11px;}
128
+ .d-line:hover{opacity:0.65;transition:opacity 0.15s;}
129
+ .d-agent{font-size:10px;font-weight:600;letter-spacing:0.06em;min-width:88px;flex-shrink:0;}
130
+ .d-msg{color:#444;}
131
+ .msg-sage-bubble{background:var(--card);border:1px solid var(--bd);border-radius:4px 18px 18px 18px;padding:14px 18px;font-size:14px;line-height:1.75;color:var(--txh);white-space:pre-wrap;word-wrap:break-word;}
132
+ .msg-sage-bubble code{font-family:var(--M);background:#0D0D0D;border:1px solid var(--bd);border-radius:4px;padding:2px 6px;font-size:12px;color:#FF8C42;}
133
+ .msg-sage-bubble pre{background:#0A0A0A;border:1px solid var(--bd);border-radius:8px;padding:14px 16px;overflow-x:auto;margin:10px 0;font-family:var(--M);font-size:12px;line-height:1.7;color:#E8A87C;}
134
+ #input-bar{position:fixed;bottom:0;left:0;right:0;background:rgba(8,8,8,0.97);border-top:1px solid var(--bd);padding:14px 24px 18px;backdrop-filter:blur(12px);z-index:998;}
135
+ #input-inner{max-width:860px;margin:0 auto;display:flex;gap:10px;align-items:flex-end;}
136
+ #msg-input textarea{background:var(--card)!important;border:1px solid var(--bd)!important;border-radius:12px!important;color:var(--txh)!important;font-family:var(--F)!important;font-size:14px!important;padding:12px 16px!important;resize:none!important;min-height:48px!important;max-height:160px!important;transition:border-color 0.2s!important;}
137
+ #msg-input textarea:focus{border-color:var(--ora)!important;outline:none!important;box-shadow:0 0 0 3px rgba(255,107,26,0.08)!important;}
138
+ #send-btn button{background:var(--ora)!important;color:#000!important;border:none!important;border-radius:12px!important;height:48px!important;width:48px!important;font-size:18px!important;font-weight:700!important;padding:0!important;min-width:48px!important;transition:all 0.2s!important;}
139
+ #send-btn button:hover{background:var(--ora2)!important;transform:scale(1.05)!important;}
140
+ #send-btn button:disabled{background:var(--bd2)!important;color:var(--txd)!important;transform:none!important;}
141
+ #clear-btn button{background:transparent!important;border:1px solid #2A2A2A!important;color:#444!important;border-radius:8px!important;font-size:11px!important;padding:4px 12px!important;font-family:var(--M)!important;}
142
+ #clear-btn button:hover{border-color:var(--red)!important;color:var(--red)!important;}
143
+ ::-webkit-scrollbar{width:4px;}
144
+ ::-webkit-scrollbar-track{background:var(--bg);}
145
+ ::-webkit-scrollbar-thumb{background:var(--bd2);border-radius:4px;}
146
+ .tabitem{background:transparent!important;border:none!important;}
147
+ """
148
 
149
+ SCROLL_JS = """
150
+ <script>
151
+ function scrollToBottom(){
152
+ var el = document.getElementById('chat-bottom');
153
+ if(el) el.scrollIntoView({behavior:'smooth'});
154
+ }
155
+ setTimeout(scrollToBottom, 100);
156
+ </script>
157
+ """
158
+
159
+ # ── Core chat function ────────────────────────────────────────────────────────
160
+ async def chat(user_msg, history):
161
+ if not user_msg or not user_msg.strip():
162
+ yield render_history(history) + SCROLL_JS, history, ""
163
+ return
164
+
165
+ history = history or []
166
+ history.append({"role": "user", "text": user_msg.strip()})
167
+
168
+ # Show user message immediately with thinking dots
169
+ thinking = render_history(history) + '''
170
+ <div class="msg-sage">
171
+ <div class="msg-sage-avatar">◎</div>
172
+ <div class="msg-sage-content">
173
+ <div style="padding:10px 0;display:flex;gap:4px;align-items:center;">
174
+ <div style="width:6px;height:6px;border-radius:50%;background:#FF6B1A;animation:bounce 1.4s infinite ease-in-out;"></div>
175
+ <div style="width:6px;height:6px;border-radius:50%;background:#FF6B1A;animation:bounce 1.4s infinite ease-in-out;animation-delay:0.2s;"></div>
176
+ <div style="width:6px;height:6px;border-radius:50%;background:#FF6B1A;animation:bounce 1.4s infinite ease-in-out;animation-delay:0.4s;"></div>
177
+ </div>
178
  </div>
179
  </div>
180
+ <style>@keyframes bounce{0%,80%,100%{transform:scale(0.6);opacity:0.35;}40%{transform:scale(1);opacity:1;}}</style>
181
+ ''' + SCROLL_JS
182
+ yield thinking, history, ""
183
 
184
+ live = backend_live()
185
+ deliberation = []
186
+ final_answer = ""
187
 
188
+ if not live:
189
+ await asyncio.sleep(0.5)
190
+ final_answer = f"[DEMO MODE] Backend offline. Your query was: **{user_msg}**\n\nConnect the backend at {SAGE_API_URL} to get real responses."
191
+ else:
192
+ try:
193
+ async with httpx.AsyncClient(timeout=httpx.Timeout(300.0, connect=10.0)) as client:
194
+ async with client.stream(
195
+ "POST",
196
+ f"{SAGE_API_URL}/v1/sage/stream",
197
+ json={"query": user_msg.strip(), "max_cycles": 1},
198
+ ) as resp:
199
+ resp.raise_for_status()
200
+ current_delib = list(deliberation)
201
+ async for raw_line in resp.aiter_lines():
202
+ line = raw_line.strip()
203
+ if not line or line.startswith(":"): continue
204
+ if line.startswith("data: "): line = line[6:]
205
+ elif line.startswith("data:"): line = line[5:]
206
+ else: continue
207
+ try:
208
+ evt = json.loads(line)
209
+ except Exception:
210
+ continue
211
+ event_type = evt.get("event", "")
212
+ agent = evt.get("agent", "SYSTEM")
213
+ content = evt.get("content", "")
214
+ if event_type == "pipeline_done":
215
+ final_answer = content
216
+ break
217
+ elif event_type == "error":
218
+ final_answer = f"Pipeline error: {content}"
219
+ break
220
+ elif content:
221
+ current_delib.append((agent, content))
222
+ deliberation = current_delib
223
+ # Yield live deliberation update
224
+ partial = render_history(history[:-1] + [{"role":"user","text":user_msg}]) + f'''
225
+ <div class="msg-sage">
226
+ <div class="msg-sage-avatar">◎</div>
227
+ <div class="msg-sage-content">
228
+ <div class="deliberation">{"".join(
229
+ f'<div class="d-line"><span class="d-agent" style="color:{AGENT_STYLES.get(a,{"color":"#444"})["color"]};">{AGENT_STYLES.get(a,{"icon":"○"})["icon"]} {a}</span><span class="d-msg">{c[:120]}</span></div>'
230
+ for a,c in current_delib[-6:]
231
+ )}</div>
232
+ <div style="padding:8px 0;display:flex;gap:4px;align-items:center;">
233
+ <div style="width:5px;height:5px;border-radius:50%;background:#FF6B1A;opacity:0.6;"></div>
234
+ <div style="width:5px;height:5px;border-radius:50%;background:#FF6B1A;opacity:0.6;"></div>
235
+ <div style="width:5px;height:5px;border-radius:50%;background:#FF6B1A;opacity:0.6;"></div>
236
+ </div>
237
+ </div>
238
+ </div>''' + SCROLL_JS
239
+ yield partial, history, ""
240
+ except Exception as e:
241
+ final_answer = f"Connection error: {str(e)}"
242
+
243
+ if not final_answer:
244
+ final_answer = "No response received from pipeline."
245
+
246
+ history.append({"role": "sage", "text": final_answer, "deliberation": deliberation})
247
+ save_history(history)
248
+ yield render_history(history) + SCROLL_JS, history, ""
249
+
250
+
251
+ def clear_chat():
252
+ save_history([])
253
+ return render_history([]), [], ""
254
+
255
+
256
+ # ── Build UI ──────────────────────────────────────────────────────────────────
257
+ def build():
258
+ live = backend_live()
259
+ init_history = load_history()
260
+
261
+ with gr.Blocks(
262
+ title="SAGE — Strategic Adversarial Generative Engine",
263
+ css=CSS,
264
+ theme=gr.themes.Base(),
265
+ ) as demo:
266
+ history_state = gr.State(init_history)
267
+
268
+ gr.HTML(navbar_html(live))
269
+
270
+ with gr.Column(elem_id="chat-wrap"):
271
+ chat_display = gr.HTML(
272
+ value=render_history(init_history) + SCROLL_JS
273
  )
 
 
 
 
 
 
 
 
 
 
274
 
275
+ with gr.Row(elem_id="input-bar"):
276
+ with gr.Column(elem_id="input-inner"):
 
 
 
 
 
 
 
 
 
 
277
  with gr.Row():
278
+ msg_input = gr.Textbox(
279
+ placeholder="Ask anything — code, analysis, creative writing, life advice...",
280
+ show_label=False,
281
+ lines=1,
282
+ max_lines=6,
283
+ elem_id="msg-input",
284
+ scale=9,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  )
286
+ send_btn = gr.Button("↑", elem_id="send-btn", scale=1)
287
+ with gr.Row():
288
+ clear_btn = gr.Button("Clear conversation", elem_id="clear-btn", size="sm")
289
 
290
+ send_btn.click(
291
+ fn=chat,
292
+ inputs=[msg_input, history_state],
293
+ outputs=[chat_display, history_state, msg_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  )
295
+ msg_input.submit(
296
+ fn=chat,
297
+ inputs=[msg_input, history_state],
298
+ outputs=[chat_display, history_state, msg_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  )
300
+ clear_btn.click(
301
+ fn=clear_chat,
302
+ outputs=[chat_display, history_state, msg_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  )
304
 
305
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
 
 
 
307
 
308
  if __name__ == "__main__":
309
+ port = int(os.environ.get("PORT", "7860"))
310
+ app = build()
311
+ app.launch(
312
+ server_name="0.0.0.0",
313
+ server_port=port,
314
+ share=False,
315
  show_error=True,
316
  favicon_path=None,
317
  )
requirements.txt CHANGED
@@ -1,2 +1,6 @@
1
- gradio>=4.29.0
2
  httpx>=0.27.0
 
 
 
 
 
1
+ gradio==5.29.0
2
  httpx>=0.27.0
3
+ numpy>=1.24.0
4
+ matplotlib>=3.7.0
5
+ pyyaml>=6.0.0
6
+ requests>=2.28.0
validate.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Quick validation script for SAGE app.py (v3.0)"""
3
+ import ast
4
+ import sys
5
+ import os
6
+
7
+ # Fix Windows console encoding
8
+ if sys.platform == "win32":
9
+ os.environ.setdefault("PYTHONIOENCODING", "utf-8")
10
+ try:
11
+ sys.stdout.reconfigure(encoding="utf-8")
12
+ except Exception:
13
+ pass
14
+
15
+
16
+ def validate():
17
+ with open("app.py", "r", encoding="utf-8") as f:
18
+ code = f.read()
19
+
20
+ ok = True
21
+
22
+ # Syntax
23
+ try:
24
+ ast.parse(code)
25
+ print("[OK] Syntax: PASS")
26
+ except SyntaxError as e:
27
+ print(f"[FAIL] Syntax: FAIL -- {e}")
28
+ return 1
29
+
30
+ # Imports
31
+ required = ["gradio", "httpx", "matplotlib", "numpy"]
32
+ for mod in required:
33
+ if f"import {mod}" in code or f"from {mod}" in code:
34
+ print(f"[OK] Import {mod}: FOUND")
35
+ else:
36
+ print(f"[WARN] Import {mod}: NOT FOUND")
37
+
38
+ # Key classes
39
+ classes = [
40
+ "SageConfig", "BackendClient", "SAGEProAPIClient",
41
+ "OllamaClient", "DemoClient", "VisualizationEngine",
42
+ ]
43
+ for cls in classes:
44
+ if f"class {cls}" in code:
45
+ print(f"[OK] Class {cls}: FOUND")
46
+ else:
47
+ print(f"[FAIL] Class {cls}: MISSING")
48
+ ok = False
49
+
50
+ # v3.0 Feature methods
51
+ v3_features = [
52
+ ("render_code", "Glass Renderer"),
53
+ ("vision_debug", "Vision Debugger"),
54
+ ("toggle_dreamer", "Chaos Dreamer Toggle"),
55
+ ("get_dreamer_stats", "Chaos Dreamer Stats"),
56
+ ("_chat_stream", "Streaming Chat (split)"),
57
+ ]
58
+ for method, label in v3_features:
59
+ if method in code:
60
+ print(f"[OK] v3 Feature [{label}]: FOUND")
61
+ else:
62
+ print(f"[FAIL] v3 Feature [{label}]: MISSING")
63
+ ok = False
64
+
65
+ # v3.0 UI Tabs
66
+ v3_tabs = ["Live Preview", "Vision Debugger", "Chaos Dreamer"]
67
+ for tab in v3_tabs:
68
+ if tab in code:
69
+ print(f"[OK] UI Tab [{tab}]: FOUND")
70
+ else:
71
+ print(f"[FAIL] UI Tab [{tab}]: MISSING")
72
+ ok = False
73
+
74
+ if ok:
75
+ print("\nAll validations passed! (v3.0)")
76
+ return 0
77
+ else:
78
+ print("\nSome validations FAILED.")
79
+ return 1
80
+
81
+
82
+ if __name__ == "__main__":
83
+ sys.exit(validate())