Initial commit from Martins Github
This commit is contained in:
348
ai-slides.html
Normal file
348
ai-slides.html
Normal file
@@ -0,0 +1,348 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>P5G Marvis AI — Architecture</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0c0f1a;
|
||||
--surface: #131825;
|
||||
--card: #1a2035;
|
||||
--border: #252e48;
|
||||
--text: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
--purple: #7c3aed;
|
||||
--blue: #3b82f6;
|
||||
--green: #10b981;
|
||||
--yellow: #f59e0b;
|
||||
--red: #ef4444;
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { height: 100%; background: #07090f; font-family: var(--font); }
|
||||
|
||||
/* ── Deck navigation ──────────────────────────────────────────── */
|
||||
.deck { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; }
|
||||
.slide { display: none; width: 1100px; aspect-ratio: 16/9; background: var(--bg); border: 1px solid var(--border); border-radius: 18px; overflow: hidden; box-shadow: 0 30px 80px rgba(0,0,0,.7); position: relative; }
|
||||
.slide.active { display: flex; flex-direction: column; }
|
||||
.nav { display: flex; gap: 12px; align-items: center; }
|
||||
.nav button { background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 8px 22px; border-radius: 8px; font-size: 13px; cursor: pointer; font-family: var(--font); transition: all .15s; }
|
||||
.nav button:hover { border-color: var(--blue); color: #fff; }
|
||||
.nav button:disabled { opacity: .3; cursor: default; border-color: var(--border); }
|
||||
.slide-indicator { color: var(--muted); font-size: 12px; min-width: 60px; text-align: center; }
|
||||
|
||||
/* ── Shared layout ──────────────────────────────────────────── */
|
||||
.slide-header { padding: 28px 40px 0; display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
|
||||
.slide-pretitle { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .14em; color: var(--purple); }
|
||||
.slide-title { font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--text); margin-top: 4px; }
|
||||
.slide-subtitle { font-size: 13px; color: var(--muted); margin-top: 4px; }
|
||||
.slide-body { flex: 1; padding: 22px 40px 28px; display: flex; gap: 20px; overflow: hidden; }
|
||||
|
||||
/* ── Slide 1 layout ──────────────────────────────────────────── */
|
||||
#s1 .slide-body { flex-direction: column; gap: 16px; }
|
||||
.flow { display: flex; align-items: stretch; gap: 0; flex: 1; }
|
||||
.flow-col { display: flex; flex-direction: column; gap: 0; }
|
||||
|
||||
/* Nodes */
|
||||
.node {
|
||||
background: var(--card); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 10px 14px; position: relative; display: flex; flex-direction: column;
|
||||
justify-content: center; font-size: 12px;
|
||||
}
|
||||
.node-label { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); margin-bottom: 4px; }
|
||||
.node-name { font-size: 13px; font-weight: 700; color: var(--text); }
|
||||
.node-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }
|
||||
.node.purple { border-color: rgba(124,58,237,.5); background: rgba(124,58,237,.08); }
|
||||
.node.blue { border-color: rgba(59,130,246,.5); background: rgba(59,130,246,.08); }
|
||||
.node.green { border-color: rgba(16,185,129,.5); background: rgba(16,185,129,.08); }
|
||||
.node.yellow { border-color: rgba(245,158,11,.5); background: rgba(245,158,11,.08); }
|
||||
.node.red { border-color: rgba(239,68,68,.5); background: rgba(239,68,68,.08); }
|
||||
|
||||
/* Arrows */
|
||||
.arrow { display: flex; align-items: center; justify-content: center; padding: 0 6px; color: var(--muted); font-size: 15px; flex-shrink: 0; position: relative; }
|
||||
.arrow-label { position: absolute; top: -11px; font-size: 8.5px; font-weight: 600; color: var(--blue); white-space: nowrap; letter-spacing: .03em; }
|
||||
.arrow-label.below { top: auto; bottom: -11px; }
|
||||
.arrow svg { width: 20px; height: 20px; }
|
||||
|
||||
/* Main flow row */
|
||||
.flow-row { display: flex; align-items: center; gap: 0; }
|
||||
|
||||
/* Context box */
|
||||
.ctx-box {
|
||||
border: 1px dashed var(--border); border-radius: 10px; padding: 10px 14px;
|
||||
display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
|
||||
background: rgba(255,255,255,.02);
|
||||
}
|
||||
.ctx-tag {
|
||||
font-size: 10px; padding: 3px 9px; border-radius: 20px; font-weight: 600;
|
||||
background: var(--card); border: 1px solid var(--border); color: var(--muted);
|
||||
display: flex; align-items: center; gap: 5px;
|
||||
}
|
||||
.ctx-tag b { color: var(--text); }
|
||||
.ctx-label { font-size: 9px; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); font-weight: 700; margin-bottom: 5px; }
|
||||
|
||||
/* ── Slide 2 layout ──────────────────────────────────────────── */
|
||||
#s2 .slide-body { display: grid; grid-template-columns: 1fr 1px 1fr; gap: 0; }
|
||||
.divider { background: var(--border); }
|
||||
.s2-col { padding: 0 28px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.s2-col:first-child { padding-left: 0; }
|
||||
.s2-col:last-child { padding-right: 0; }
|
||||
|
||||
.s2-section { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .12em; color: var(--purple); margin-bottom: 4px; }
|
||||
|
||||
.list-item { display: flex; gap: 10px; align-items: flex-start; }
|
||||
.list-icon { font-size: 16px; flex-shrink: 0; width: 22px; text-align: center; margin-top: 1px; }
|
||||
.list-text { flex: 1; }
|
||||
.list-title { font-size: 13px; font-weight: 700; color: var(--text); }
|
||||
.list-desc { font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.5; }
|
||||
|
||||
.code-block {
|
||||
background: #0a0d16; border: 1px solid var(--border); border-radius: 8px;
|
||||
padding: 10px 14px; font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
|
||||
font-size: 10.5px; line-height: 1.7; color: #94a3b8; overflow: hidden;
|
||||
}
|
||||
.code-block .k { color: #7c3aed; }
|
||||
.code-block .s { color: #10b981; }
|
||||
.code-block .c { color: #4b5563; font-style: italic; }
|
||||
.code-block .v { color: #f59e0b; }
|
||||
|
||||
.kv-row { display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid var(--border); }
|
||||
.kv-row:last-child { border-bottom: none; }
|
||||
.kv-key { font-size: 11px; color: var(--muted); }
|
||||
.kv-val { font-size: 11px; font-weight: 700; color: var(--text); }
|
||||
.kv-val.green { color: var(--green); }
|
||||
.kv-val.yellow { color: var(--yellow); }
|
||||
.kv-val.blue { color: var(--blue); }
|
||||
.kv-val.purple { color: var(--purple); }
|
||||
|
||||
/* Slide number watermark */
|
||||
.slide-num { position: absolute; bottom: 16px; right: 22px; font-size: 10px; color: var(--border); font-weight: 600; }
|
||||
|
||||
/* Gradient accent line at top */
|
||||
.slide::before { content:''; position:absolute; top:0; left:0; right:0; height:3px; background: linear-gradient(90deg, var(--purple), var(--blue), var(--green)); border-radius: 18px 18px 0 0; }
|
||||
|
||||
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 10px; font-weight: 700; padding: 3px 10px; border-radius: 20px; }
|
||||
.badge.purple { background: rgba(124,58,237,.15); color: var(--purple); border: 1px solid rgba(124,58,237,.4); }
|
||||
.badge.green { background: rgba(16,185,129,.12); color: var(--green); border: 1px solid rgba(16,185,129,.4); }
|
||||
.badge.blue { background: rgba(59,130,246,.12); color: var(--blue); border: 1px solid rgba(59,130,246,.4); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="deck">
|
||||
|
||||
<!-- ═══════════════════ SLIDE 1 — Architecture ═════════════════════ -->
|
||||
<div class="slide active" id="s1">
|
||||
<div class="slide-header">
|
||||
<div>
|
||||
<div class="slide-pretitle">P5G Marvis · AI Integration</div>
|
||||
<div class="slide-title">End-to-End Request Flow</div>
|
||||
<div class="slide-subtitle">How a natural-language query becomes a network-aware AI response</div>
|
||||
</div>
|
||||
<div style="margin-left:auto;display:flex;gap:8px">
|
||||
<span class="badge purple">On-Prem LLM</span>
|
||||
<span class="badge green">No data leaves the network</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-body">
|
||||
|
||||
<!-- Main flow -->
|
||||
<div class="flow-row" style="gap:0;align-items:stretch">
|
||||
|
||||
<!-- NCM Browser -->
|
||||
<div class="node purple" style="width:155px;flex-shrink:0">
|
||||
<div class="node-label">1 · Operator</div>
|
||||
<div class="node-name">NCM Browser</div>
|
||||
<div class="node-sub">React SPA (HPE NCM)</div>
|
||||
<div class="node-sub" style="margin-top:6px;font-size:9px;color:#7c3aed">P5G Marvis AI pane<br>loaded in iframe</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<span class="arrow-label">HTTPS query</span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</div>
|
||||
|
||||
<!-- Traefik -->
|
||||
<div class="node blue" style="width:140px;flex-shrink:0">
|
||||
<div class="node-label">2 · Proxy</div>
|
||||
<div class="node-name">Traefik</div>
|
||||
<div class="node-sub">172.27.0.159</div>
|
||||
<div class="node-sub" style="margin-top:6px;font-size:9px;color:#3b82f6">/core/marvis/*<br>→ :8100 strip prefix</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<span class="arrow-label">POST /api/query</span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</div>
|
||||
|
||||
<!-- FastAPI -->
|
||||
<div class="node blue" style="width:170px;flex-shrink:0">
|
||||
<div class="node-label">3 · Backend</div>
|
||||
<div class="node-name">p5g-marvis</div>
|
||||
<div class="node-sub">FastAPI · :8100</div>
|
||||
<div class="node-sub" style="margin-top:6px;font-size:9px;color:#3b82f6">Fetches live NF status<br>+ active alerts<br>→ builds system prompt</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<span class="arrow-label">OpenAI-compat API</span>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</div>
|
||||
|
||||
<!-- LLM -->
|
||||
<div class="node green" style="flex:1">
|
||||
<div class="node-label">4 · Inference</div>
|
||||
<div class="node-name">llama.cpp server</div>
|
||||
<div class="node-sub">172.27.0.135:8001 · HTTPS · self-signed TLS</div>
|
||||
<div style="margin-top:7px;display:flex;gap:6px;flex-wrap:wrap">
|
||||
<span style="font-size:9px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.35);color:#10b981;padding:2px 7px;border-radius:10px">Gemma 4 · 26B</span>
|
||||
<span style="font-size:9px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.35);color:#10b981;padding:2px 7px;border-radius:10px">Q4_K_S quant</span>
|
||||
<span style="font-size:9px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.35);color:#10b981;padding:2px 7px;border-radius:10px">Reasoning model</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Context enrichment box -->
|
||||
<div>
|
||||
<div class="ctx-label">Context injected into system prompt by p5g-marvis before every LLM call</div>
|
||||
<div class="ctx-box">
|
||||
<div class="ctx-tag"><span>📡</span> <b>12 NF states</b> (UDR, AMF, SMF, UPF…)</div>
|
||||
<div class="ctx-tag"><span>🔴</span> <b>Active alerts</b> (name, severity, summary)</div>
|
||||
<div class="ctx-tag"><span>🕐</span> <b>Timestamp</b></div>
|
||||
<div class="ctx-tag"><span>📝</span> <b>User query</b> (natural language)</div>
|
||||
<div style="flex:1"></div>
|
||||
<div style="font-size:9px;color:var(--muted);text-align:right;line-height:1.6">
|
||||
No training data leaves the site.<br>
|
||||
Context window: ~1 024 tokens out · 120 s timeout.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Return path note -->
|
||||
<div style="display:flex;align-items:center;gap:8px;font-size:10px;color:var(--muted)">
|
||||
<span style="color:var(--green);font-weight:700">↩ Response path:</span>
|
||||
LLM generates markdown analysis (via <code style="font-size:9.5px;background:#0a0d16;padding:1px 5px;border-radius:3px;color:#7c3aed">content</code> or <code style="font-size:9.5px;background:#0a0d16;padding:1px 5px;border-radius:3px;color:#7c3aed">reasoning_content</code> field) → FastAPI returns JSON → iframe renders markdown → operator reads actionable insight
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="slide-num">1 / 2</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════ SLIDE 2 — Components ══════════════════════ -->
|
||||
<div class="slide" id="s2">
|
||||
<div class="slide-header">
|
||||
<div>
|
||||
<div class="slide-pretitle">P5G Marvis · AI Integration</div>
|
||||
<div class="slide-title">Configuration & Design Choices</div>
|
||||
<div class="slide-subtitle">How the integration is wired, and why</div>
|
||||
</div>
|
||||
<div style="margin-left:auto;display:flex;gap:8px">
|
||||
<span class="badge blue">172.27.0.159 only</span>
|
||||
<span class="badge purple">Rule-based fallback</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-body">
|
||||
|
||||
<!-- LEFT: Design choices -->
|
||||
<div class="s2-col">
|
||||
<div class="s2-section">Design Choices</div>
|
||||
|
||||
<div class="list-item">
|
||||
<div class="list-icon">🔒</div>
|
||||
<div class="list-text">
|
||||
<div class="list-title">Fully air-gapped inference</div>
|
||||
<div class="list-desc">LLM at 172.27.0.135 stays inside the private 5G network. No cloud API keys, no data egress. Self-signed TLS with verify=False for local trust boundary.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item">
|
||||
<div class="list-icon">🧠</div>
|
||||
<div class="list-text">
|
||||
<div class="list-title">Context-enriched prompt engineering</div>
|
||||
<div class="list-desc">Every request carries live NF state and alert data. The model never sees a bare question — it always gets the full network picture, so answers are grounded in real telemetry.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item">
|
||||
<div class="list-icon">⚡</div>
|
||||
<div class="list-text">
|
||||
<div class="list-title">Reasoning model handling</div>
|
||||
<div class="list-desc">Gemma 4 returns a <code style="font-size:10px;background:#0a0d16;padding:1px 5px;border-radius:3px;color:#7c3aed">reasoning_content</code> field when <code style="font-size:10px;background:#0a0d16;padding:1px 5px;border-radius:3px;color:#7c3aed">content</code> is empty. The backend falls back gracefully so the thinking trace is surfaced rather than dropped.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item">
|
||||
<div class="list-icon">🛡️</div>
|
||||
<div class="list-text">
|
||||
<div class="list-title">Rule-based fallback</div>
|
||||
<div class="list-desc">If the LLM is unreachable or times out, the backend falls through to a deterministic rule engine that still returns a formatted, accurate network health summary.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- RIGHT: Config + key params -->
|
||||
<div class="s2-col">
|
||||
<div class="s2-section">Runtime Configuration (systemd env)</div>
|
||||
|
||||
<div class="code-block">
|
||||
<span class="c"># /etc/systemd/system/p5g-marvis.service</span>
|
||||
<span class="k">Environment</span>=<span class="v">MARVIS_AI_MODE</span>=<span class="s">openai</span>
|
||||
<span class="k">Environment</span>=<span class="v">MARVIS_OPENAI_BASE_URL</span>=<span class="s">https://172.27.0.135:8001</span>
|
||||
<span class="k">Environment</span>=<span class="v">MARVIS_OPENAI_MODEL</span>=<span class="s">gemma-4-26B-A4B-it-UD-Q4_K_S.gguf</span>
|
||||
</div>
|
||||
|
||||
<div class="s2-section" style="margin-top:8px">Key Parameters</div>
|
||||
<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 14px;">
|
||||
<div class="kv-row"><span class="kv-key">LLM endpoint</span><span class="kv-val blue">172.27.0.135:8001</span></div>
|
||||
<div class="kv-row"><span class="kv-key">API format</span><span class="kv-val">/v1/chat/completions</span></div>
|
||||
<div class="kv-row"><span class="kv-key">Auth header</span><span class="kv-val green">None (skipped if key empty)</span></div>
|
||||
<div class="kv-row"><span class="kv-key">TLS verify</span><span class="kv-val yellow">Disabled (self-signed)</span></div>
|
||||
<div class="kv-row"><span class="kv-key">max_tokens</span><span class="kv-val">1 024</span></div>
|
||||
<div class="kv-row"><span class="kv-key">Timeout</span><span class="kv-val">120 s</span></div>
|
||||
<div class="kv-row"><span class="kv-key">Hosts with LLM mode</span><span class="kv-val purple">172.27.0.159 only</span></div>
|
||||
<div class="kv-row"><span class="kv-key">192.168.86.173 mode</span><span class="kv-val green">rule (deterministic)</span></div>
|
||||
</div>
|
||||
|
||||
<div class="s2-section" style="margin-top:8px">Routing (Traefik)</div>
|
||||
<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 14px;">
|
||||
<div class="kv-row"><span class="kv-key">NCM sidebar inject</span><span class="kv-val blue">patch-ncm.py → JS bundle</span></div>
|
||||
<div class="kv-row"><span class="kv-key">Marvis iframe path</span><span class="kv-val">/core/marvis/ → :8100</span></div>
|
||||
<div class="kv-row"><span class="kv-key">AI sub-page</span><span class="kv-val">/core/marvis/ai</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="slide-num">2 / 2</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="nav">
|
||||
<button id="prev" onclick="go(-1)" disabled>← Prev</button>
|
||||
<span class="slide-indicator" id="indicator">1 / 2</span>
|
||||
<button id="next" onclick="go(1)">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
let cur = 0;
|
||||
function go(d) {
|
||||
slides[cur].classList.remove('active');
|
||||
cur = Math.max(0, Math.min(slides.length - 1, cur + d));
|
||||
slides[cur].classList.add('active');
|
||||
document.getElementById('indicator').textContent = `${cur+1} / ${slides.length}`;
|
||||
document.getElementById('prev').disabled = cur === 0;
|
||||
document.getElementById('next').disabled = cur === slides.length - 1;
|
||||
}
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'ArrowRight') go(1);
|
||||
if (e.key === 'ArrowLeft') go(-1);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user