Spaces:
Running
Running
Commit ·
1d68c54
1
Parent(s): 794de26
Added UI
Browse files- UI_MIGRATION.md +192 -0
- app.py +2 -0
- ui/app/components/AgentTrustMonitor.tsx +111 -0
- ui/app/components/ArchitecturePipeline.tsx +48 -0
- ui/app/components/ExecutionLog.tsx +87 -0
- ui/app/components/HeroCanvas.tsx +117 -0
- ui/app/components/MetricsGrid.tsx +104 -0
- ui/app/components/SimCanvas.tsx +195 -0
- ui/app/components/SystemModules.tsx +85 -0
- ui/app/globals.css +384 -349
- ui/app/page.tsx +266 -97
UI_MIGRATION.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# UI Migration Guide: index.html → Next.js
|
| 2 |
+
|
| 3 |
+
## ✅ COMPLETED
|
| 4 |
+
|
| 5 |
+
### 1. **CSS Design Tokens**
|
| 6 |
+
- ✅ Created design token mapping in `globals.css`
|
| 7 |
+
- All color variables, fonts, and spacing preserved
|
| 8 |
+
- Ready for selective integration
|
| 9 |
+
|
| 10 |
+
### 2. **JSX Conversion**
|
| 11 |
+
- ✅ Created `/ui/app/page-landing.tsx`
|
| 12 |
+
- Converted HTML → React JSX components
|
| 13 |
+
- `class` → `className`
|
| 14 |
+
- Canvas refs with `useEffect` lifecycle
|
| 15 |
+
- All sections: hero, overview, simulation, architecture, metrics
|
| 16 |
+
|
| 17 |
+
### 3. **Canvas Initialization**
|
| 18 |
+
- ✅ Added `useRef` hooks for canvas elements
|
| 19 |
+
- ✅ Wrapped canvas logic in `useEffect`
|
| 20 |
+
- ✅ TODO placeholders for actual drawing logic
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 📋 INTEGRATION STEPS
|
| 25 |
+
|
| 26 |
+
### Step 1: Apply index.html CSS to globals.css
|
| 27 |
+
|
| 28 |
+
Your `globals.css` has old styles. Update it with index.html theme:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
# Option A: Keep existing structure, add index.html CSS as override
|
| 32 |
+
# Option B: Replace entire globals.css with index.html styles (RECOMMENDED)
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
The `page-landing.tsx` expects these CSS classes:
|
| 36 |
+
- Layout: `nav`, `section`, `footer`
|
| 37 |
+
- Components: `card`, `btn-primary`, `btn-secondary`, `metric-block`
|
| 38 |
+
- Utilities: `anim-1`, `divider`, `hero-stats`
|
| 39 |
+
|
| 40 |
+
### Step 2: Choose Integration Strategy
|
| 41 |
+
|
| 42 |
+
**Option A: Use Landing Page Separately** ✅ (SIMPLEST)
|
| 43 |
+
```
|
| 44 |
+
/app/page.tsx → Your existing interactive app
|
| 45 |
+
/app/landing.tsx → New static landing (page-landing.tsx)
|
| 46 |
+
/app/layout.tsx → Wrap both
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
**Option B: Replace page.tsx Completely** (if landing is the main page)
|
| 50 |
+
```
|
| 51 |
+
/app/page.tsx → Replace with page-landing.tsx
|
| 52 |
+
/app/mission/page.tsx → Move existing components to subfolder
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
**Option C: Hybrid** (recommended)
|
| 56 |
+
```
|
| 57 |
+
/app/page.tsx → Dynamic switcher (landing vs mission)
|
| 58 |
+
/app/landing.tsx → page-landing.tsx
|
| 59 |
+
/app/components/* → Existing components
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## 🎨 CSS INTEGRATION
|
| 65 |
+
|
| 66 |
+
### Required Classes from index.html:
|
| 67 |
+
|
| 68 |
+
```css
|
| 69 |
+
/* Navigation */
|
| 70 |
+
nav, .nav-logo, .nav-links, .nav-badge
|
| 71 |
+
|
| 72 |
+
/* Hero Section */
|
| 73 |
+
#hero, .hero-content, .hero-tag, .hero-ctas
|
| 74 |
+
.btn-primary, .btn-secondary
|
| 75 |
+
|
| 76 |
+
/* Cards */
|
| 77 |
+
.cards-grid, .card, .card-id, .card-title, .card-footer
|
| 78 |
+
|
| 79 |
+
/* Simulation */
|
| 80 |
+
.sim-wrapper, .sim-topbar, .sim-body, .sim-panel
|
| 81 |
+
.agent-row, .trust-bar-bg, .trust-bar-fill
|
| 82 |
+
.metric-block, .metric-value
|
| 83 |
+
|
| 84 |
+
/* Architecture */
|
| 85 |
+
.arch-node, .arch-flow
|
| 86 |
+
|
| 87 |
+
/* Metrics */
|
| 88 |
+
.metric-block, .metric-grid
|
| 89 |
+
|
| 90 |
+
/* Footer */
|
| 91 |
+
footer, .footer-left, .footer-right
|
| 92 |
+
|
| 93 |
+
/* Animations */
|
| 94 |
+
@keyframes pulse-dot, fadeInUp
|
| 95 |
+
.anim-1, .anim-2, .anim-3, .anim-4, .anim-5
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## 🖼️ CANVAS MIGRATION
|
| 101 |
+
|
| 102 |
+
The new `page-landing.tsx` has placeholders:
|
| 103 |
+
|
| 104 |
+
```typescript
|
| 105 |
+
function initHeroCanvas(canvas: HTMLCanvasElement) {
|
| 106 |
+
// TODO: Copy canvas.js logic from index.html
|
| 107 |
+
// The neural network grid visualization
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
function initSimCanvas(canvas: HTMLCanvasElement) {
|
| 111 |
+
// TODO: Copy canvas.js logic from index.html
|
| 112 |
+
// The orchestrator network visualization
|
| 113 |
+
}
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
**To migrate canvas logic:**
|
| 117 |
+
|
| 118 |
+
1. Find the inline `<script>` block in `index.html` (lines ~1710+)
|
| 119 |
+
2. Extract the two IIFE functions:
|
| 120 |
+
- Hero canvas: Neural network grid
|
| 121 |
+
- Sim canvas: Orchestrator network
|
| 122 |
+
3. Paste into `initHeroCanvas()` and `initSimCanvas()`
|
| 123 |
+
4. Update canvas size handling for React
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## ✨ FEATURES NOT YET IMPLEMENTED
|
| 128 |
+
|
| 129 |
+
These are hooks for future work - marked with `TODO`:
|
| 130 |
+
|
| 131 |
+
- [ ] Canvas drawing logic (detailed in "Canvas Migration" above)
|
| 132 |
+
- [ ] Button click handlers (Launch Mission, Read Docs)
|
| 133 |
+
- [ ] Live metrics updates (currently static)
|
| 134 |
+
- [ ] Specialist status animation
|
| 135 |
+
- [ ] Episode log scrolling (right panel)
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## 📁 FILE STRUCTURE AFTER MIGRATION
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
+
/ui
|
| 143 |
+
├── app/
|
| 144 |
+
│ ├── layout.tsx (unchanged)
|
| 145 |
+
│ ├── page.tsx (existing - interactive mode)
|
| 146 |
+
│ ├── page-landing.tsx (NEW - landing page)
|
| 147 |
+
│ ├── globals.css (UPDATED with index.html styles)
|
| 148 |
+
│ ├── components/
|
| 149 |
+
│ │ ├── Landing.tsx
|
| 150 |
+
│ │ ├── MissionControl.tsx
|
| 151 |
+
│ │ └── ...
|
| 152 |
+
│ └── hooks/
|
| 153 |
+
│ └── useSentinel.ts
|
| 154 |
+
├── index.html (ARCHIVE - no longer needed after migration)
|
| 155 |
+
├── .env (unchanged)
|
| 156 |
+
└── package.json (unchanged)
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## 🚀 QUICK START
|
| 162 |
+
|
| 163 |
+
1. **Update globals.css** with index.html styles (or use the CSS I provided)
|
| 164 |
+
2. **Keep page-landing.tsx** as new file (or rename to page.tsx if replacing)
|
| 165 |
+
3. **Migrate canvas logic** from index.html `<script>` section
|
| 166 |
+
4. **Test**: Visit `http://localhost:3000/` or `/landing` depending on routing
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## ✅ DONE vs 🚧 TODO
|
| 171 |
+
|
| 172 |
+
| Part | Status | Notes |
|
| 173 |
+
|------|--------|-------|
|
| 174 |
+
| HTML → JSX | ✅ | page-landing.tsx ready |
|
| 175 |
+
| CSS Variables | ✅ | Design tokens extracted |
|
| 176 |
+
| Layout/Structure | ✅ | All sections preserved |
|
| 177 |
+
| Canvas Setup | ✅ | useRef + useEffect ready |
|
| 178 |
+
| Canvas Drawing | 🚧 | TODO - copy from index.html |
|
| 179 |
+
| Button Handlers | 🚧 | TODO - implement event handlers |
|
| 180 |
+
| Live Updates | 🚧 | TODO - connect to API/state |
|
| 181 |
+
| Animations | ✅ | @keyframes preserved in CSS |
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## NEXT: What You Need to Do
|
| 186 |
+
|
| 187 |
+
1. **Decision**: Keep separate landing page or replace main page?
|
| 188 |
+
2. **CSS**: Merge index.html styles into `globals.css`
|
| 189 |
+
3. **Canvas**: Copy drawing logic from `index.html` `<script>` into the TODO functions
|
| 190 |
+
4. **Test**: Run `npm run dev` and verify visual parity
|
| 191 |
+
|
| 192 |
+
Need help with any step? 🚀
|
app.py
CHANGED
|
@@ -222,6 +222,8 @@ class StepRequest(BaseModel):
|
|
| 222 |
# Endpoints
|
| 223 |
# ---------------------------------------------------------------------------
|
| 224 |
|
|
|
|
|
|
|
| 225 |
@app.get("/health")
|
| 226 |
def health():
|
| 227 |
return {
|
|
|
|
| 222 |
# Endpoints
|
| 223 |
# ---------------------------------------------------------------------------
|
| 224 |
|
| 225 |
+
|
| 226 |
+
|
| 227 |
@app.get("/health")
|
| 228 |
def health():
|
| 229 |
return {
|
ui/app/components/AgentTrustMonitor.tsx
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import type { Observation, EventItem } from "../lib/types";
|
| 4 |
+
|
| 5 |
+
const AGENT_ROLES: Record<string, string> = {
|
| 6 |
+
S0: "COORDINATOR", S1: "OBSERVER", S2: "EXECUTOR", S3: "FLAGGED", S4: "VALIDATOR",
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
type Props = {
|
| 10 |
+
observation: Observation | null;
|
| 11 |
+
trustDeltas: Record<string, number>;
|
| 12 |
+
activeSpec: string | null;
|
| 13 |
+
events: EventItem[];
|
| 14 |
+
running: boolean;
|
| 15 |
+
totalReward: number;
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
function trustClass(t: number) {
|
| 19 |
+
if (t >= 0.65) return "high";
|
| 20 |
+
if (t >= 0.35) return "mid";
|
| 21 |
+
return "low";
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
function trustColor(t: number) {
|
| 25 |
+
if (t >= 0.65) return "var(--green)";
|
| 26 |
+
if (t >= 0.35) return "var(--amber)";
|
| 27 |
+
return "var(--red)";
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
export default function AgentTrustMonitor({
|
| 31 |
+
observation, trustDeltas, activeSpec, events, running, totalReward,
|
| 32 |
+
}: Props) {
|
| 33 |
+
const agents = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
|
| 34 |
+
const trust = observation?.trust_snapshot ?? {};
|
| 35 |
+
const lastReward = observation?.last_reward ?? 0;
|
| 36 |
+
|
| 37 |
+
// detect adversarial from events
|
| 38 |
+
const poisonedAgents = new Set(
|
| 39 |
+
events.filter(e => e.outcome === "poisoned" || e.outcome === "blocked")
|
| 40 |
+
.map(e => e.specialist).filter(Boolean)
|
| 41 |
+
);
|
| 42 |
+
|
| 43 |
+
// mean trust
|
| 44 |
+
const vals = agents.map(id => trust[id] ?? 0.5);
|
| 45 |
+
const meanTrust = vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0.5;
|
| 46 |
+
const advRatio = agents.length ? Math.round((poisonedAgents.size / agents.length) * 100) : 0;
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<div className="sim-panel">
|
| 50 |
+
<div className="sim-panel-label">AGENT TRUST REGISTRY</div>
|
| 51 |
+
|
| 52 |
+
{agents.map(id => {
|
| 53 |
+
const t = trust[id] ?? 0.5;
|
| 54 |
+
const delta = trustDeltas[id] ?? 0;
|
| 55 |
+
const isActive = activeSpec === id;
|
| 56 |
+
const isAdv = poisonedAgents.has(id);
|
| 57 |
+
const role = AGENT_ROLES[id] ?? "AGENT";
|
| 58 |
+
|
| 59 |
+
return (
|
| 60 |
+
<div className="agent-row" key={id}>
|
| 61 |
+
<div className="agent-header">
|
| 62 |
+
<span className={`agent-id ${isActive ? "active" : isAdv ? "adversarial" : "neutral"}`}>
|
| 63 |
+
{id} // {isAdv ? "⚠ FLAGGED" : role}
|
| 64 |
+
</span>
|
| 65 |
+
<span className="agent-trust-val" style={{ color: trustColor(t) }}>
|
| 66 |
+
{t.toFixed(2)}
|
| 67 |
+
</span>
|
| 68 |
+
</div>
|
| 69 |
+
<div className="trust-bar-bg">
|
| 70 |
+
<div
|
| 71 |
+
className={`trust-bar-fill ${trustClass(t)}`}
|
| 72 |
+
style={{ width: `${Math.max(2, t * 100)}%` }}
|
| 73 |
+
/>
|
| 74 |
+
</div>
|
| 75 |
+
<div className="agent-state" style={isAdv ? { color: "var(--red)" } : {}}>
|
| 76 |
+
{isAdv
|
| 77 |
+
? "STATE: ADVERSARIAL // ISOLATED"
|
| 78 |
+
: isActive
|
| 79 |
+
? "STATE: ACTIVE // DELEGATING"
|
| 80 |
+
: `STATE: READY // Δ ${delta >= 0 ? "+" : ""}${delta.toFixed(3)}`
|
| 81 |
+
}
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
);
|
| 85 |
+
})}
|
| 86 |
+
|
| 87 |
+
<div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid rgba(0,200,255,0.08)" }}>
|
| 88 |
+
<div className="sim-metric-row">
|
| 89 |
+
<span className="sim-metric-label">MEAN TRUST</span>
|
| 90 |
+
<span className="sim-metric-val c">{meanTrust.toFixed(3)}</span>
|
| 91 |
+
</div>
|
| 92 |
+
<div className="sim-metric-row">
|
| 93 |
+
<span className="sim-metric-label">ADV RATIO</span>
|
| 94 |
+
<span className="sim-metric-val r">{advRatio}%</span>
|
| 95 |
+
</div>
|
| 96 |
+
<div className="sim-metric-row">
|
| 97 |
+
<span className="sim-metric-label">STEP REWARD</span>
|
| 98 |
+
<span className={`sim-metric-val ${lastReward >= 0 ? "g" : "r"}`}>
|
| 99 |
+
{lastReward >= 0 ? "+" : ""}{lastReward.toFixed(3)}
|
| 100 |
+
</span>
|
| 101 |
+
</div>
|
| 102 |
+
<div className="sim-metric-row">
|
| 103 |
+
<span className="sim-metric-label">TOTAL REWARD</span>
|
| 104 |
+
<span className={`sim-metric-val ${totalReward >= 0 ? "g" : "r"}`}>
|
| 105 |
+
{totalReward >= 0 ? "+" : ""}{totalReward.toFixed(2)}
|
| 106 |
+
</span>
|
| 107 |
+
</div>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
);
|
| 111 |
+
}
|
ui/app/components/ArchitecturePipeline.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
export default function ArchitecturePipeline() {
|
| 4 |
+
const nodes = [
|
| 5 |
+
{ id: "LAYER-01", name: "AGENTS", desc: "S0–S4 emit\nobservations + actions\nper timestep", color: "rgba(0, 200, 255, 0.4)", labelColor: "#00f5ff" },
|
| 6 |
+
{ id: "LAYER-02", name: "ADV DETECTOR", desc: "KL-divergence\nanomaly scan\nByzantine flag", color: "rgba(255, 45, 85, 0.3)", labelColor: "#ff2d55" },
|
| 7 |
+
{ id: "LAYER-03", name: "ORCHESTRATOR", desc: "Trust-weighted\naggregation &\ndecision output", color: "rgba(0, 255, 136, 0.3)", labelColor: "#00ff88" },
|
| 8 |
+
{ id: "LAYER-04", name: "REWARD SIG.", desc: "Shaped scalar\nwith adversarial\npenalty term", color: "rgba(255, 184, 0, 0.3)", labelColor: "#ffb800" },
|
| 9 |
+
{ id: "LAYER-05", name: "POLICY UPDATE", desc: "PPO gradient\nstep + trust\nposterior update", color: "rgba(0, 85, 255, 0.4)", labelColor: "#0088ff" },
|
| 10 |
+
];
|
| 11 |
+
|
| 12 |
+
const arrows = ["ACTIONS", "FLAGS", "DECISION", "REWARD"];
|
| 13 |
+
|
| 14 |
+
return (
|
| 15 |
+
<>
|
| 16 |
+
<div className="arch-flow">
|
| 17 |
+
{nodes.map((node, i) => (
|
| 18 |
+
<span key={node.id} style={{ display: "contents" }}>
|
| 19 |
+
<div className="arch-node" style={{ "--node-color": node.color } as React.CSSProperties}>
|
| 20 |
+
<div className="arch-node-id" style={{ color: node.labelColor }}>{node.id}</div>
|
| 21 |
+
<div className="arch-node-name">{node.name}</div>
|
| 22 |
+
<div className="arch-node-desc">{node.desc.split("\n").map((l, j) => <span key={j}>{l}<br /></span>)}</div>
|
| 23 |
+
</div>
|
| 24 |
+
{i < nodes.length - 1 && (
|
| 25 |
+
<div className="arch-arrow">
|
| 26 |
+
<div className="arch-arrow-line" />
|
| 27 |
+
<div className="arch-arrow-label">{arrows[i]}</div>
|
| 28 |
+
</div>
|
| 29 |
+
)}
|
| 30 |
+
</span>
|
| 31 |
+
))}
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<div className="arch-code-loop">
|
| 35 |
+
<span style={{ color: "var(--muted)" }}>LOOP:</span>
|
| 36 |
+
<span style={{ color: "var(--cyan)" }}>observe()</span> →{" "}
|
| 37 |
+
<span style={{ color: "var(--red)" }}>detect_adversary()</span> →{" "}
|
| 38 |
+
<span style={{ color: "var(--green)" }}>aggregate_trust()</span> →{" "}
|
| 39 |
+
<span style={{ color: "var(--white)" }}>act()</span> →{" "}
|
| 40 |
+
<span style={{ color: "var(--amber)" }}>compute_reward()</span> →{" "}
|
| 41 |
+
<span style={{ color: "#0088ff" }}>update_policy()</span> →{" "}
|
| 42 |
+
<span style={{ color: "var(--cyan)" }}>repeat</span>
|
| 43 |
+
|
| 44 |
+
<span style={{ color: "var(--muted)" }}>// T: O(N·K) // SPACE: O(N²)</span>
|
| 45 |
+
</div>
|
| 46 |
+
</>
|
| 47 |
+
);
|
| 48 |
+
}
|
ui/app/components/ExecutionLog.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useRef, useEffect } from "react";
|
| 4 |
+
import type { EventItem, Observation } from "../lib/types";
|
| 5 |
+
|
| 6 |
+
type Props = {
|
| 7 |
+
events: EventItem[];
|
| 8 |
+
observation: Observation | null;
|
| 9 |
+
info: { total_reward: number; score: number; adversarial_detections?: number; adversarial_poisonings?: number } | undefined;
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
function logClass(e: EventItem): string {
|
| 13 |
+
if (e.outcome === "poisoned") return "alert";
|
| 14 |
+
if (e.outcome === "blocked") return "warn";
|
| 15 |
+
if (e.outcome === "success") return "ok";
|
| 16 |
+
return "";
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
function formatTime(step: number): string {
|
| 20 |
+
const m = 14, s = 3 + step;
|
| 21 |
+
const ss = s % 60;
|
| 22 |
+
const mm = m + Math.floor(s / 60);
|
| 23 |
+
return `${mm}:${String(ss).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
export default function ExecutionLog({ events, observation, info }: Props) {
|
| 27 |
+
const scrollRef = useRef<HTMLDivElement>(null);
|
| 28 |
+
|
| 29 |
+
useEffect(() => {
|
| 30 |
+
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
| 31 |
+
}, [events.length]);
|
| 32 |
+
|
| 33 |
+
const detectRate = info?.adversarial_detections !== undefined && info?.adversarial_poisonings !== undefined
|
| 34 |
+
? (info.adversarial_detections + info.adversarial_poisonings) > 0
|
| 35 |
+
? Math.round((info.adversarial_detections / (info.adversarial_detections + info.adversarial_poisonings)) * 100)
|
| 36 |
+
: 0
|
| 37 |
+
: null;
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<div className="sim-panel" style={{ borderRight: "none", borderLeft: "1px solid rgba(0,200,255,0.08)" }}>
|
| 41 |
+
<div className="sim-panel-label">EVENT LOG</div>
|
| 42 |
+
|
| 43 |
+
<div ref={scrollRef} style={{ maxHeight: 220, overflowY: "auto" }}>
|
| 44 |
+
{events.slice(-12).map((e, i) => (
|
| 45 |
+
<div key={i} className={`log-entry ${logClass(e)}`}>
|
| 46 |
+
<span className="log-time">{formatTime(e.step)}</span>
|
| 47 |
+
{e.action === "reset"
|
| 48 |
+
? "EPISODE RESET ⟳"
|
| 49 |
+
: `${e.specialist ?? "SYS"} ${e.summary.substring(0, 40)}`
|
| 50 |
+
}
|
| 51 |
+
</div>
|
| 52 |
+
))}
|
| 53 |
+
{events.length === 0 && (
|
| 54 |
+
<div className="log-entry" style={{ opacity: 0.3 }}>
|
| 55 |
+
Waiting for simulation data...
|
| 56 |
+
</div>
|
| 57 |
+
)}
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid rgba(0,200,255,0.08)" }}>
|
| 61 |
+
<div className="sim-panel-label">EPISODE METRICS</div>
|
| 62 |
+
<div className="sim-metric-row">
|
| 63 |
+
<span className="sim-metric-label">CUMULATIVE REWARD</span>
|
| 64 |
+
<span className={`sim-metric-val ${(info?.total_reward ?? 0) >= 0 ? "g" : "r"}`}>
|
| 65 |
+
{(info?.total_reward ?? 0) >= 0 ? "+" : ""}{(info?.total_reward ?? 0).toFixed(2)}
|
| 66 |
+
</span>
|
| 67 |
+
</div>
|
| 68 |
+
<div className="sim-metric-row">
|
| 69 |
+
<span className="sim-metric-label">SCORE</span>
|
| 70 |
+
<span className="sim-metric-val c">{(info?.score ?? 0).toFixed(3)}</span>
|
| 71 |
+
</div>
|
| 72 |
+
{detectRate !== null && (
|
| 73 |
+
<div className="sim-metric-row">
|
| 74 |
+
<span className="sim-metric-label">DETECT RATE</span>
|
| 75 |
+
<span className="sim-metric-val c">{detectRate}%</span>
|
| 76 |
+
</div>
|
| 77 |
+
)}
|
| 78 |
+
<div className="sim-metric-row">
|
| 79 |
+
<span className="sim-metric-label">STEP</span>
|
| 80 |
+
<span className="sim-metric-val a">
|
| 81 |
+
{observation?.step_count ?? 0}/{observation?.max_steps ?? 0}
|
| 82 |
+
</span>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
);
|
| 87 |
+
}
|
ui/app/components/HeroCanvas.tsx
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useRef, useEffect } from "react";
|
| 4 |
+
|
| 5 |
+
type Node = {
|
| 6 |
+
x: number; y: number;
|
| 7 |
+
vx: number; vy: number;
|
| 8 |
+
r: number; pulse: number;
|
| 9 |
+
type: "blue" | "green" | "red";
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
export default function HeroCanvas() {
|
| 13 |
+
const ref = useRef<HTMLCanvasElement>(null);
|
| 14 |
+
|
| 15 |
+
useEffect(() => {
|
| 16 |
+
const canvas = ref.current;
|
| 17 |
+
if (!canvas) return;
|
| 18 |
+
const ctx = canvas.getContext("2d");
|
| 19 |
+
if (!ctx) return;
|
| 20 |
+
|
| 21 |
+
let W = 0, H = 0;
|
| 22 |
+
let nodes: Node[] = [];
|
| 23 |
+
let animId = 0;
|
| 24 |
+
|
| 25 |
+
function resize() {
|
| 26 |
+
W = canvas!.width = canvas!.offsetWidth;
|
| 27 |
+
H = canvas!.height = canvas!.offsetHeight;
|
| 28 |
+
buildNodes();
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function buildNodes() {
|
| 32 |
+
nodes = [];
|
| 33 |
+
const count = Math.floor((W * H) / 18000);
|
| 34 |
+
for (let i = 0; i < count; i++) {
|
| 35 |
+
nodes.push({
|
| 36 |
+
x: Math.random() * W, y: Math.random() * H,
|
| 37 |
+
vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3,
|
| 38 |
+
r: Math.random() * 2 + 1,
|
| 39 |
+
pulse: Math.random() * Math.PI * 2,
|
| 40 |
+
type: Math.random() < 0.1 ? "red" : Math.random() < 0.15 ? "green" : "blue",
|
| 41 |
+
});
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function draw() {
|
| 46 |
+
ctx!.clearRect(0, 0, W, H);
|
| 47 |
+
|
| 48 |
+
// Grid
|
| 49 |
+
ctx!.strokeStyle = "rgba(0,100,200,0.06)";
|
| 50 |
+
ctx!.lineWidth = 0.5;
|
| 51 |
+
for (let x = 0; x < W; x += 60) {
|
| 52 |
+
ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke();
|
| 53 |
+
}
|
| 54 |
+
for (let y = 0; y < H; y += 60) {
|
| 55 |
+
ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke();
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Update
|
| 59 |
+
nodes.forEach(n => {
|
| 60 |
+
n.x += n.vx; n.y += n.vy;
|
| 61 |
+
if (n.x < 0 || n.x > W) n.vx *= -1;
|
| 62 |
+
if (n.y < 0 || n.y > H) n.vy *= -1;
|
| 63 |
+
n.pulse += 0.02;
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
// Edges
|
| 67 |
+
for (let i = 0; i < nodes.length; i++) {
|
| 68 |
+
for (let j = i + 1; j < nodes.length; j++) {
|
| 69 |
+
const a = nodes[i], b = nodes[j];
|
| 70 |
+
const dx = a.x - b.x, dy = a.y - b.y;
|
| 71 |
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
| 72 |
+
if (dist < 140) {
|
| 73 |
+
const alpha = (1 - dist / 140) * 0.25;
|
| 74 |
+
let color: string;
|
| 75 |
+
if (a.type === "red" || b.type === "red") color = `rgba(255,45,85,${alpha * 0.8})`;
|
| 76 |
+
else if (a.type === "green" || b.type === "green") color = `rgba(0,255,136,${alpha * 0.6})`;
|
| 77 |
+
else color = `rgba(0,200,255,${alpha})`;
|
| 78 |
+
ctx!.strokeStyle = color;
|
| 79 |
+
ctx!.lineWidth = 0.5;
|
| 80 |
+
ctx!.beginPath(); ctx!.moveTo(a.x, a.y); ctx!.lineTo(b.x, b.y); ctx!.stroke();
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Nodes
|
| 86 |
+
nodes.forEach(n => {
|
| 87 |
+
const pulse = 0.5 + 0.5 * Math.sin(n.pulse);
|
| 88 |
+
let color: string, gcolor: string;
|
| 89 |
+
if (n.type === "red") { color = `rgba(255,45,85,${0.4 + 0.5 * pulse})`; gcolor = "#FF2D55"; }
|
| 90 |
+
else if (n.type === "green") { color = `rgba(0,255,136,${0.4 + 0.5 * pulse})`; gcolor = "#00FF88"; }
|
| 91 |
+
else { color = `rgba(0,200,255,${0.35 + 0.4 * pulse})`; gcolor = "#00F5FF"; }
|
| 92 |
+
|
| 93 |
+
const g = ctx!.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
|
| 94 |
+
g.addColorStop(0, gcolor); g.addColorStop(1, "transparent");
|
| 95 |
+
ctx!.fillStyle = g;
|
| 96 |
+
ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx!.fill();
|
| 97 |
+
ctx!.fillStyle = color;
|
| 98 |
+
ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx!.fill();
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
animId = requestAnimationFrame(draw);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
const onResize = () => { cancelAnimationFrame(animId); resize(); draw(); };
|
| 105 |
+
window.addEventListener("resize", onResize);
|
| 106 |
+
resize();
|
| 107 |
+
draw();
|
| 108 |
+
|
| 109 |
+
return () => { cancelAnimationFrame(animId); window.removeEventListener("resize", onResize); };
|
| 110 |
+
}, []);
|
| 111 |
+
|
| 112 |
+
return (
|
| 113 |
+
<div className="hero-canvas-wrap">
|
| 114 |
+
<canvas ref={ref} />
|
| 115 |
+
</div>
|
| 116 |
+
);
|
| 117 |
+
}
|
ui/app/components/MetricsGrid.tsx
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import type { EvalSummary } from "../lib/types";
|
| 4 |
+
|
| 5 |
+
type Props = {
|
| 6 |
+
proof: {
|
| 7 |
+
random: EvalSummary;
|
| 8 |
+
heuristic: EvalSummary;
|
| 9 |
+
oracle: EvalSummary;
|
| 10 |
+
trained?: EvalSummary;
|
| 11 |
+
} | null;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
export default function MetricsGrid({ proof }: Props) {
|
| 15 |
+
const trained = proof?.trained;
|
| 16 |
+
const heuristic = proof?.heuristic;
|
| 17 |
+
const random = proof?.random;
|
| 18 |
+
|
| 19 |
+
const trustAcc = trained ? Math.round(trained.avg_trust_calibration * 100) : 92;
|
| 20 |
+
const detectRate = trained ? Math.round(trained.avg_detection_rate * 100) : 87;
|
| 21 |
+
const improvement = trained && heuristic
|
| 22 |
+
? Math.round((trained.avg_score - heuristic.avg_score) * 100)
|
| 23 |
+
: 34;
|
| 24 |
+
const avgScore = trained ? trained.avg_score.toFixed(2) : "0.91";
|
| 25 |
+
|
| 26 |
+
const baselineTrust = random ? Math.round(random.avg_trust_calibration * 100) : 61;
|
| 27 |
+
const baselineDetect = random ? Math.round(random.avg_detection_rate * 100) : 43;
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<div className="metrics-grid">
|
| 31 |
+
<div className="metric-block" style={{ "--m-color": "#00f5ff" } as React.CSSProperties}>
|
| 32 |
+
<div className="metric-ref">TABLE 1 // ROW A // TRUST ACCURACY</div>
|
| 33 |
+
<div className="metric-value">{trustAcc}<span className="metric-unit">%</span></div>
|
| 34 |
+
<div className="metric-label">Trust Accuracy</div>
|
| 35 |
+
<div className="metric-sub">
|
| 36 |
+
Correct trust assignment rate against ground-truth agent labels across all evaluation episodes.
|
| 37 |
+
</div>
|
| 38 |
+
<div className="metric-bar-wrap">
|
| 39 |
+
<div className="metric-bar-bg">
|
| 40 |
+
<div className="metric-bar-fill" style={{ width: `${trustAcc}%` }} />
|
| 41 |
+
</div>
|
| 42 |
+
<div className="metric-bar-label">
|
| 43 |
+
<span>BASELINE: {baselineTrust}%</span>
|
| 44 |
+
<span>SENTINEL: {trustAcc}%</span>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
<div className="metric-block" style={{ "--m-color": "#ff2d55" } as React.CSSProperties}>
|
| 50 |
+
<div className="metric-ref">TABLE 1 // ROW B // ADV DETECTION</div>
|
| 51 |
+
<div className="metric-value">{detectRate}<span className="metric-unit">%</span></div>
|
| 52 |
+
<div className="metric-label">Adversarial Detection Rate</div>
|
| 53 |
+
<div className="metric-sub">
|
| 54 |
+
Precision-recall F1 on Byzantine agent identification. False positive rate held below 5% threshold.
|
| 55 |
+
</div>
|
| 56 |
+
<div className="metric-bar-wrap">
|
| 57 |
+
<div className="metric-bar-bg">
|
| 58 |
+
<div className="metric-bar-fill" style={{ width: `${detectRate}%` }} />
|
| 59 |
+
</div>
|
| 60 |
+
<div className="metric-bar-label">
|
| 61 |
+
<span>BASELINE: {baselineDetect}%</span>
|
| 62 |
+
<span>SENTINEL: {detectRate}%</span>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
|
| 67 |
+
<div className="metric-block" style={{ "--m-color": "#00ff88" } as React.CSSProperties}>
|
| 68 |
+
<div className="metric-ref">TABLE 2 // ROW C // POLICY GAIN</div>
|
| 69 |
+
<div className="metric-value">+{improvement}<span className="metric-unit">%</span></div>
|
| 70 |
+
<div className="metric-label">Policy Improvement</div>
|
| 71 |
+
<div className="metric-sub">
|
| 72 |
+
Cumulative episode return gain over heuristic baseline after convergence.
|
| 73 |
+
</div>
|
| 74 |
+
<div className="metric-bar-wrap">
|
| 75 |
+
<div className="metric-bar-bg">
|
| 76 |
+
<div className="metric-bar-fill" style={{ width: `${Math.min(100, 50 + improvement)}%` }} />
|
| 77 |
+
</div>
|
| 78 |
+
<div className="metric-bar-label">
|
| 79 |
+
<span>HEURISTIC</span>
|
| 80 |
+
<span>TRAINED RL</span>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div className="metric-block" style={{ "--m-color": "#ffb800" } as React.CSSProperties}>
|
| 86 |
+
<div className="metric-ref">TABLE 2 // ROW D // FINAL SCORE</div>
|
| 87 |
+
<div className="metric-value">{avgScore}</div>
|
| 88 |
+
<div className="metric-label">Average Score</div>
|
| 89 |
+
<div className="metric-sub">
|
| 90 |
+
Mean normalized score across all tasks. Higher is better (range 0–1, boundary exclusive).
|
| 91 |
+
</div>
|
| 92 |
+
<div className="metric-bar-wrap">
|
| 93 |
+
<div className="metric-bar-bg">
|
| 94 |
+
<div className="metric-bar-fill" style={{ width: `${Number(avgScore) * 100}%` }} />
|
| 95 |
+
</div>
|
| 96 |
+
<div className="metric-bar-label">
|
| 97 |
+
<span>RANDOM: {random ? random.avg_score.toFixed(2) : "0.28"}</span>
|
| 98 |
+
<span>SENTINEL: {avgScore}</span>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
);
|
| 104 |
+
}
|
ui/app/components/SimCanvas.tsx
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useRef, useEffect } from "react";
|
| 4 |
+
|
| 5 |
+
type AgentData = { id: string; trust: number; type: string };
|
| 6 |
+
|
| 7 |
+
type Props = {
|
| 8 |
+
trustSnapshot: Record<string, number>;
|
| 9 |
+
adversarialAgents: Set<string>;
|
| 10 |
+
activeSpec: string | null;
|
| 11 |
+
};
|
| 12 |
+
|
| 13 |
+
const AGENT_ANGLES = [0, 72, 144, 216, 288];
|
| 14 |
+
const COLORS: Record<string, string> = {
|
| 15 |
+
orch: "#00F5FF", normal: "#00FF88", degraded: "#FFB800", adversarial: "#FF2D55",
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
function hexToRgb(hex: string) {
|
| 19 |
+
if (hex === "#00F5FF") return "0,245,255";
|
| 20 |
+
if (hex === "#00FF88") return "0,255,136";
|
| 21 |
+
if (hex === "#FFB800") return "255,184,0";
|
| 22 |
+
if (hex === "#FF2D55") return "255,45,85";
|
| 23 |
+
return "0,200,255";
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
function agentType(id: string, trust: number, isAdv: boolean): string {
|
| 27 |
+
if (isAdv) return "adversarial";
|
| 28 |
+
if (trust < 0.4) return "degraded";
|
| 29 |
+
return "normal";
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export default function SimCanvas({ trustSnapshot, adversarialAgents, activeSpec }: Props) {
|
| 33 |
+
const ref = useRef<HTMLCanvasElement>(null);
|
| 34 |
+
const dataRef = useRef({ trustSnapshot, adversarialAgents, activeSpec });
|
| 35 |
+
|
| 36 |
+
useEffect(() => {
|
| 37 |
+
dataRef.current = { trustSnapshot, adversarialAgents, activeSpec };
|
| 38 |
+
}, [trustSnapshot, adversarialAgents, activeSpec]);
|
| 39 |
+
|
| 40 |
+
useEffect(() => {
|
| 41 |
+
const canvas = ref.current;
|
| 42 |
+
if (!canvas) return;
|
| 43 |
+
const ctx = canvas.getContext("2d");
|
| 44 |
+
if (!ctx) return;
|
| 45 |
+
|
| 46 |
+
let W = 0, H = 0, tick = 0, animId = 0;
|
| 47 |
+
type Packet = { agentIdx: number; progress: number; dir: number };
|
| 48 |
+
let dataPackets: Packet[] = [];
|
| 49 |
+
|
| 50 |
+
function resize() {
|
| 51 |
+
W = canvas!.width = canvas!.offsetWidth;
|
| 52 |
+
H = canvas!.height = canvas!.offsetHeight;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
function getPos(angle: number, cx: number, cy: number, r: number) {
|
| 56 |
+
const rad = ((angle - 90) * Math.PI) / 180;
|
| 57 |
+
return { x: cx + Math.cos(rad) * r, y: cy + Math.sin(rad) * r };
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function draw() {
|
| 61 |
+
const { trustSnapshot: ts, adversarialAgents: advSet, activeSpec: active } = dataRef.current;
|
| 62 |
+
ctx!.clearRect(0, 0, W, H);
|
| 63 |
+
tick++;
|
| 64 |
+
|
| 65 |
+
const cx = W / 2, cy = H / 2;
|
| 66 |
+
const outerR = Math.min(W, H) * 0.35;
|
| 67 |
+
const orchR = 30, agentR = 18;
|
| 68 |
+
|
| 69 |
+
const agents: AgentData[] = ["S0", "S1", "S2", "S3", "S4"].map((id, i) => ({
|
| 70 |
+
id, trust: ts[id] ?? 0.5,
|
| 71 |
+
type: i === 0 ? "orch" : agentType(id, ts[id] ?? 0.5, advSet.has(id)),
|
| 72 |
+
}));
|
| 73 |
+
|
| 74 |
+
// Grid
|
| 75 |
+
ctx!.strokeStyle = "rgba(0,100,200,0.04)";
|
| 76 |
+
ctx!.lineWidth = 0.5;
|
| 77 |
+
for (let x = 0; x < W; x += 40) { ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke(); }
|
| 78 |
+
for (let y = 0; y < H; y += 40) { ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke(); }
|
| 79 |
+
|
| 80 |
+
// Orbit ring
|
| 81 |
+
ctx!.beginPath(); ctx!.arc(cx, cy, outerR, 0, Math.PI * 2);
|
| 82 |
+
ctx!.strokeStyle = "rgba(0,200,255,0.06)"; ctx!.lineWidth = 1;
|
| 83 |
+
ctx!.setLineDash([4, 8]); ctx!.stroke(); ctx!.setLineDash([]);
|
| 84 |
+
|
| 85 |
+
// Scanning ring
|
| 86 |
+
const scanAngle = (tick * 0.02) % (Math.PI * 2);
|
| 87 |
+
ctx!.save(); ctx!.translate(cx, cy); ctx!.rotate(scanAngle);
|
| 88 |
+
const scanArc = ctx!.createLinearGradient(-outerR, 0, outerR, 0);
|
| 89 |
+
scanArc.addColorStop(0, "rgba(0,200,255,0)");
|
| 90 |
+
scanArc.addColorStop(1, "rgba(0,200,255,0.08)");
|
| 91 |
+
ctx!.beginPath(); ctx!.moveTo(0, 0); ctx!.arc(0, 0, outerR, -0.4, 0); ctx!.closePath();
|
| 92 |
+
ctx!.fillStyle = scanArc; ctx!.fill(); ctx!.restore();
|
| 93 |
+
|
| 94 |
+
// Connections from orch to agents
|
| 95 |
+
const orchPos = { x: cx, y: cy };
|
| 96 |
+
agents.slice(1).forEach((a, i) => {
|
| 97 |
+
const pos = getPos(AGENT_ANGLES[i + 1], cx, cy, outerR);
|
| 98 |
+
const col = COLORS[a.type] || COLORS.normal;
|
| 99 |
+
const alpha = a.type === "adversarial" ? 0.12 : a.trust * 0.35;
|
| 100 |
+
const isDash = a.type === "adversarial";
|
| 101 |
+
if (isDash) ctx!.setLineDash([4, 6]); else ctx!.setLineDash([]);
|
| 102 |
+
ctx!.beginPath(); ctx!.moveTo(orchPos.x, orchPos.y); ctx!.lineTo(pos.x, pos.y);
|
| 103 |
+
ctx!.strokeStyle = `rgba(${hexToRgb(col)},${alpha})`;
|
| 104 |
+
ctx!.lineWidth = a.trust * 2; ctx!.stroke(); ctx!.setLineDash([]);
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
// Data packets
|
| 108 |
+
if (tick % 20 === 0) {
|
| 109 |
+
const ai = Math.floor(Math.random() * 4) + 1;
|
| 110 |
+
dataPackets.push({ agentIdx: ai, progress: 0, dir: Math.random() > 0.5 ? 1 : -1 });
|
| 111 |
+
}
|
| 112 |
+
dataPackets = dataPackets.filter(p => p.progress <= 1);
|
| 113 |
+
dataPackets.forEach(p => {
|
| 114 |
+
p.progress += 0.025;
|
| 115 |
+
const a = agents[p.agentIdx];
|
| 116 |
+
const pos = getPos(AGENT_ANGLES[p.agentIdx], cx, cy, outerR);
|
| 117 |
+
const t = p.progress;
|
| 118 |
+
const px = orchPos.x + (pos.x - orchPos.x) * (p.dir > 0 ? t : 1 - t);
|
| 119 |
+
const py = orchPos.y + (pos.y - orchPos.y) * (p.dir > 0 ? t : 1 - t);
|
| 120 |
+
const col = COLORS[a.type] || COLORS.normal;
|
| 121 |
+
ctx!.beginPath(); ctx!.arc(px, py, 3, 0, Math.PI * 2); ctx!.fillStyle = col; ctx!.fill();
|
| 122 |
+
const g = ctx!.createRadialGradient(px, py, 0, px, py, 8);
|
| 123 |
+
g.addColorStop(0, col); g.addColorStop(1, "transparent");
|
| 124 |
+
ctx!.beginPath(); ctx!.arc(px, py, 8, 0, Math.PI * 2); ctx!.fillStyle = g; ctx!.fill();
|
| 125 |
+
});
|
| 126 |
+
|
| 127 |
+
// Outer agents
|
| 128 |
+
agents.slice(1).forEach((a, i) => {
|
| 129 |
+
const pos = getPos(AGENT_ANGLES[i + 1], cx, cy, outerR);
|
| 130 |
+
const col = COLORS[a.type] || COLORS.normal;
|
| 131 |
+
const pulse = 0.7 + 0.3 * Math.sin(tick * 0.05 + AGENT_ANGLES[i + 1]);
|
| 132 |
+
const isActive = active === a.id;
|
| 133 |
+
|
| 134 |
+
// Glow
|
| 135 |
+
const g = ctx!.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, agentR * 2.5);
|
| 136 |
+
g.addColorStop(0, `rgba(${hexToRgb(col)},${(isActive ? 0.35 : 0.2) * pulse})`);
|
| 137 |
+
g.addColorStop(1, "transparent");
|
| 138 |
+
ctx!.beginPath(); ctx!.arc(pos.x, pos.y, agentR * 2.5, 0, Math.PI * 2);
|
| 139 |
+
ctx!.fillStyle = g; ctx!.fill();
|
| 140 |
+
|
| 141 |
+
// Node circle
|
| 142 |
+
ctx!.beginPath(); ctx!.arc(pos.x, pos.y, agentR, 0, Math.PI * 2);
|
| 143 |
+
ctx!.fillStyle = `rgba(${hexToRgb(col)},0.1)`; ctx!.fill();
|
| 144 |
+
ctx!.strokeStyle = `rgba(${hexToRgb(col)},${0.6 * pulse})`;
|
| 145 |
+
ctx!.lineWidth = isActive ? 2.5 : 1.5; ctx!.stroke();
|
| 146 |
+
|
| 147 |
+
// Adversarial warning ring
|
| 148 |
+
if (a.type === "adversarial") {
|
| 149 |
+
ctx!.beginPath();
|
| 150 |
+
ctx!.arc(pos.x, pos.y, agentR + 6 + Math.sin(tick * 0.1) * 3, 0, Math.PI * 2);
|
| 151 |
+
ctx!.strokeStyle = `rgba(255,45,85,${0.3 * pulse})`;
|
| 152 |
+
ctx!.lineWidth = 1; ctx!.setLineDash([3, 4]); ctx!.stroke(); ctx!.setLineDash([]);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
// Labels
|
| 156 |
+
ctx!.font = '9px "Share Tech Mono"'; ctx!.fillStyle = col; ctx!.textAlign = "center";
|
| 157 |
+
ctx!.fillText(a.id, pos.x, pos.y - agentR - 8);
|
| 158 |
+
ctx!.fillStyle = "rgba(232,244,255,0.3)";
|
| 159 |
+
ctx!.fillText(a.trust.toFixed(2), pos.x, pos.y + 4);
|
| 160 |
+
});
|
| 161 |
+
|
| 162 |
+
// Orchestrator
|
| 163 |
+
const orchPulse = 0.7 + 0.3 * Math.sin(tick * 0.04);
|
| 164 |
+
const orchG = ctx!.createRadialGradient(cx, cy, 0, cx, cy, orchR * 3);
|
| 165 |
+
orchG.addColorStop(0, `rgba(0,245,255,${0.15 * orchPulse})`);
|
| 166 |
+
orchG.addColorStop(1, "transparent");
|
| 167 |
+
ctx!.beginPath(); ctx!.arc(cx, cy, orchR * 3, 0, Math.PI * 2);
|
| 168 |
+
ctx!.fillStyle = orchG; ctx!.fill();
|
| 169 |
+
|
| 170 |
+
ctx!.beginPath(); ctx!.arc(cx, cy, orchR, 0, Math.PI * 2);
|
| 171 |
+
ctx!.fillStyle = "rgba(0,245,255,0.1)"; ctx!.fill();
|
| 172 |
+
ctx!.strokeStyle = `rgba(0,245,255,${0.8 * orchPulse})`;
|
| 173 |
+
ctx!.lineWidth = 2; ctx!.stroke();
|
| 174 |
+
|
| 175 |
+
ctx!.beginPath(); ctx!.arc(cx, cy, orchR * 0.6, 0, Math.PI * 2);
|
| 176 |
+
ctx!.strokeStyle = `rgba(0,245,255,${0.4 * orchPulse})`;
|
| 177 |
+
ctx!.lineWidth = 1; ctx!.stroke();
|
| 178 |
+
|
| 179 |
+
ctx!.font = 'bold 9px "Share Tech Mono"'; ctx!.fillStyle = "#00F5FF";
|
| 180 |
+
ctx!.textAlign = "center"; ctx!.fillText("S0", cx, cy - 4);
|
| 181 |
+
ctx!.font = '8px "Share Tech Mono"'; ctx!.fillStyle = "rgba(0,245,255,0.5)";
|
| 182 |
+
ctx!.fillText("ORCH", cx, cy + 8);
|
| 183 |
+
|
| 184 |
+
animId = requestAnimationFrame(draw);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
const ro = new ResizeObserver(() => resize());
|
| 188 |
+
ro.observe(canvas.parentElement!);
|
| 189 |
+
resize(); draw();
|
| 190 |
+
|
| 191 |
+
return () => { cancelAnimationFrame(animId); ro.disconnect(); };
|
| 192 |
+
}, []);
|
| 193 |
+
|
| 194 |
+
return <canvas ref={ref} />;
|
| 195 |
+
}
|
ui/app/components/SystemModules.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
type Props = { running: boolean; done: boolean; adversarialCount: number };
|
| 4 |
+
|
| 5 |
+
export default function SystemModules({ running, done, adversarialCount }: Props) {
|
| 6 |
+
return (
|
| 7 |
+
<div className="cards-grid">
|
| 8 |
+
<div className="card" style={{ "--card-accent": "#00f5ff" } as React.CSSProperties}>
|
| 9 |
+
<div className="card-id">MOD-001 // ENVIRONMENT</div>
|
| 10 |
+
<div className="card-icon">
|
| 11 |
+
<svg viewBox="0 0 40 40" fill="none"><rect x="2" y="2" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.6"/><rect x="22" y="2" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><rect x="2" y="22" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><rect x="22" y="22" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.6"/><line x1="18" y1="10" x2="22" y2="10" stroke="#00F5FF" strokeWidth="1"/><line x1="10" y1="18" x2="10" y2="22" stroke="#00F5FF" strokeWidth="1"/><circle cx="10" cy="10" r="2" fill="#00F5FF"/><circle cx="30" cy="30" r="2" fill="#00F5FF" opacity="0.5"/></svg>
|
| 12 |
+
</div>
|
| 13 |
+
<div className="card-title">Multi-Agent Environment</div>
|
| 14 |
+
<div className="card-body">
|
| 15 |
+
Discrete-time partially observable environment hosting N heterogeneous agents.
|
| 16 |
+
Supports configurable adversarial injection ratios and stochastic reward structures per episode.
|
| 17 |
+
</div>
|
| 18 |
+
<div className="card-footer">
|
| 19 |
+
<div className="card-status">
|
| 20 |
+
<div className="status-dot" style={running ? {} : { background: done ? "var(--amber)" : "var(--muted)", animation: "none" }} />
|
| 21 |
+
{running ? "RUNNING" : done ? "COMPLETE" : "IDLE"}
|
| 22 |
+
</div>
|
| 23 |
+
<div className="card-ver">gym v0.26.2</div>
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
<div className="card" style={{ "--card-accent": "#00ff88" } as React.CSSProperties}>
|
| 28 |
+
<div className="card-id">MOD-002 // TRUST ENGINE</div>
|
| 29 |
+
<div className="card-icon">
|
| 30 |
+
<svg viewBox="0 0 40 40" fill="none"><circle cx="20" cy="20" r="14" stroke="#00FF88" strokeWidth="1" strokeDasharray="4 2" opacity="0.5"/><circle cx="20" cy="20" r="8" stroke="#00FF88" strokeWidth="1" opacity="0.8"/><circle cx="20" cy="20" r="3" fill="#00FF88"/><line x1="20" y1="6" x2="20" y2="12" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="20" y1="28" x2="20" y2="34" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="6" y1="20" x2="12" y2="20" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="28" y1="20" x2="34" y2="20" stroke="#00FF88" strokeWidth="1" opacity="0.5"/></svg>
|
| 31 |
+
</div>
|
| 32 |
+
<div className="card-title">Trust Calibration Engine</div>
|
| 33 |
+
<div className="card-body">
|
| 34 |
+
Bayesian trust scoring module that maintains per-agent belief distributions.
|
| 35 |
+
Updates posteriors using observed action-outcome consistency.
|
| 36 |
+
</div>
|
| 37 |
+
<div className="card-footer">
|
| 38 |
+
<div className="card-status">
|
| 39 |
+
<div className="status-dot" />
|
| 40 |
+
CALIBRATING
|
| 41 |
+
</div>
|
| 42 |
+
<div className="card-ver">TCE v1.1.4</div>
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
|
| 46 |
+
<div className="card" style={{ "--card-accent": "#ff2d55" } as React.CSSProperties}>
|
| 47 |
+
<div className="card-id">MOD-003 // ADV DETECTION</div>
|
| 48 |
+
<div className="card-icon">
|
| 49 |
+
<svg viewBox="0 0 40 40" fill="none"><polygon points="20,4 36,32 4,32" stroke="#FF2D55" strokeWidth="1" fill="none" opacity="0.7"/><line x1="20" y1="14" x2="20" y2="24" stroke="#FF2D55" strokeWidth="1.5"/><circle cx="20" cy="28" r="1.5" fill="#FF2D55"/></svg>
|
| 50 |
+
</div>
|
| 51 |
+
<div className="card-title">Adversarial Detection Layer</div>
|
| 52 |
+
<div className="card-body">
|
| 53 |
+
Anomaly-based detector using temporal divergence scoring across agent action histories.
|
| 54 |
+
Flags Byzantine agents via KL-divergence threshold on expected vs observed policy distributions.
|
| 55 |
+
</div>
|
| 56 |
+
<div className="card-footer">
|
| 57 |
+
<div className="card-status" style={{ color: adversarialCount > 0 ? "var(--red)" : "var(--green)" }}>
|
| 58 |
+
<div className="status-dot" style={adversarialCount > 0 ? { background: "var(--red)" } : {}} />
|
| 59 |
+
{adversarialCount > 0 ? `${adversarialCount} THREAT${adversarialCount > 1 ? "S" : ""}` : "CLEAR"}
|
| 60 |
+
</div>
|
| 61 |
+
<div className="card-ver">ADL v2.0.1</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div className="card" style={{ "--card-accent": "#ffb800" } as React.CSSProperties}>
|
| 66 |
+
<div className="card-id">MOD-004 // RL OPTIMIZER</div>
|
| 67 |
+
<div className="card-icon">
|
| 68 |
+
<svg viewBox="0 0 40 40" fill="none"><polyline points="4,32 10,20 16,26 22,14 28,18 36,6" stroke="#FFB800" strokeWidth="1.5" fill="none"/><circle cx="10" cy="20" r="2" fill="#FFB800" opacity="0.6"/><circle cx="22" cy="14" r="2" fill="#FFB800" opacity="0.6"/><circle cx="36" cy="6" r="2.5" fill="#FFB800"/><line x1="4" y1="35" x2="36" y2="35" stroke="#FFB800" strokeWidth="0.5" opacity="0.3"/><line x1="4" y1="35" x2="4" y2="4" stroke="#FFB800" strokeWidth="0.5" opacity="0.3"/></svg>
|
| 69 |
+
</div>
|
| 70 |
+
<div className="card-title">Reinforcement Learning Optimizer</div>
|
| 71 |
+
<div className="card-body">
|
| 72 |
+
Proximal Policy Optimization (PPO) with trust-weighted reward shaping.
|
| 73 |
+
Policy gradient updates incorporate adversarial penalty terms.
|
| 74 |
+
</div>
|
| 75 |
+
<div className="card-footer">
|
| 76 |
+
<div className="card-status" style={{ color: "var(--amber)" }}>
|
| 77 |
+
<div className="status-dot" style={{ background: "var(--amber)" }} />
|
| 78 |
+
TRAINING
|
| 79 |
+
</div>
|
| 80 |
+
<div className="card-ver">PPO v3.2.0</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
);
|
| 85 |
+
}
|
ui/app/globals.css
CHANGED
|
@@ -1,379 +1,414 @@
|
|
| 1 |
-
@import url('https://fonts.googleapis.com/css2?family=
|
| 2 |
|
| 3 |
:root {
|
| 4 |
-
--bg: #
|
| 5 |
-
--
|
| 6 |
-
--
|
| 7 |
-
--
|
| 8 |
-
--
|
| 9 |
-
--
|
| 10 |
-
--
|
| 11 |
-
--
|
| 12 |
-
--
|
| 13 |
-
--
|
| 14 |
-
--
|
| 15 |
-
--
|
| 16 |
-
--
|
| 17 |
-
--
|
| 18 |
-
--
|
| 19 |
-
--
|
| 20 |
-
--
|
| 21 |
-
--glow-accent: rgba(124,92,252,.35);
|
| 22 |
-
--glow-green: rgba(52,211,153,.25);
|
| 23 |
-
--glow-red: rgba(251,78,106,.2);
|
| 24 |
-
--mono: 'JetBrains Mono',ui-monospace,monospace;
|
| 25 |
-
--sans: 'Inter',system-ui,sans-serif;
|
| 26 |
-
--display: 'Space Grotesk','Inter',system-ui,sans-serif;
|
| 27 |
-
--r: 20px;
|
| 28 |
-
--r-sm: 12px;
|
| 29 |
-
--r-xs: 8px;
|
| 30 |
}
|
| 31 |
|
| 32 |
-
*{box-sizing:border-box;margin:0}
|
| 33 |
-
html{
|
| 34 |
-
body{
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
}
|
| 42 |
-
button,select,input{font:inherit;color:inherit}
|
| 43 |
-
|
| 44 |
-
/* ── animated bg ─────────────────────────────────────── */
|
| 45 |
-
.grid-bg{
|
| 46 |
-
position:fixed;inset:0;pointer-events:none;z-index:0;
|
| 47 |
-
background-image:
|
| 48 |
-
radial-gradient(circle at 1px 1px,rgba(120,130,180,.06) 1px,transparent 0);
|
| 49 |
-
background-size:32px 32px;
|
| 50 |
-
mask-image:radial-gradient(ellipse 60% 50% at 50% 40%,black,transparent);
|
| 51 |
}
|
| 52 |
|
| 53 |
-
/* ──
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
filter:blur(100px);opacity:.4;z-index:0;
|
| 58 |
-
}
|
| 59 |
-
.shell::before{width:600px;height:600px;top:-200px;left:-100px;background:rgba(124,92,252,.15)}
|
| 60 |
-
.shell::after{width:500px;height:500px;bottom:-150px;right:-100px;background:rgba(56,189,248,.1)}
|
| 61 |
|
| 62 |
-
/* ──
|
| 63 |
-
.
|
| 64 |
-
position:
|
| 65 |
-
display:flex;align-items:center;
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
/* ──
|
| 99 |
-
.
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
}
|
| 105 |
-
.btn:hover{border-color:var(--bdr-h);transform:translateY(-1px);box-shadow:0 4px 20px rgba(0,0,0,.3)}
|
| 106 |
-
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
|
| 107 |
-
.btn-primary{
|
| 108 |
-
background:linear-gradient(135deg,rgba(124,92,252,.3),rgba(56,189,248,.12));
|
| 109 |
-
border-color:rgba(124,92,252,.3);
|
| 110 |
-
box-shadow:0 0 20px rgba(124,92,252,.08);
|
| 111 |
-
}
|
| 112 |
-
.btn-primary:hover{box-shadow:0 0 30px rgba(124,92,252,.2);border-color:rgba(124,92,252,.5)}
|
| 113 |
-
.btn-danger{border-color:rgba(251,78,106,.3);color:var(--red)}
|
| 114 |
-
.btn-lg{height:52px;padding:0 28px;font-size:15px;border-radius:14px;font-family:var(--display)}
|
| 115 |
-
.btn-block{width:100%}
|
| 116 |
-
.btn-ghost{border-color:transparent;background:transparent}
|
| 117 |
-
.btn-ghost:hover{background:rgba(124,92,252,.06)}
|
| 118 |
-
.btn-glow{
|
| 119 |
-
position:relative;overflow:hidden;
|
| 120 |
-
}
|
| 121 |
-
.btn-glow::before{
|
| 122 |
-
content:'';position:absolute;inset:-2px;border-radius:inherit;
|
| 123 |
-
background:linear-gradient(135deg,#7c5cfc,#38bdf8,#c084fc);opacity:0;
|
| 124 |
-
transition:opacity .3s;z-index:-1;filter:blur(8px);
|
| 125 |
-
}
|
| 126 |
-
.btn-glow:hover::before{opacity:.4}
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
box-shadow:0 8px 40px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.03);
|
| 135 |
-
position:relative;overflow:hidden;
|
| 136 |
-
}
|
| 137 |
-
.panel::before{
|
| 138 |
-
content:'';position:absolute;top:0;left:0;right:0;height:1px;
|
| 139 |
-
background:linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent);
|
| 140 |
-
}
|
| 141 |
-
.panel-head{margin-bottom:18px}
|
| 142 |
-
.panel-eyebrow{
|
| 143 |
-
font-size:10px;text-transform:uppercase;letter-spacing:.14em;
|
| 144 |
-
color:var(--accent);font-weight:700;margin-bottom:6px;
|
| 145 |
-
font-family:var(--mono);
|
| 146 |
-
}
|
| 147 |
-
.panel-title{font-size:20px;font-weight:700;font-family:var(--display);letter-spacing:-.02em}
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
.land-hero h1{
|
| 158 |
-
font-family:var(--display);font-size:clamp(40px,7vw,72px);font-weight:700;
|
| 159 |
-
line-height:.95;letter-spacing:-.04em;margin-bottom:20px;
|
| 160 |
-
}
|
| 161 |
-
.land-hero h1 span{
|
| 162 |
-
background:linear-gradient(135deg,#7c5cfc 0%,#38bdf8 50%,#34d399 100%);
|
| 163 |
-
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
|
| 164 |
-
}
|
| 165 |
-
.land-hero p{
|
| 166 |
-
max-width:580px;margin:0 auto 36px;font-size:16px;line-height:1.7;color:var(--ink2);
|
| 167 |
-
}
|
| 168 |
-
.land-ctas{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
|
| 169 |
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
.
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
.
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
.
|
| 188 |
-
.ba-tag{
|
| 189 |
-
display:inline-flex;align-items:center;gap:6px;
|
| 190 |
-
font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
| 191 |
-
padding:5px 12px;border-radius:6px;margin-bottom:14px;font-family:var(--mono);
|
| 192 |
-
}
|
| 193 |
-
.ba-card.before .ba-tag{background:rgba(251,78,106,.1);color:var(--red)}
|
| 194 |
-
.ba-card.after .ba-tag{background:rgba(52,211,153,.1);color:var(--green)}
|
| 195 |
-
.ba-card h3{font-size:18px;margin-bottom:14px;font-family:var(--display)}
|
| 196 |
-
.ba-steps{display:grid;gap:8px}
|
| 197 |
-
.ba-step{
|
| 198 |
-
font-size:13px;color:var(--ink2);line-height:1.5;padding:12px 14px;
|
| 199 |
-
border-radius:var(--r-xs);background:rgba(0,0,0,.25);
|
| 200 |
-
display:flex;align-items:flex-start;gap:10px;
|
| 201 |
-
border:1px solid rgba(255,255,255,.02);
|
| 202 |
-
}
|
| 203 |
-
.ba-step .num{
|
| 204 |
-
flex-shrink:0;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:700;
|
| 205 |
-
display:grid;place-items:center;
|
| 206 |
-
}
|
| 207 |
-
.ba-card.before .ba-step .num{background:rgba(251,78,106,.15);color:var(--red)}
|
| 208 |
-
.ba-card.after .ba-step .num{background:rgba(52,211,153,.15);color:var(--green)}
|
| 209 |
-
.ba-score{font-family:var(--mono);font-size:40px;font-weight:700;margin-top:20px}
|
| 210 |
-
.ba-card.before .ba-score{color:var(--red);text-shadow:0 0 30px var(--glow-red)}
|
| 211 |
-
.ba-card.after .ba-score{color:var(--green);text-shadow:0 0 30px var(--glow-green)}
|
| 212 |
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
-
/*
|
| 228 |
-
.
|
| 229 |
-
.
|
| 230 |
-
.
|
| 231 |
-
|
| 232 |
-
.
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
box-shadow:0 0 24px var(--glow-accent),inset 0 0 20px rgba(124,92,252,.05);
|
| 247 |
-
}
|
| 248 |
-
.net-node.danger{border-color:rgba(251,78,106,.35);box-shadow:0 0 20px var(--glow-red)}
|
| 249 |
-
.net-node .id{font-weight:700;font-size:14px;font-family:var(--display)}
|
| 250 |
-
.net-node .trust{font-family:var(--mono);font-size:12px;color:var(--ink2);margin-top:3px}
|
| 251 |
-
.net-node .delta{font-family:var(--mono);font-size:10px;margin-top:2px}
|
| 252 |
-
.delta-up{color:var(--green)}
|
| 253 |
-
.delta-down{color:var(--red)}
|
| 254 |
|
| 255 |
-
/*
|
| 256 |
-
.
|
| 257 |
-
.tl-row{display:grid;grid-template-columns:40px 1fr 54px 56px;align-items:center;gap:10px}
|
| 258 |
-
.tl-id{font-weight:700;font-size:14px;font-family:var(--display)}
|
| 259 |
-
.tl-track{height:8px;border-radius:99px;background:rgba(255,255,255,.04);overflow:hidden;border:1px solid rgba(255,255,255,.03)}
|
| 260 |
-
.tl-fill{height:100%;border-radius:inherit;transition:width .5s cubic-bezier(.34,1.56,.64,1)}
|
| 261 |
-
.tl-val{font-family:var(--mono);font-size:13px;font-weight:600;text-align:right}
|
| 262 |
-
.tl-delta{font-family:var(--mono);font-size:11px;text-align:right}
|
| 263 |
|
| 264 |
-
/*
|
| 265 |
-
.
|
| 266 |
-
.
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
.
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
-
/*
|
| 275 |
-
.
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
-
/*
|
| 283 |
-
.
|
| 284 |
-
.
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
}
|
| 291 |
-
.
|
| 292 |
-
.
|
| 293 |
-
.
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
box-shadow:0 0 24px rgba(124,92,252,.08);
|
| 297 |
-
}
|
| 298 |
-
.ac-btn.rec::after{content:'★';font-size:10px;color:var(--accent)}
|
| 299 |
-
.ac-auto{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px}
|
| 300 |
|
| 301 |
-
/*
|
| 302 |
-
.
|
| 303 |
-
.
|
| 304 |
-
.
|
| 305 |
-
.
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
}
|
| 311 |
-
.fr-row:hover{background:rgba(124,92,252,.04)}
|
| 312 |
-
.fr-step{font-weight:700;color:var(--ink3);font-family:var(--mono);font-size:12px}
|
| 313 |
-
.fr-action{font-weight:600;font-size:13px}
|
| 314 |
-
.fr-summary{color:var(--ink2);font-size:11px;margin-top:2px}
|
| 315 |
-
.fr-reward{font-family:var(--mono);font-weight:700;font-size:14px}
|
| 316 |
-
.fr-reward.pos{color:var(--green);text-shadow:0 0 8px var(--glow-green)}
|
| 317 |
-
.fr-reward.neg{color:var(--red)}
|
| 318 |
-
.fr-icon{font-size:14px}
|
| 319 |
-
.fr-toggle{font-size:11px;color:var(--ink3);cursor:pointer;background:none;border:none;padding:6px 0;margin-top:8px;font-family:var(--mono)}
|
| 320 |
-
.fr-toggle:hover{color:var(--accent)}
|
| 321 |
|
| 322 |
-
/*
|
| 323 |
-
.
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
.
|
| 338 |
-
.
|
| 339 |
-
.jw-bar.done{background:var(--green)}
|
| 340 |
|
| 341 |
-
|
| 342 |
-
.
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
-
|
| 347 |
-
.
|
| 348 |
-
.
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
|
| 353 |
-
|
| 354 |
-
.
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
-
/*
|
| 361 |
-
|
| 362 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
-
/* ──
|
| 365 |
-
@media(max-width:
|
| 366 |
-
.
|
| 367 |
-
.
|
| 368 |
-
.
|
| 369 |
-
}
|
| 370 |
-
|
| 371 |
-
.
|
| 372 |
-
.
|
| 373 |
-
.
|
| 374 |
-
.arch-
|
| 375 |
-
.
|
| 376 |
-
|
| 377 |
-
.land-hero h1{font-size:36px}
|
| 378 |
-
.net{max-height:280px}
|
| 379 |
-
}
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;600;700;900&family=Space+Mono:wght@400;700&display=swap');
|
| 2 |
|
| 3 |
:root {
|
| 4 |
+
--bg: #03060f;
|
| 5 |
+
--bg2: #060d1a;
|
| 6 |
+
--bg3: #0a1628;
|
| 7 |
+
--cyan: #00f5ff;
|
| 8 |
+
--blue: #0055ff;
|
| 9 |
+
--blue-dim: #0033aa;
|
| 10 |
+
--green: #00ff88;
|
| 11 |
+
--red: #ff2d55;
|
| 12 |
+
--amber: #ffb800;
|
| 13 |
+
--white: #e8f4ff;
|
| 14 |
+
--muted: #4a6080;
|
| 15 |
+
--glass: rgba(0, 200, 255, 0.04);
|
| 16 |
+
--glass-border: rgba(0, 200, 255, 0.12);
|
| 17 |
+
--glass-hover: rgba(0, 200, 255, 0.08);
|
| 18 |
+
--font-display: 'Orbitron', monospace;
|
| 19 |
+
--font-mono: 'Share Tech Mono', monospace;
|
| 20 |
+
--font-body: 'Space Mono', monospace;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 24 |
+
html { scroll-behavior: smooth; }
|
| 25 |
+
body {
|
| 26 |
+
background: var(--bg);
|
| 27 |
+
color: var(--white);
|
| 28 |
+
font-family: var(--font-body);
|
| 29 |
+
font-size: 14px;
|
| 30 |
+
line-height: 1.7;
|
| 31 |
+
overflow-x: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
+
/* ── SCROLLBAR ── */
|
| 35 |
+
::-webkit-scrollbar { width: 4px; }
|
| 36 |
+
::-webkit-scrollbar-track { background: var(--bg); }
|
| 37 |
+
::-webkit-scrollbar-thumb { background: var(--blue-dim); border-radius: 2px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
/* ── NAV ── */
|
| 40 |
+
.nav-bar {
|
| 41 |
+
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
| 42 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 43 |
+
padding: 18px 48px;
|
| 44 |
+
background: rgba(3, 6, 15, 0.85);
|
| 45 |
+
backdrop-filter: blur(12px);
|
| 46 |
+
border-bottom: 1px solid rgba(0, 200, 255, 0.08);
|
| 47 |
+
overflow: hidden;
|
| 48 |
+
}
|
| 49 |
+
.nav-bar::before {
|
| 50 |
+
content: ""; position: absolute; inset: 0;
|
| 51 |
+
background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 245, 255, 0.01) 2px, rgba(0, 245, 255, 0.01) 4px);
|
| 52 |
+
pointer-events: none; opacity: 0.5;
|
| 53 |
+
}
|
| 54 |
+
.nav-logo {
|
| 55 |
+
font-family: var(--font-display);
|
| 56 |
+
font-size: 15px; font-weight: 700;
|
| 57 |
+
letter-spacing: 0.35em; color: var(--cyan);
|
| 58 |
+
display: flex; align-items: center; gap: 10px;
|
| 59 |
+
animation: logo-flicker 10s linear infinite;
|
| 60 |
+
}
|
| 61 |
+
@keyframes logo-flicker {
|
| 62 |
+
0%, 95%, 100% { opacity: 1; }
|
| 63 |
+
96% { opacity: 0.7; }
|
| 64 |
+
97% { opacity: 1; }
|
| 65 |
+
98% { opacity: 0.8; }
|
| 66 |
+
99% { opacity: 1; }
|
| 67 |
+
}
|
| 68 |
+
.nav-logo-dot {
|
| 69 |
+
width: 8px; height: 8px;
|
| 70 |
+
background: var(--cyan); border-radius: 50%;
|
| 71 |
+
box-shadow: 0 0 10px var(--cyan);
|
| 72 |
+
animation: pulse-dot 2s ease-in-out infinite;
|
| 73 |
+
}
|
| 74 |
+
@keyframes pulse-dot {
|
| 75 |
+
0%, 100% { opacity: 1; box-shadow: 0 0 10px var(--cyan); }
|
| 76 |
+
50% { opacity: 0.4; box-shadow: 0 0 20px var(--cyan); }
|
| 77 |
+
}
|
| 78 |
+
.nav-links {
|
| 79 |
+
display: flex; gap: 36px; list-style: none;
|
| 80 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 81 |
+
letter-spacing: 0.15em; color: var(--muted);
|
| 82 |
+
}
|
| 83 |
+
.nav-links a, .nav-links button {
|
| 84 |
+
color: inherit; text-decoration: none; background: none; border: none;
|
| 85 |
+
cursor: pointer; font: inherit; letter-spacing: inherit;
|
| 86 |
+
transition: color 0.2s;
|
| 87 |
+
}
|
| 88 |
+
.nav-links a:hover, .nav-links button:hover { color: var(--cyan); }
|
| 89 |
+
.nav-badge {
|
| 90 |
+
font-family: var(--font-mono); font-size: 10px;
|
| 91 |
+
letter-spacing: 0.12em; color: var(--green);
|
| 92 |
+
border: 1px solid rgba(0, 255, 136, 0.3);
|
| 93 |
+
padding: 4px 12px; border-radius: 2px;
|
| 94 |
+
background: rgba(0, 255, 136, 0.06);
|
| 95 |
+
display: flex; align-items: center; gap: 6px;
|
| 96 |
+
}
|
| 97 |
+
.nav-badge-dot {
|
| 98 |
+
width: 5px; height: 5px; background: currentColor; border-radius: 50%;
|
| 99 |
+
box-shadow: 0 0 8px currentColor;
|
| 100 |
+
animation: pulse-badge 1.5s ease-in-out infinite;
|
| 101 |
+
}
|
| 102 |
+
.nav-badge.running { color: var(--cyan); border-color: rgba(0, 245, 255, 0.3); background: rgba(0, 245, 255, 0.06); }
|
| 103 |
+
.nav-badge.complete { color: var(--amber); border-color: rgba(255, 184, 0, 0.3); background: rgba(255, 184, 0, 0.06); }
|
| 104 |
|
| 105 |
+
/* ── HERO ── */
|
| 106 |
+
.hero { position: relative; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; padding: 120px 48px 80px; overflow: hidden; }
|
| 107 |
+
.hero-scanline { position: absolute; inset: 0; z-index: 1; pointer-events: none; background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.04) 2px, rgba(0,0,0,0.04) 4px); }
|
| 108 |
+
.hero-canvas-wrap { position: absolute; inset: 0; z-index: 0; opacity: 0.55; }
|
| 109 |
+
.hero-canvas-wrap canvas { display: block; width: 100%; height: 100%; }
|
| 110 |
+
.hero-content { position: relative; z-index: 2; max-width: 760px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
.hero-tag {
|
| 113 |
+
font-family: var(--font-mono); font-size: 10px;
|
| 114 |
+
letter-spacing: 0.3em; color: var(--cyan);
|
| 115 |
+
margin-bottom: 24px; display: flex; align-items: center; gap: 10px;
|
| 116 |
+
}
|
| 117 |
+
.hero-tag::before { content: ''; display: block; width: 32px; height: 1px; background: var(--cyan); opacity: 0.6; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
.hero h1 {
|
| 120 |
+
font-family: var(--font-display);
|
| 121 |
+
font-size: clamp(32px, 5vw, 58px);
|
| 122 |
+
font-weight: 900; line-height: 1.1;
|
| 123 |
+
letter-spacing: -0.01em; color: var(--white);
|
| 124 |
+
margin-bottom: 24px;
|
| 125 |
+
}
|
| 126 |
+
.hero h1 .accent { color: var(--cyan); }
|
| 127 |
+
.hero h1 .accent-red { color: var(--red); }
|
| 128 |
|
| 129 |
+
.hero-sub {
|
| 130 |
+
font-family: var(--font-body); font-size: 14px; line-height: 1.9;
|
| 131 |
+
color: rgba(232, 244, 255, 0.55);
|
| 132 |
+
max-width: 560px; margin-bottom: 44px;
|
| 133 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
.hero-ctas { display: flex; gap: 16px; flex-wrap: wrap; }
|
| 136 |
+
|
| 137 |
+
.btn-primary {
|
| 138 |
+
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em;
|
| 139 |
+
padding: 14px 32px; background: var(--cyan); color: #03060f;
|
| 140 |
+
border: none; cursor: pointer; font-weight: 700; text-transform: uppercase;
|
| 141 |
+
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
|
| 142 |
+
transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
|
| 143 |
+
}
|
| 144 |
+
.btn-primary:hover { background: #fff; box-shadow: 0 0 30px rgba(0, 245, 255, 0.5); }
|
| 145 |
+
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
|
| 146 |
|
| 147 |
+
.btn-secondary {
|
| 148 |
+
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em;
|
| 149 |
+
padding: 13px 32px; background: transparent; color: var(--cyan);
|
| 150 |
+
border: 1px solid rgba(0, 245, 255, 0.35); cursor: pointer; text-transform: uppercase;
|
| 151 |
+
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
|
| 152 |
+
transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
|
| 153 |
+
}
|
| 154 |
+
.btn-secondary:hover { background: rgba(0, 245, 255, 0.08); border-color: var(--cyan); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
+
.btn-sm-ctrl {
|
| 157 |
+
font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.15em;
|
| 158 |
+
padding: 6px 14px; background: transparent; color: var(--cyan);
|
| 159 |
+
border: 1px solid rgba(0, 245, 255, 0.2); cursor: pointer; text-transform: uppercase;
|
| 160 |
+
transition: all 0.2s;
|
| 161 |
+
}
|
| 162 |
+
.btn-sm-ctrl:hover { background: rgba(0, 245, 255, 0.08); border-color: var(--cyan); }
|
| 163 |
+
.btn-sm-ctrl:disabled { opacity: 0.3; cursor: not-allowed; }
|
| 164 |
+
.btn-sm-ctrl.active { background: rgba(0, 245, 255, 0.12); border-color: var(--cyan); }
|
| 165 |
+
.btn-sm-ctrl.danger { color: var(--red); border-color: rgba(255, 45, 85, 0.3); }
|
| 166 |
+
.btn-sm-ctrl.danger:hover { background: rgba(255, 45, 85, 0.08); }
|
| 167 |
|
| 168 |
+
.hero-stats {
|
| 169 |
+
position: absolute; right: 48px; bottom: 80px; z-index: 2;
|
| 170 |
+
display: flex; flex-direction: column; gap: 20px; text-align: right;
|
| 171 |
+
}
|
| 172 |
+
.hero-stat-item { font-family: var(--font-mono); }
|
| 173 |
+
.hero-stat-num {
|
| 174 |
+
font-family: var(--font-display); font-size: 26px; font-weight: 700;
|
| 175 |
+
color: var(--cyan); line-height: 1;
|
| 176 |
+
text-shadow: 0 0 20px rgba(0, 245, 255, 0.4);
|
| 177 |
+
}
|
| 178 |
+
.hero-stat-label {
|
| 179 |
+
font-size: 9px; letter-spacing: 0.2em;
|
| 180 |
+
color: var(--muted); text-transform: uppercase;
|
| 181 |
+
}
|
| 182 |
|
| 183 |
+
/* ── SECTION SHARED ── */
|
| 184 |
+
.section-block { padding: 100px 48px; position: relative; }
|
| 185 |
+
.section-block.alt-bg { background: var(--bg2); }
|
| 186 |
+
.section-label {
|
| 187 |
+
font-family: var(--font-mono); font-size: 9px;
|
| 188 |
+
letter-spacing: 0.35em; color: var(--muted); text-transform: uppercase;
|
| 189 |
+
margin-bottom: 12px; display: flex; align-items: center; gap: 12px;
|
| 190 |
+
}
|
| 191 |
+
.section-label::after { content: ''; flex: 1; max-width: 60px; height: 1px; background: var(--muted); opacity: 0.4; }
|
| 192 |
+
.section-title {
|
| 193 |
+
font-family: var(--font-display);
|
| 194 |
+
font-size: clamp(22px, 3vw, 34px);
|
| 195 |
+
font-weight: 700; color: var(--white);
|
| 196 |
+
margin-bottom: 16px; letter-spacing: 0.02em;
|
| 197 |
+
}
|
| 198 |
+
.section-desc {
|
| 199 |
+
font-size: 13px; color: rgba(232, 244, 255, 0.4);
|
| 200 |
+
max-width: 480px; line-height: 1.8; margin-bottom: 60px;
|
| 201 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
/* ── DIVIDER ── */
|
| 204 |
+
.divider { width: calc(100% - 96px); height: 1px; background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent); margin: 0 48px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
+
/* ── FEATURE CARDS ── */
|
| 207 |
+
.cards-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 2px; }
|
| 208 |
+
.card {
|
| 209 |
+
background: var(--glass); border: 1px solid var(--glass-border);
|
| 210 |
+
padding: 32px 28px; position: relative; overflow: hidden;
|
| 211 |
+
transition: border-color 0.3s, background 0.3s;
|
| 212 |
+
}
|
| 213 |
+
.card::before {
|
| 214 |
+
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
| 215 |
+
background: linear-gradient(90deg, transparent, var(--card-accent, var(--cyan)), transparent);
|
| 216 |
+
opacity: 0; transition: opacity 0.3s;
|
| 217 |
+
}
|
| 218 |
+
.card:hover { background: var(--glass-hover); border-color: rgba(0, 200, 255, 0.25); }
|
| 219 |
+
.card:hover::before { opacity: 1; }
|
| 220 |
+
.card-id { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.3em; color: var(--card-accent, var(--cyan)); margin-bottom: 20px; opacity: 0.7; }
|
| 221 |
+
.card-icon { width: 40px; height: 40px; margin-bottom: 20px; }
|
| 222 |
+
.card-icon svg { width: 100%; height: 100%; }
|
| 223 |
+
.card-title { font-family: var(--font-display); font-size: 13px; font-weight: 600; letter-spacing: 0.08em; color: var(--white); margin-bottom: 12px; }
|
| 224 |
+
.card-body { font-size: 12px; line-height: 1.8; color: rgba(232, 244, 255, 0.45); }
|
| 225 |
+
.card-footer { margin-top: 24px; padding-top: 16px; border-top: 1px solid rgba(0, 200, 255, 0.08); display: flex; justify-content: space-between; align-items: center; }
|
| 226 |
+
.card-status { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.2em; color: var(--green); display: flex; align-items: center; gap: 6px; }
|
| 227 |
+
.status-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); animation: pulse-dot 1.5s ease-in-out infinite; }
|
| 228 |
+
.card-ver { font-family: var(--font-mono); font-size: 9px; color: var(--muted); }
|
| 229 |
|
| 230 |
+
/* ── SIMULATION ── */
|
| 231 |
+
.sim-wrapper {
|
| 232 |
+
background: #020810;
|
| 233 |
+
border: 1px solid rgba(0, 200, 255, 0.14); border-radius: 2px; overflow: hidden;
|
| 234 |
+
box-shadow: 0 0 80px rgba(0, 80, 200, 0.08), inset 0 0 80px rgba(0, 0, 0, 0.5);
|
| 235 |
+
}
|
| 236 |
+
.sim-topbar {
|
| 237 |
+
background: rgba(0, 20, 50, 0.9);
|
| 238 |
+
border-bottom: 1px solid rgba(0, 200, 255, 0.1);
|
| 239 |
+
padding: 10px 20px; display: flex; align-items: center; gap: 16px;
|
| 240 |
+
}
|
| 241 |
+
.sim-dots { display: flex; gap: 6px; }
|
| 242 |
+
.sim-dot { width: 10px; height: 10px; border-radius: 50%; }
|
| 243 |
+
.sim-dot.r { background: #ff2d55; }
|
| 244 |
+
.sim-dot.y { background: #ffb800; }
|
| 245 |
+
.sim-dot.g { background: #00ff88; }
|
| 246 |
+
.sim-topbar-title {
|
| 247 |
+
font-family: var(--font-mono); font-size: 10px;
|
| 248 |
+
letter-spacing: 0.2em; color: var(--muted); flex: 1; text-align: center;
|
| 249 |
+
}
|
| 250 |
+
.sim-topbar-badge {
|
| 251 |
+
font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.15em;
|
| 252 |
+
color: var(--green); border: 1px solid rgba(0, 255, 136, 0.25);
|
| 253 |
+
padding: 2px 8px; animation: pulse-badge 2s ease-in-out infinite;
|
| 254 |
+
}
|
| 255 |
+
@keyframes pulse-badge { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
| 256 |
+
.sim-body { display: grid; grid-template-columns: 240px 1fr 240px; min-height: 420px; }
|
| 257 |
+
.sim-panel {
|
| 258 |
+
border-right: 1px solid rgba(0, 200, 255, 0.08); padding: 20px 16px;
|
| 259 |
+
}
|
| 260 |
+
.sim-panel:last-child { border-right: none; border-left: 1px solid rgba(0, 200, 255, 0.08); }
|
| 261 |
+
.sim-panel-label {
|
| 262 |
+
font-family: var(--font-mono); font-size: 8px;
|
| 263 |
+
letter-spacing: 0.3em; color: var(--muted); text-transform: uppercase;
|
| 264 |
+
margin-bottom: 16px; padding-bottom: 8px;
|
| 265 |
+
border-bottom: 1px solid rgba(0, 200, 255, 0.08);
|
| 266 |
+
}
|
| 267 |
+
.sim-center { position: relative; overflow: hidden; }
|
| 268 |
+
.sim-center canvas { display: block; width: 100%; height: 100%; }
|
| 269 |
|
| 270 |
+
/* agent rows inside sim */
|
| 271 |
+
.agent-row { margin-bottom: 14px; position: relative; }
|
| 272 |
+
.agent-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
|
| 273 |
+
.agent-id { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.15em; }
|
| 274 |
+
.agent-id.active { color: var(--cyan); }
|
| 275 |
+
.agent-id.neutral { color: var(--muted); }
|
| 276 |
+
.agent-id.adversarial { color: var(--red); }
|
| 277 |
+
.agent-trust-val { font-family: var(--font-display); font-size: 11px; font-weight: 700; }
|
| 278 |
+
.trust-bar-bg { height: 4px; background: rgba(255,255,255,0.06); border-radius: 2px; overflow: hidden; }
|
| 279 |
+
.trust-bar-fill { height: 100%; border-radius: 2px; transition: width 0.8s ease; }
|
| 280 |
+
.trust-bar-fill.high { background: linear-gradient(90deg, #00a870, var(--green)); box-shadow: 0 0 6px rgba(0,255,136,0.5); }
|
| 281 |
+
.trust-bar-fill.mid { background: linear-gradient(90deg, #cc8800, var(--amber)); box-shadow: 0 0 6px rgba(255,184,0,0.4); }
|
| 282 |
+
.trust-bar-fill.low { background: linear-gradient(90deg, #aa0020, var(--red)); box-shadow: 0 0 6px rgba(255,45,85,0.5); }
|
| 283 |
+
.agent-state { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.15em; color: var(--muted); margin-top: 3px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
+
/* sim metrics */
|
| 286 |
+
.sim-metric-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid rgba(0, 200, 255, 0.06); }
|
| 287 |
+
.sim-metric-row:last-child { border-bottom: none; }
|
| 288 |
+
.sim-metric-label { font-family: var(--font-mono); font-size: 9px; color: var(--muted); letter-spacing: 0.1em; }
|
| 289 |
+
.sim-metric-val { font-family: var(--font-display); font-size: 13px; font-weight: 700; }
|
| 290 |
+
.sim-metric-val.c { color: var(--cyan); }
|
| 291 |
+
.sim-metric-val.g { color: var(--green); }
|
| 292 |
+
.sim-metric-val.r { color: var(--red); }
|
| 293 |
+
.sim-metric-val.a { color: var(--amber); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
+
/* log entries */
|
| 296 |
+
.log-entry {
|
| 297 |
+
font-family: var(--font-mono); font-size: 9px; line-height: 1.6;
|
| 298 |
+
color: rgba(232, 244, 255, 0.4); margin-bottom: 6px;
|
| 299 |
+
padding-left: 10px; border-left: 2px solid rgba(0, 200, 255, 0.1);
|
| 300 |
+
animation: log-slide 0.3s ease-out;
|
| 301 |
}
|
| 302 |
+
@keyframes log-slide { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
|
| 303 |
+
.log-entry.warn { color: rgba(255, 184, 0, 0.7); border-left-color: var(--amber); }
|
| 304 |
+
.log-entry.alert { color: rgba(255, 45, 85, 0.8); border-left-color: var(--red); }
|
| 305 |
+
.log-entry.ok { color: rgba(0, 255, 136, 0.7); border-left-color: var(--green); }
|
| 306 |
+
.log-time { color: var(--muted); margin-right: 6px; }
|
| 307 |
|
| 308 |
+
.sim-footer {
|
| 309 |
+
background: rgba(0, 10, 25, 0.8);
|
| 310 |
+
border-top: 1px solid rgba(0, 200, 255, 0.08);
|
| 311 |
+
padding: 10px 20px; display: flex; gap: 32px;
|
| 312 |
+
font-family: var(--font-mono); font-size: 9px; color: var(--muted);
|
| 313 |
+
flex-wrap: wrap;
|
| 314 |
+
}
|
| 315 |
+
.sim-footer span { display: flex; align-items: center; gap: 6px; }
|
| 316 |
+
.sim-footer b { color: var(--cyan); }
|
|
|
|
| 317 |
|
| 318 |
+
/* sim controls row */
|
| 319 |
+
.sim-controls-row {
|
| 320 |
+
display: flex; gap: 8px; padding: 12px 20px;
|
| 321 |
+
background: rgba(0, 10, 25, 0.6);
|
| 322 |
+
border-top: 1px solid rgba(0, 200, 255, 0.06);
|
| 323 |
+
flex-wrap: wrap; align-items: center;
|
| 324 |
+
}
|
| 325 |
+
.sim-controls-row .ctrl-label {
|
| 326 |
+
font-family: var(--font-mono); font-size: 8px;
|
| 327 |
+
letter-spacing: 0.25em; color: var(--muted); text-transform: uppercase;
|
| 328 |
+
margin-right: 8px;
|
| 329 |
+
}
|
| 330 |
|
| 331 |
+
/* ── ARCHITECTURE ── */
|
| 332 |
+
.arch-flow { display: flex; align-items: center; justify-content: center; gap: 0; flex-wrap: nowrap; overflow-x: auto; padding-bottom: 10px; }
|
| 333 |
+
.arch-node {
|
| 334 |
+
min-width: 140px; border: 1px solid var(--node-color, rgba(0, 200, 255, 0.25));
|
| 335 |
+
padding: 20px 16px; background: rgba(0, 20, 50, 0.5);
|
| 336 |
+
position: relative; text-align: center;
|
| 337 |
+
transition: border-color 0.3s, background 0.3s;
|
| 338 |
+
}
|
| 339 |
+
.arch-node::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(0, 200, 255, 0.03), transparent); }
|
| 340 |
+
.arch-node:hover { background: rgba(0, 200, 255, 0.08); border-color: var(--cyan); }
|
| 341 |
+
.arch-node-id { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.25em; color: var(--node-color, var(--muted)); margin-bottom: 8px; }
|
| 342 |
+
.arch-node-name { font-family: var(--font-display); font-size: 10px; font-weight: 600; letter-spacing: 0.1em; color: var(--white); margin-bottom: 8px; }
|
| 343 |
+
.arch-node-desc { font-size: 9px; color: var(--muted); line-height: 1.5; }
|
| 344 |
+
.arch-arrow { padding: 0 4px; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
| 345 |
+
.arch-arrow-line { width: 40px; height: 1px; background: linear-gradient(90deg, rgba(0, 200, 255, 0.2), rgba(0, 200, 255, 0.6)); position: relative; }
|
| 346 |
+
.arch-arrow-line::after { content: '▶'; position: absolute; right: -5px; top: -5px; font-size: 10px; color: rgba(0, 200, 255, 0.6); }
|
| 347 |
+
.arch-arrow-label { font-family: var(--font-mono); font-size: 8px; color: var(--muted); letter-spacing: 0.15em; white-space: nowrap; }
|
| 348 |
+
.arch-code-loop {
|
| 349 |
+
margin-top: 40px; padding: 24px;
|
| 350 |
+
background: rgba(0, 20, 50, 0.5);
|
| 351 |
+
border: 1px solid rgba(0, 200, 255, 0.1);
|
| 352 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 353 |
+
color: rgba(232, 244, 255, 0.4); line-height: 2;
|
| 354 |
+
}
|
| 355 |
|
| 356 |
+
/* ── METRICS ── */
|
| 357 |
+
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 2px; }
|
| 358 |
+
.metric-block {
|
| 359 |
+
background: var(--glass); border: 1px solid var(--glass-border);
|
| 360 |
+
padding: 36px 28px; position: relative; overflow: hidden;
|
| 361 |
+
transition: border-color 0.3s;
|
| 362 |
+
}
|
| 363 |
+
.metric-block::after {
|
| 364 |
+
content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 3px;
|
| 365 |
+
background: linear-gradient(90deg, transparent, var(--m-color, var(--cyan)), transparent);
|
| 366 |
+
opacity: 0.5;
|
| 367 |
+
}
|
| 368 |
+
.metric-block:hover { border-color: rgba(0, 200, 255, 0.25); }
|
| 369 |
+
.metric-ref { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.25em; color: var(--muted); margin-bottom: 20px; }
|
| 370 |
+
.metric-value {
|
| 371 |
+
font-family: var(--font-display); font-size: 52px; font-weight: 900;
|
| 372 |
+
line-height: 1; color: var(--m-color, var(--cyan));
|
| 373 |
+
text-shadow: 0 0 40px rgba(0, 245, 255, 0.25);
|
| 374 |
+
margin-bottom: 8px; letter-spacing: -0.02em;
|
| 375 |
+
}
|
| 376 |
+
.metric-unit { font-family: var(--font-display); font-size: 24px; font-weight: 400; opacity: 0.5; vertical-align: super; }
|
| 377 |
+
.metric-label { font-family: var(--font-display); font-size: 11px; font-weight: 600; letter-spacing: 0.1em; color: var(--white); margin-bottom: 6px; }
|
| 378 |
+
.metric-sub { font-size: 11px; color: var(--muted); line-height: 1.7; }
|
| 379 |
+
.metric-bar-wrap { margin-top: 20px; }
|
| 380 |
+
.metric-bar-bg { height: 2px; background: rgba(255,255,255,0.05); border-radius: 1px; overflow: hidden; }
|
| 381 |
+
.metric-bar-fill { height: 100%; background: var(--m-color, var(--cyan)); border-radius: 1px; box-shadow: 0 0 8px var(--m-color, var(--cyan)); transition: width 1.2s ease; }
|
| 382 |
+
.metric-bar-label { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 9px; color: var(--muted); margin-top: 6px; }
|
| 383 |
|
| 384 |
+
/* ── FOOTER ── */
|
| 385 |
+
.site-footer {
|
| 386 |
+
background: var(--bg2); border-top: 1px solid rgba(0, 200, 255, 0.08);
|
| 387 |
+
padding: 40px 48px; display: flex; justify-content: space-between;
|
| 388 |
+
align-items: center; flex-wrap: wrap; gap: 20px;
|
| 389 |
+
}
|
| 390 |
+
.footer-left { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
|
| 391 |
+
.footer-left strong { color: var(--cyan); font-family: var(--font-display); letter-spacing: 0.2em; }
|
| 392 |
+
.footer-right { font-family: var(--font-mono); font-size: 9px; color: rgba(74, 96, 128, 0.5); }
|
| 393 |
|
| 394 |
+
/* ── ANIMATIONS ── */
|
| 395 |
+
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
| 396 |
+
.anim-1 { animation: fadeInUp 0.8s ease both; }
|
| 397 |
+
.anim-2 { animation: fadeInUp 0.8s 0.15s ease both; }
|
| 398 |
+
.anim-3 { animation: fadeInUp 0.8s 0.3s ease both; }
|
| 399 |
+
.anim-4 { animation: fadeInUp 0.8s 0.45s ease both; }
|
| 400 |
+
.anim-5 { animation: fadeInUp 0.8s 0.6s ease both; }
|
| 401 |
|
| 402 |
+
/* ── RESPONSIVE ── */
|
| 403 |
+
@media (max-width: 900px) {
|
| 404 |
+
.nav-bar { padding: 12px 20px; }
|
| 405 |
+
.nav-links { display: none; }
|
| 406 |
+
.hero { padding: 100px 20px 60px; }
|
| 407 |
+
.hero-stats { position: static; flex-direction: row; gap: 24px; margin-top: 40px; text-align: left; }
|
| 408 |
+
.section-block { padding: 60px 20px; }
|
| 409 |
+
.sim-body { grid-template-columns: 1fr; }
|
| 410 |
+
.sim-panel { border-right: none; border-bottom: 1px solid rgba(0,200,255,0.08); }
|
| 411 |
+
.sim-panel:last-child { border-left: none; border-top: 1px solid rgba(0,200,255,0.08); }
|
| 412 |
+
.arch-flow { flex-wrap: wrap; justify-content: center; }
|
| 413 |
+
.divider { margin: 0 20px; width: calc(100% - 40px); }
|
| 414 |
+
}
|
|
|
|
|
|
|
|
|
ui/app/page.tsx
CHANGED
|
@@ -1,54 +1,236 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { useEffect } from "react";
|
| 4 |
import { useSentinel } from "./hooks/useSentinel";
|
| 5 |
-
import
|
| 6 |
-
import
|
| 7 |
-
import
|
| 8 |
-
import
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
{ id: "judge", label: "Judge Demo" },
|
| 14 |
-
];
|
| 15 |
-
|
| 16 |
-
const TASKS: { value: TaskType; label: string }[] = [
|
| 17 |
-
{ value: "task1", label: "Task 1" },
|
| 18 |
-
{ value: "task2", label: "Task 2" },
|
| 19 |
-
{ value: "task3", label: "Task 3" },
|
| 20 |
-
];
|
| 21 |
|
| 22 |
export default function Page() {
|
| 23 |
const s = useSentinel();
|
| 24 |
|
| 25 |
-
/* auto-reset on first mount */
|
| 26 |
useEffect(() => {
|
| 27 |
void s.resetEpisode();
|
| 28 |
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 29 |
}, []);
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
return (
|
| 32 |
-
<
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
<
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
<button
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
>
|
| 45 |
-
|
| 46 |
</button>
|
| 47 |
-
|
| 48 |
-
</
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
<select
|
| 53 |
value={s.taskType}
|
| 54 |
onChange={(e) => {
|
|
@@ -56,71 +238,58 @@ export default function Page() {
|
|
| 56 |
s.setTaskType(next);
|
| 57 |
void s.resetEpisode(next, s.seed);
|
| 58 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
>
|
| 60 |
-
{
|
| 61 |
-
|
| 62 |
-
)
|
| 63 |
</select>
|
| 64 |
</div>
|
| 65 |
-
<div className="hdr-pill">
|
| 66 |
-
<label>Seed</label>
|
| 67 |
-
<input
|
| 68 |
-
type="number"
|
| 69 |
-
value={s.seed}
|
| 70 |
-
onChange={(e) => s.setSeed(Number(e.target.value || 0))}
|
| 71 |
-
/>
|
| 72 |
-
</div>
|
| 73 |
-
<button className="btn" onClick={() => void s.resetEpisode()}>
|
| 74 |
-
Reset
|
| 75 |
-
</button>
|
| 76 |
-
<button className="btn btn-ghost" onClick={() => void s.swapProfiles()}>
|
| 77 |
-
Swap Profiles
|
| 78 |
-
</button>
|
| 79 |
</div>
|
| 80 |
-
</
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
/>
|
| 122 |
-
)}
|
| 123 |
-
</main>
|
| 124 |
-
</div>
|
| 125 |
);
|
| 126 |
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useEffect, useMemo } from "react";
|
| 4 |
import { useSentinel } from "./hooks/useSentinel";
|
| 5 |
+
import HeroCanvas from "./components/HeroCanvas";
|
| 6 |
+
import SystemModules from "./components/SystemModules";
|
| 7 |
+
import AgentTrustMonitor from "./components/AgentTrustMonitor";
|
| 8 |
+
import SimCanvas from "./components/SimCanvas";
|
| 9 |
+
import ExecutionLog from "./components/ExecutionLog";
|
| 10 |
+
import ArchitecturePipeline from "./components/ArchitecturePipeline";
|
| 11 |
+
import MetricsGrid from "./components/MetricsGrid";
|
| 12 |
+
import type { TaskType, AutoPolicy } from "./lib/types";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
export default function Page() {
|
| 15 |
const s = useSentinel();
|
| 16 |
|
|
|
|
| 17 |
useEffect(() => {
|
| 18 |
void s.resetEpisode();
|
| 19 |
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 20 |
}, []);
|
| 21 |
|
| 22 |
+
const adversarialAgents = useMemo(() => new Set(
|
| 23 |
+
s.events
|
| 24 |
+
.filter(e => e.outcome === "poisoned" || e.outcome === "blocked")
|
| 25 |
+
.map(e => e.specialist)
|
| 26 |
+
.filter((x): x is string => !!x)
|
| 27 |
+
), [s.events]);
|
| 28 |
+
|
| 29 |
+
const totalReward = s.info?.total_reward ?? 0;
|
| 30 |
+
|
| 31 |
return (
|
| 32 |
+
<>
|
| 33 |
+
{/* NAV */}
|
| 34 |
+
<nav className="nav-bar">
|
| 35 |
+
<div className="nav-logo">
|
| 36 |
+
<div className="nav-logo-dot" />
|
| 37 |
+
SENTINEL
|
| 38 |
+
</div>
|
| 39 |
+
<ul className="nav-links">
|
| 40 |
+
<li><a href="#overview">MODULES</a></li>
|
| 41 |
+
<li><a href="#simulation">SIMULATION</a></li>
|
| 42 |
+
<li><a href="#architecture">ARCHITECTURE</a></li>
|
| 43 |
+
<li><a href="#metrics">METRICS</a></li>
|
| 44 |
+
</ul>
|
| 45 |
+
<div className={`nav-badge ${s.running ? "running" : s.done ? "complete" : ""}`}>
|
| 46 |
+
<div className="nav-badge-dot" />
|
| 47 |
+
{s.running ? "RUNNING" : s.done ? "COMPLETE" : "SYSTEM ONLINE"}
|
| 48 |
+
</div>
|
| 49 |
+
</nav>
|
| 50 |
+
|
| 51 |
+
{/* HERO */}
|
| 52 |
+
<section className="hero" id="hero">
|
| 53 |
+
<HeroCanvas />
|
| 54 |
+
<div className="hero-scanline" />
|
| 55 |
+
|
| 56 |
+
<div className="hero-content">
|
| 57 |
+
<div className="hero-tag anim-1">
|
| 58 |
+
SYS.CORE // SENTINEL v2.4.1 // MARL FRAMEWORK
|
| 59 |
+
</div>
|
| 60 |
+
<h1 className="anim-2">
|
| 61 |
+
Train AI to <span className="accent">Trust</span> —<br />
|
| 62 |
+
and Survive <span className="accent-red">Adversaries</span>
|
| 63 |
+
</h1>
|
| 64 |
+
<p className="hero-sub anim-3">
|
| 65 |
+
A multi-agent reinforcement learning system where an orchestrator
|
| 66 |
+
learns to detect deception, assign trust, and optimize decisions in
|
| 67 |
+
real-time adversarial environments.
|
| 68 |
+
</p>
|
| 69 |
+
<div className="hero-ctas anim-4">
|
| 70 |
<button
|
| 71 |
+
className="btn-primary"
|
| 72 |
+
onClick={() => {
|
| 73 |
+
document.getElementById("simulation")?.scrollIntoView({ behavior: "smooth" });
|
| 74 |
+
setTimeout(() => {
|
| 75 |
+
void s.resetEpisode().then(() => s.autoRun("trained" as AutoPolicy));
|
| 76 |
+
}, 400);
|
| 77 |
+
}}
|
| 78 |
+
disabled={s.running}
|
| 79 |
+
>
|
| 80 |
+
▶ Launch Simulation
|
| 81 |
+
</button>
|
| 82 |
+
<button
|
| 83 |
+
className="btn-secondary"
|
| 84 |
+
onClick={() => document.getElementById("architecture")?.scrollIntoView({ behavior: "smooth" })}
|
| 85 |
>
|
| 86 |
+
View System Architecture
|
| 87 |
</button>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div className="hero-stats anim-5">
|
| 92 |
+
<div className="hero-stat-item">
|
| 93 |
+
<div className="hero-stat-num">{s.observation?.available_specialists?.length ?? 5}</div>
|
| 94 |
+
<div className="hero-stat-label">Active Agents</div>
|
| 95 |
+
</div>
|
| 96 |
+
<div className="hero-stat-item">
|
| 97 |
+
<div className="hero-stat-num">
|
| 98 |
+
{s.proof?.trained
|
| 99 |
+
? Math.round(s.proof.trained.avg_detection_rate * 100)
|
| 100 |
+
: 92
|
| 101 |
+
}<span style={{ fontSize: 14, opacity: 0.4 }}>%</span>
|
| 102 |
+
</div>
|
| 103 |
+
<div className="hero-stat-label">Trust Accuracy</div>
|
| 104 |
+
</div>
|
| 105 |
+
<div className="hero-stat-item">
|
| 106 |
+
<div className="hero-stat-num">
|
| 107 |
+
{s.proof?.trained
|
| 108 |
+
? s.proof.trained.avg_score.toFixed(2)
|
| 109 |
+
: "0.91"
|
| 110 |
+
}
|
| 111 |
+
</div>
|
| 112 |
+
<div className="hero-stat-label">Avg Score</div>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
</section>
|
| 116 |
+
|
| 117 |
+
<div className="divider" />
|
| 118 |
+
|
| 119 |
+
{/* SYSTEM MODULES */}
|
| 120 |
+
<section className="section-block alt-bg" id="overview">
|
| 121 |
+
<div className="section-label">01 // SYSTEM MODULES</div>
|
| 122 |
+
<h2 className="section-title">Core Architecture</h2>
|
| 123 |
+
<p className="section-desc">
|
| 124 |
+
Each module operates as an independent inference layer within the
|
| 125 |
+
trust-calibration pipeline. All components communicate via the
|
| 126 |
+
orchestration bus.
|
| 127 |
+
</p>
|
| 128 |
+
<SystemModules
|
| 129 |
+
running={s.running}
|
| 130 |
+
done={s.done}
|
| 131 |
+
adversarialCount={adversarialAgents.size}
|
| 132 |
+
/>
|
| 133 |
+
</section>
|
| 134 |
+
|
| 135 |
+
<div className="divider" />
|
| 136 |
+
|
| 137 |
+
{/* SIMULATION */}
|
| 138 |
+
<section className="section-block" id="simulation">
|
| 139 |
+
<div className="section-label">02 // LIVE PREVIEW</div>
|
| 140 |
+
<h2 className="section-title">Simulation Control Panel</h2>
|
| 141 |
+
<p className="section-desc">
|
| 142 |
+
Real-time orchestrator view. Agent trust scores update per-step. Red
|
| 143 |
+
indicates flagged adversarial behaviour.
|
| 144 |
+
</p>
|
| 145 |
+
|
| 146 |
+
<div className="sim-wrapper">
|
| 147 |
+
<div className="sim-topbar">
|
| 148 |
+
<div className="sim-dots">
|
| 149 |
+
<div className="sim-dot r" />
|
| 150 |
+
<div className="sim-dot y" />
|
| 151 |
+
<div className="sim-dot g" />
|
| 152 |
+
</div>
|
| 153 |
+
<div className="sim-topbar-title">
|
| 154 |
+
SENTINEL // ORCHESTRATOR VIEW // TASK: {s.taskType?.toUpperCase() ?? "TASK3"} // STEP: {s.observation?.step_count ?? 0}
|
| 155 |
+
</div>
|
| 156 |
+
<div className="sim-topbar-badge">
|
| 157 |
+
● {s.running ? "LIVE" : s.done ? "DONE" : "READY"}
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
<div className="sim-body">
|
| 162 |
+
{/* LEFT: AGENTS */}
|
| 163 |
+
<AgentTrustMonitor
|
| 164 |
+
observation={s.observation}
|
| 165 |
+
trustDeltas={s.trustDeltas}
|
| 166 |
+
activeSpec={s.activeSpec}
|
| 167 |
+
events={s.events}
|
| 168 |
+
running={s.running}
|
| 169 |
+
totalReward={totalReward}
|
| 170 |
+
/>
|
| 171 |
+
|
| 172 |
+
{/* CENTER: CANVAS */}
|
| 173 |
+
<div className="sim-center">
|
| 174 |
+
<SimCanvas
|
| 175 |
+
trustSnapshot={s.observation?.trust_snapshot ?? {}}
|
| 176 |
+
adversarialAgents={adversarialAgents}
|
| 177 |
+
activeSpec={s.activeSpec}
|
| 178 |
+
/>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
{/* RIGHT: LOGS */}
|
| 182 |
+
<ExecutionLog
|
| 183 |
+
events={s.events}
|
| 184 |
+
observation={s.observation}
|
| 185 |
+
info={s.info}
|
| 186 |
+
/>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
{/* Controls Row */}
|
| 190 |
+
<div className="sim-controls-row">
|
| 191 |
+
<span className="ctrl-label">POLICY:</span>
|
| 192 |
+
<button className="btn-sm-ctrl" onClick={() => void s.autoRun("heuristic" as AutoPolicy)} disabled={s.running || s.done}>
|
| 193 |
+
▶ HEURISTIC
|
| 194 |
+
</button>
|
| 195 |
+
<button className="btn-sm-ctrl" onClick={() => void s.autoRun("random" as AutoPolicy)} disabled={s.running || s.done}>
|
| 196 |
+
⚄ RANDOM
|
| 197 |
+
</button>
|
| 198 |
+
<button className="btn-sm-ctrl" onClick={() => void s.autoRun("trained" as AutoPolicy)} disabled={s.running || s.done}>
|
| 199 |
+
🧠 TRAINED RL
|
| 200 |
+
</button>
|
| 201 |
+
{s.running && (
|
| 202 |
+
<button className="btn-sm-ctrl danger" onClick={s.stopAutoRun}>
|
| 203 |
+
■ STOP
|
| 204 |
+
</button>
|
| 205 |
+
)}
|
| 206 |
+
<span className="ctrl-label" style={{ marginLeft: "auto" }}>ACTIONS:</span>
|
| 207 |
+
<button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("delegate")} disabled={s.running || s.done}>
|
| 208 |
+
DELEGATE
|
| 209 |
+
</button>
|
| 210 |
+
<button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("verify")} disabled={s.running || s.done}>
|
| 211 |
+
VERIFY
|
| 212 |
+
</button>
|
| 213 |
+
<button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("skip")} disabled={s.running || s.done}>
|
| 214 |
+
SKIP
|
| 215 |
+
</button>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
{/* Sim Footer */}
|
| 219 |
+
<div className="sim-footer">
|
| 220 |
+
<span>TASK: <b>{s.taskType?.toUpperCase() ?? "TASK3"}</b></span>
|
| 221 |
+
<span>SEED: <b>{s.seed}</b></span>
|
| 222 |
+
<span>ALGO: <b>DQN+TCE</b></span>
|
| 223 |
+
<span>SESSION: <b>{s.sessionId?.slice(0, 8) ?? "—"}</b></span>
|
| 224 |
+
<span style={{ marginLeft: "auto" }}>
|
| 225 |
+
<button
|
| 226 |
+
className="btn-sm-ctrl"
|
| 227 |
+
onClick={() => void s.resetEpisode()}
|
| 228 |
+
disabled={s.running}
|
| 229 |
+
style={{ fontSize: 8 }}
|
| 230 |
+
>
|
| 231 |
+
⟳ RESET EPISODE
|
| 232 |
+
</button>
|
| 233 |
+
</span>
|
| 234 |
<select
|
| 235 |
value={s.taskType}
|
| 236 |
onChange={(e) => {
|
|
|
|
| 238 |
s.setTaskType(next);
|
| 239 |
void s.resetEpisode(next, s.seed);
|
| 240 |
}}
|
| 241 |
+
style={{
|
| 242 |
+
background: "transparent", border: "1px solid rgba(0,200,255,0.2)",
|
| 243 |
+
color: "var(--cyan)", fontFamily: "var(--font-mono)", fontSize: 9,
|
| 244 |
+
padding: "2px 6px", cursor: "pointer",
|
| 245 |
+
}}
|
| 246 |
>
|
| 247 |
+
<option value="task1" style={{ background: "var(--bg)" }}>TASK 1</option>
|
| 248 |
+
<option value="task2" style={{ background: "var(--bg)" }}>TASK 2</option>
|
| 249 |
+
<option value="task3" style={{ background: "var(--bg)" }}>TASK 3</option>
|
| 250 |
</select>
|
| 251 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
</div>
|
| 253 |
+
</section>
|
| 254 |
+
|
| 255 |
+
<div className="divider" />
|
| 256 |
+
|
| 257 |
+
{/* ARCHITECTURE */}
|
| 258 |
+
<section className="section-block alt-bg" id="architecture">
|
| 259 |
+
<div className="section-label">03 // SYSTEM DESIGN</div>
|
| 260 |
+
<h2 className="section-title">Execution Pipeline</h2>
|
| 261 |
+
<p className="section-desc">
|
| 262 |
+
Data flows unidirectionally through the trust-calibrated RL loop. Each
|
| 263 |
+
stage emits telemetry to the monitoring bus.
|
| 264 |
+
</p>
|
| 265 |
+
<ArchitecturePipeline />
|
| 266 |
+
</section>
|
| 267 |
+
|
| 268 |
+
<div className="divider" />
|
| 269 |
+
|
| 270 |
+
{/* METRICS */}
|
| 271 |
+
<section className="section-block" id="metrics">
|
| 272 |
+
<div className="section-label">04 // EVALUATION RESULTS</div>
|
| 273 |
+
<h2 className="section-title">Experimental Benchmarks</h2>
|
| 274 |
+
<p className="section-desc">
|
| 275 |
+
Averaged across evaluation episodes. Adversarial injection ratio fixed at
|
| 276 |
+
20%. Baseline: naive averaging orchestrator without trust calibration.
|
| 277 |
+
</p>
|
| 278 |
+
<MetricsGrid proof={s.proof} />
|
| 279 |
+
</section>
|
| 280 |
+
|
| 281 |
+
{/* FOOTER */}
|
| 282 |
+
<footer className="site-footer">
|
| 283 |
+
<div className="footer-left">
|
| 284 |
+
<strong>SENTINEL</strong><br />
|
| 285 |
+
Multi-Agent Reinforcement Learning System<br />
|
| 286 |
+
Research prototype — not for production use.
|
| 287 |
+
</div>
|
| 288 |
+
<div className="footer-right">
|
| 289 |
+
BUILD 2.4.1 // MARL-FRAMEWORK // MIT LICENSE<br />
|
| 290 |
+
© 2025 SENTINEL LAB. ALL RIGHTS RESERVED.
|
| 291 |
+
</div>
|
| 292 |
+
</footer>
|
| 293 |
+
</>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
);
|
| 295 |
}
|