Commit ·
8f3e429
1
Parent(s): 2d5f620
feat: SAGE-PRO Gradio frontend with tier-based model routing
Browse files- Dockerfile +12 -9
- README.md +19 -7
- app.py +282 -1963
- requirements.txt +5 -1
- validate.py +83 -0
Dockerfile
CHANGED
|
@@ -1,21 +1,24 @@
|
|
| 1 |
-
# SAGE-PRO
|
| 2 |
-
# Runs independently from the backend; connects via SAGE_BACKEND_URL
|
| 3 |
-
|
| 4 |
FROM python:3.11-slim
|
| 5 |
|
| 6 |
WORKDIR /app
|
| 7 |
|
| 8 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
COPY requirements.txt .
|
| 10 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 11 |
|
| 12 |
-
# Copy
|
| 13 |
COPY app.py .
|
| 14 |
-
COPY README.md .
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
ENV
|
| 18 |
-
ENV
|
|
|
|
| 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:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
-
python_version: "3.11"
|
| 9 |
pinned: true
|
|
|
|
| 10 |
---
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 4 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1074 |
-
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
|
| 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 |
-
· 4-Agent Ensemble
|
| 1115 |
-
· 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 |
-
|
| 1186 |
-
|
| 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 |
-
|
| 1315 |
-
|
| 1316 |
-
|
|
|
|
| 1317 |
except Exception:
|
| 1318 |
-
|
| 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 |
-
|
| 1345 |
-
|
| 1346 |
-
|
| 1347 |
-
|
| 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 · Developer Hackathon 2024
|
| 1537 |
-
</div>
|
| 1538 |
|
| 1539 |
-
|
| 1540 |
-
|
| 1541 |
-
|
| 1542 |
-
|
| 1543 |
-
|
| 1544 |
-
|
| 1545 |
|
| 1546 |
-
|
| 1547 |
-
|
| 1548 |
-
|
| 1549 |
-
<
|
| 1550 |
-
|
| 1551 |
-
|
| 1552 |
-
|
| 1553 |
-
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
| 1557 |
-
<div
|
| 1558 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1559 |
</div>
|
| 1560 |
-
<div
|
| 1561 |
-
<
|
| 1562 |
-
<
|
| 1563 |
</div>
|
| 1564 |
-
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1565 |
|
| 1566 |
-
|
| 1567 |
-
|
| 1568 |
-
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
-
|
| 1575 |
-
|
| 1576 |
-
|
| 1577 |
-
|
| 1578 |
-
|
| 1579 |
-
|
| 1580 |
-
|
| 1581 |
-
|
| 1582 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1583 |
</div>
|
| 1584 |
</div>
|
|
|
|
|
|
|
|
|
|
| 1585 |
|
| 1586 |
-
|
| 1587 |
-
|
| 1588 |
-
""
|
| 1589 |
|
| 1590 |
-
|
| 1591 |
-
|
| 1592 |
-
|
| 1593 |
-
|
| 1594 |
-
|
| 1595 |
-
|
| 1596 |
-
|
| 1597 |
-
|
| 1598 |
-
|
| 1599 |
-
""
|
| 1600 |
-
|
| 1601 |
-
|
| 1602 |
-
|
| 1603 |
-
|
| 1604 |
-
|
| 1605 |
-
|
| 1606 |
-
|
| 1607 |
-
|
| 1608 |
-
|
| 1609 |
-
|
| 1610 |
-
|
| 1611 |
-
|
| 1612 |
-
|
| 1613 |
-
|
| 1614 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1615 |
)
|
| 1616 |
-
login_error = gr.HTML("")
|
| 1617 |
-
gr.HTML("""
|
| 1618 |
-
<div class="auth-footer">
|
| 1619 |
-
SAGE-PRO · AMD Instinct MI300X · 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 |
-
|
| 1628 |
-
|
| 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 |
-
|
| 1641 |
-
|
| 1642 |
-
|
| 1643 |
-
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
|
| 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 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 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 |
-
'⚠ 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 |
-
|
| 1851 |
-
|
| 1852 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1992 |
-
|
| 1993 |
-
|
| 1994 |
-
|
| 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
|
| 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())
|