From ecc1423a4fc7d9067d43d753b135678d9d255240 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:46:40 +0200 Subject: [PATCH] redesign ArchitectureSlide: COMPLAI/CERTifAI/Scanner + EU-only infra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New architecture: CERTifAI (GenAI portal), COMPLAI (compliance), Scanner (code security) — MCP bidirectional connection between COMPLAI and Scanner - LiteLLM hub: token budget, PII guardrails, anon web search, no US providers - Inference layer: Qwen3/DeepSeek (local LLM), bge-m3 embeddings, AI Tools - Fix hover snapping: position wrapper (div) separate from scale (motion.button) - Always-on data traffic packets on all connections - Bidirectional MCP packets + MCP badge between COMPLAI and Scanner - Live token ticker counter on active inference nodes - BSI/EU sovereign badges on tier labels - Spinning dashed ring on active LiteLLM hub - Secondary infra chips in GenAI tier (PostgreSQL, Gitea, Orca, etc.) Co-Authored-By: Claude Sonnet 4.6 --- .../components/slides/ArchitectureSlide.tsx | 636 ++++++++++-------- 1 file changed, 354 insertions(+), 282 deletions(-) diff --git a/pitch-deck/components/slides/ArchitectureSlide.tsx b/pitch-deck/components/slides/ArchitectureSlide.tsx index f4beb86..a9c182d 100644 --- a/pitch-deck/components/slides/ArchitectureSlide.tsx +++ b/pitch-deck/components/slides/ArchitectureSlide.tsx @@ -1,21 +1,23 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Language } from '@/lib/types' import { t } from '@/lib/i18n' import GradientText from '../ui/GradientText' import FadeInView from '../ui/FadeInView' import { - Shield, Brain, ScanLine, Zap, Cpu, Globe, Cloud, - X, Users, Lock, Server, Network, ChevronRight, Layers, + Brain, Shield, ScanLine, Zap, Cpu, + Layers, Wrench, X, Users, Lock, + Server, Network, ChevronRight, BadgeCheck, } from 'lucide-react' interface ArchitectureSlideProps { lang: Language } -type NodeId = 'breakpilot' | 'certifai' | 'compliance-scanner' | 'litellm' | 'ollama' | 'claude' | 'openai' +type NodeId = 'certifai' | 'complai' | 'scanner' | 'litellm' | 'llm' | 'embeddings' | 'tools' +type ConnType = 'api' | 'mcp' | 'embed' | 'tool' interface NodeDef { id: NodeId @@ -35,265 +37,324 @@ interface NodeDef { badge?: string } -const CONNECTIONS: [NodeId, NodeId][] = [ - ['breakpilot', 'litellm'], - ['certifai', 'litellm'], - ['compliance-scanner', 'litellm'], - ['litellm', 'ollama'], - ['litellm', 'claude'], - ['litellm', 'openai'], -] - -// Quadratic bezier paths in 0-100 viewBox space -const ROUTES: Record = { - 'breakpilot-litellm': 'M 15 22 Q 26 43 50 50', - 'certifai-litellm': 'M 50 18 Q 50 34 50 50', - 'compliance-scanner-litellm': 'M 85 22 Q 74 43 50 50', - 'litellm-ollama': 'M 50 50 Q 39 64 15 80', - 'litellm-claude': 'M 50 50 Q 50 65 50 80', - 'litellm-openai': 'M 50 50 Q 61 64 85 80', +interface ConnDef { + from: NodeId + to: NodeId + type: ConnType + d: string + revD?: string // reverse path for bidirectional MCP } +const CONNS: ConnDef[] = [ + { from: 'certifai', to: 'litellm', type: 'api', d: 'M 18 22 Q 28 43 50 52' }, + { from: 'complai', to: 'litellm', type: 'api', d: 'M 50 22 L 50 52' }, + { from: 'scanner', to: 'litellm', type: 'api', d: 'M 82 22 Q 72 43 50 52' }, + { from: 'complai', to: 'scanner', type: 'mcp', d: 'M 50 22 Q 66 9 82 22', revD: 'M 82 22 Q 66 9 50 22' }, + { from: 'litellm', to: 'llm', type: 'api', d: 'M 50 52 Q 37 67 18 82' }, + { from: 'litellm', to: 'embeddings', type: 'embed', d: 'M 50 52 L 50 82' }, + { from: 'litellm', to: 'tools', type: 'tool', d: 'M 50 52 Q 63 67 82 82' }, +] + function getNodes(de: boolean): NodeDef[] { return [ { - id: 'breakpilot', + id: 'certifai', + icon: Brain, + title: 'CERTifAI', + subtitle: de ? 'GenAI Mandantenportal' : 'GenAI Tenant Portal', + color: '#c084fc', twColor: 'text-purple-400', + twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400', + cx: 18, cy: 22, tier: 'product', + badge: 'Rust · Dioxus', + tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG', 'LangGraph'], + services: [ + { name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kostentracking' : 'Model mgmt & cost tracking' }, + { name: 'LibreChat + SSO', desc: de ? 'Mandanten-Chat mit Keycloak' : 'Tenant chat with Keycloak' }, + { name: 'LangGraph Agents', desc: de ? 'Agent-Orchestrierung' : 'Agent orchestration' }, + { name: 'MCP Hub', desc: de ? 'Tool-Integration für KI-Clients' : 'Tool integration for AI clients' }, + ], + }, + { + id: 'complai', icon: Shield, - title: 'BreakPilot', - subtitle: de ? 'Compliance & Bildung' : 'Compliance & Education', + title: 'COMPLAI', + subtitle: de ? 'Compliance & Audit' : 'Compliance & Audit', color: '#818cf8', twColor: 'text-indigo-400', twBorder: 'border-indigo-500/50', twBg: 'bg-indigo-500/10', twDot: 'bg-indigo-400', - cx: 15, cy: 22, tier: 'product', - badge: de ? 'Kernprodukt' : 'Core Product', + cx: 50, cy: 22, tier: 'product', + badge: 'Next.js · FastAPI', tech: ['Next.js 15', 'FastAPI', 'Go/Gin', 'PostgreSQL', 'Qdrant', 'Valkey'], services: [ { name: de ? 'DSGVO / AI Act / NIS2' : 'GDPR / AI Act / NIS2', desc: de ? '70k+ auditierbare Controls' : '70k+ auditable controls' }, { name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen, semantische Suche' : '75+ legal sources, semantic search' }, - { name: de ? 'Lehrer Plattform' : 'Lehrer Platform', desc: de ? 'KI-Unterrichtsassistent' : 'AI teaching assistant' }, - { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via Claude' : 'Legal text analysis via Claude' }, + { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse via LLM' : 'Legal text analysis via LLM' }, + { name: 'MCP Client', desc: de ? 'Echtzeit-Findings vom Scanner' : 'Real-time findings from Scanner' }, ], }, { - id: 'certifai', - icon: Brain, - title: 'CERTifAI', - subtitle: de ? 'GenAI Infrastruktur' : 'GenAI Infrastructure', - color: '#c084fc', twColor: 'text-purple-400', - twBorder: 'border-purple-500/50', twBg: 'bg-purple-500/10', twDot: 'bg-purple-400', - cx: 50, cy: 18, tier: 'product', - badge: de ? 'DSGVO-konform' : 'GDPR-Compliant', - tech: ['Rust', 'Dioxus', 'MongoDB', 'Keycloak', 'SearXNG'], - services: [ - { name: 'LiteLLM Dashboard', desc: de ? 'Modellverwaltung & Kosten' : 'Model mgmt & spend tracking' }, - { name: 'LibreChat + SSO', desc: de ? 'Mandanten-Chat' : 'Tenant chat with Keycloak' }, - { name: 'LangGraph Agents', desc: de ? 'Agent-Orchestrierung' : 'Agent orchestration' }, - { name: 'MCP Hub', desc: de ? 'Tool-Integration für KI' : 'Tool integration for AI clients' }, - ], - }, - { - id: 'compliance-scanner', + id: 'scanner', icon: ScanLine, title: 'Compliance Scanner', - subtitle: de ? 'Autonome Sicherheit' : 'Autonomous Security', + subtitle: de ? 'Code-Sicherheit' : 'Code Security', color: '#34d399', twColor: 'text-emerald-400', twBorder: 'border-emerald-500/50', twBg: 'bg-emerald-500/10', twDot: 'bg-emerald-400', - cx: 85, cy: 22, tier: 'product', - badge: 'Rust + AI', + cx: 82, cy: 22, tier: 'product', + badge: 'Rust · Axum', tech: ['Rust', 'Axum', 'MongoDB', 'Semgrep', 'Gitleaks', 'Syft'], services: [ { name: 'SAST / SBOM / CVE', desc: de ? 'Vollautomatische Pipeline' : 'Fully automated pipeline' }, { name: de ? 'KI-Triage' : 'AI Triage', desc: de ? 'LLM filtert False Positives' : 'LLM filters false positives' }, { name: de ? 'KI-Pentest' : 'AI Pentest', desc: de ? 'Autonome Angriffsketten' : 'Autonomous attack chains' }, - { name: 'MCP Server', desc: de ? 'Live-Findings für AI-Tools' : 'Live findings for AI tools' }, + { name: 'MCP Server', desc: de ? 'Live-Findings für COMPLAI' : 'Live findings for COMPLAI' }, ], }, { id: 'litellm', icon: Zap, title: 'LiteLLM Proxy', - subtitle: de ? 'Zentrale KI-Infrastruktur' : 'Central AI Proxy', + subtitle: de ? 'KI-Gateway & Guardrails' : 'AI Gateway & Guardrails', color: '#fbbf24', twColor: 'text-amber-400', twBorder: 'border-amber-500/60', twBg: 'bg-amber-500/10', twDot: 'bg-amber-400', - cx: 50, cy: 50, tier: 'proxy', + cx: 50, cy: 52, tier: 'proxy', badge: 'Hub', - tech: ['OpenAI-compatible API', 'Bearer Auth', 'Rate Limiting', 'Spend Tracking'], + tech: ['OpenAI-kompatible API', 'Bearer Auth', 'Rate Limiting', 'PII-Filter', 'Spend Tracking'], services: [ - { name: de ? 'Multi-Provider' : 'Multi-Provider', desc: 'Ollama · Claude · OpenAI · HuggingFace' }, + { name: de ? 'Token-Budget' : 'Token Budget', desc: de ? 'Pro-Mandant Kontingente & Abrechnung' : 'Per-tenant quotas & billing' }, + { name: 'PII Guardrails', desc: de ? 'Datenschutz-Filter für alle Anfragen' : 'Privacy filter on all requests' }, + { name: de ? 'Web-Suche (anonym)' : 'Web Search (anon)', desc: de ? 'SearXNG-Proxy, kein US-Anbieter' : 'SearXNG proxy, no US providers' }, { name: de ? 'Namespace-Isolierung' : 'Namespace Isolation', desc: de ? 'Mandantentrennung per API-Key' : 'Tenant isolation per API key' }, - { name: de ? 'Kosten-Tracking' : 'Cost Tracking', desc: de ? 'Token-Verbrauch & USD-Kosten' : 'Token usage & USD costs' }, - { name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback routing' }, + { name: de ? 'Failover-Routing' : 'Failover Routing', desc: de ? 'Automatisches Fallback' : 'Automatic fallback between models' }, ], }, { - id: 'ollama', + id: 'llm', icon: Cpu, - title: 'Ollama', - subtitle: de ? 'Lokale Inferenz' : 'Local Inference', + title: de ? 'LLM Inferenz' : 'LLM Inference', + subtitle: de ? 'Lokale Sprachmodelle' : 'Local Language Models', color: '#60a5fa', twColor: 'text-blue-400', twBorder: 'border-blue-500/50', twBg: 'bg-blue-500/10', twDot: 'bg-blue-400', - cx: 15, cy: 80, tier: 'inference', - badge: de ? 'On-Premise' : 'On-Premise', - tech: ['Qwen3-Coder-30B', 'Qwen3-32b', 'bge-m3', 'GPU-optimized'], + cx: 18, cy: 82, tier: 'inference', + badge: de ? 'On-Premise · BSI' : 'On-Premise · BSI', + tech: ['Qwen3-32B', 'Qwen3-Coder-30B', 'DeepSeek-R1-8B', 'Ollama'], services: [ { name: de ? 'Vollständig lokal' : 'Fully local', desc: de ? 'Daten verlassen nie den Server' : 'Data never leaves the server' }, { name: de ? 'Air-Gap fähig' : 'Air-Gap Capable', desc: de ? 'Kein Internet erforderlich' : 'No internet required' }, + { name: de ? 'GPU-optimiert' : 'GPU-optimized', desc: de ? 'Dedizierte Inferenz-Hardware' : 'Dedicated inference hardware' }, ], }, { - id: 'claude', - icon: Globe, - title: 'Anthropic Claude', - subtitle: de ? 'Externe Inferenz' : 'External Inference', - color: '#fb7185', twColor: 'text-rose-400', - twBorder: 'border-rose-500/50', twBg: 'bg-rose-500/10', twDot: 'bg-rose-400', - cx: 50, cy: 80, tier: 'inference', - badge: de ? 'Optional' : 'Optional', - tech: ['claude-sonnet-4-6', 'claude-haiku-4-5', 'Direct API'], + id: 'embeddings', + icon: Layers, + title: 'Embeddings', + subtitle: de ? 'Semantische Suche' : 'Semantic Search', + color: '#a78bfa', twColor: 'text-violet-400', + twBorder: 'border-violet-500/50', twBg: 'bg-violet-500/10', twDot: 'bg-violet-400', + cx: 50, cy: 82, tier: 'inference', + badge: de ? 'EU-Souverän' : 'EU Sovereign', + tech: ['bge-m3', 'Qdrant Vector DB', 'Sentence-Transformers'], services: [ - { name: 'Control Pipeline', desc: de ? 'Gesetzestextanalyse & Control-Gen.' : 'Legal text analysis & control gen.' }, - { name: 'Pitch Deck Chatbot', desc: de ? 'Investor-FAQ' : 'Investor FAQ' }, + { name: 'RAG Pipeline', desc: de ? '75+ Rechtsquellen indexiert' : '75+ legal sources indexed' }, + { name: de ? 'Semantische Suche' : 'Semantic Search', desc: de ? 'Multi-linguale Einbettungen' : 'Multi-lingual embeddings' }, + { name: de ? 'Lokal' : 'Fully local', desc: de ? 'Keine externen APIs' : 'No external APIs' }, ], }, { - id: 'openai', - icon: Cloud, - title: de ? 'OpenAI / Weitere' : 'OpenAI / Others', - subtitle: de ? 'Cloud-Modelle' : 'Cloud Models', + id: 'tools', + icon: Wrench, + title: de ? 'KI-Tools' : 'AI Tools', + subtitle: de ? 'Web-Suche & MCP' : 'Web Search & MCP', color: '#2dd4bf', twColor: 'text-teal-400', twBorder: 'border-teal-500/50', twBg: 'bg-teal-500/10', twDot: 'bg-teal-400', - cx: 85, cy: 80, tier: 'inference', - badge: de ? 'Optional' : 'Optional', - tech: ['gpt-oss-120b', 'HuggingFace', 'text-embedding-3-small'], + cx: 82, cy: 82, tier: 'inference', + badge: de ? 'EU-Souverän' : 'EU Sovereign', + tech: ['SearXNG', 'MCP Protocol', 'Semgrep API', 'Gitleaks API'], services: [ - { name: de ? 'Erweiterbar' : 'Extensible', desc: de ? 'Jeder OpenAI-kompatibler Anbieter' : 'Any OpenAI-compatible provider' }, + { name: 'SearXNG', desc: de ? 'Anonymisierte EU-Websuche' : 'Anonymized EU web search' }, + { name: 'MCP Tools', desc: de ? 'Auditdokumente & Code-Findings' : 'Audit docs & code findings' }, + { name: de ? 'Kein US-Anbieter' : 'No US providers', desc: de ? '100% DSGVO-konform' : '100% GDPR-compliant' }, ], }, ] } -function SeaRoute({ d, color, active }: { d: string; color: string; active: boolean }) { +// ── Token counter (inference tier ambient animation) ───────────────────────── +function TokenTicker({ color }: { color: string }) { + const [n, setN] = useState(() => 12480 + Math.floor(Math.random() * 8000)) + useEffect(() => { + const id = setInterval(() => setN(v => v + Math.floor(Math.random() * 180 + 40)), 220) + return () => clearInterval(id) + }, []) + return ( +
+ + + {n.toLocaleString('de-DE')} tok/s + +
+ ) +} + +// ── Data-flow path (always-on packets, fast when active) ───────────────────── +function DataFlow({ + d, revD, color, active, type, +}: { + d: string; revD?: string; color: string; active: boolean; type: ConnType +}) { + const isMcp = type === 'mcp' + const speed = active ? 1.2 : 5 + const op = active ? 0.85 : 0.2 + const w = active ? 2.5 : 1.2 + const dash = active ? 3 : 2 + return ( + {/* Base line */} - {active && ( - ( + - )} + ))} + + {/* Reverse packets for MCP (bidirectional) */} + {isMcp && revD && [0, -42].map((off, i) => ( + + ))} ) } -function MapMarker({ node, active, onClick }: { node: NodeDef; active: boolean; onClick: () => void }) { +// ── Node card ────────────────────────────────────────────────────────────────── +// Position wrapper (div) is never scaled — only the inner motion.button scales. +// This prevents the "snap" that happened when translate(-50%,-50%) and scale +// were on the same element. +function NodeCard({ + node, active, onClick, de, +}: { + node: NodeDef; active: boolean; onClick: () => void; de: boolean +}) { const Icon = node.icon const isHub = node.id === 'litellm' - const iconSize = isHub ? 'w-[52px] h-[52px]' : 'w-[40px] h-[40px]' return ( - -
- {/* Ripple pulse when active */} - {active && ( - - )} - - {/* Icon circle */} +
- - {/* Spinning dashed ring on active hub */} + + {/* Icon + title */} +
+
+ +
+ + {node.title} + + {active && ( +
+ )} +
+ + {/* Subtitle */} +

+ {node.subtitle} +

+ + {/* Badge */} + {node.badge && ( +
+ + {node.badge} + +
+ )} + + {/* Token ticker on inference nodes */} + {active && node.tier === 'inference' && ( +
+ +
+ )} + + {/* Hub: spinning indicator ring */} {isHub && active && ( )}
-
- - {/* Label */} -
-

- {node.title} -

- {(active || isHub) && ( -

- {node.subtitle} -

- )} - {node.badge && ( - - {node.badge} - - )} -
-
+ +
) } +// ── Main slide ───────────────────────────────────────────────────────────────── export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { - const i = t(lang) - const de = lang === 'de' - const nodes = getNodes(de) + const i = t(lang) + const de = lang === 'de' + const nodes = getNodes(de) const [activeId, setActiveId] = useState(null) const active = nodes.find(n => n.id === activeId) ?? null @@ -306,6 +367,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { ? ['Mandant A', 'Mandant B', 'Mandant C', 'Mandant N…'] : ['Namespace A', 'Namespace B', 'Namespace C', 'Namespace N…'] + const tiers = [ + { y: '22%', label: de ? 'Anwendungsschicht' : 'Application Layer', clr: '#818cf8', badge: null }, + { y: '52%', label: de ? 'GenAI-Infrastruktur' : 'GenAI Infrastructure', clr: '#fbbf24', badge: de ? 'BSI-Rechenzentrum' : 'BSI Data Center' }, + { y: '82%', label: de ? 'Inferenzschicht' : 'Inference Layer', clr: '#60a5fa', badge: de ? 'BSI · EU-Souverän' : 'BSI · EU Sovereign' }, + ] + return (
{/* Header */} @@ -329,151 +396,149 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {de ? 'Kundenmandanten' : 'Customer Namespaces'} {tenants.map(tn => ( - + {tn} ))}
- {/* ── THE MAP ──────────────────────────────────────────────────── */} -
+ {/* ── MAP ──────────────────────────────────────────────────────────── */} +
- {/* Ocean background (clipped to rounded rect) */} -
- {/* Fine dot grid */} -
- {/* Zone separator lines */} - {(['35%', '65%'] as const).map(y => ( -
- ))}
- {/* Zone labels */} - {[ - { y: '22%', label: de ? 'Produkte' : 'Products', clr: '#818cf8' }, - { y: '50%', label: de ? 'KI-Proxy' : 'AI Proxy', clr: '#fbbf24' }, - { y: '80%', label: de ? 'Inferenz' : 'Inference', clr: '#60a5fa' }, - ].map(({ y, label, clr }) => ( -
-
- - {label} - + {/* Tier separator lines */} + {['37%', '66%'].map(y => ( +
+ ))} + + {/* Tier labels (left) + BSI badges (right) */} + {tiers.map(({ y, label, clr, badge }) => ( +
+
+
+ + {label} + +
+ {badge && ( +
+ + + {badge} + +
+ )}
))} - {/* SVG: island territories + sea routes */} - - {/* Wide zone territory fills */} - - - + MCP ↔ +
- {/* Per-node island blobs (outer contour + fill) */} - {nodes.map(node => { - const isHub = node.id === 'litellm' - const isActive = activeId === node.id - const rx = isHub ? 10 : 7 - const ry = isHub ? 6 : 3.8 - const rxOuter = isHub ? 14.5 : 10 - const ryOuter = isHub ? 8.5 : 5.5 + {/* Infra tech chips (secondary, very subtle, in the infra tier band) */} +
+ {['PostgreSQL', 'MongoDB', 'Qdrant', 'Valkey', 'Nginx', 'Vault', 'Gitea', 'Orca', 'Woodpecker'].map(chip => ( + + {chip} + + ))} +
+ + {/* SVG: island territories + data-flow paths */} + + + {/* Tier zone fills */} + + + + + {/* Island blobs per node */} + {nodes.map(n => { + const isHub = n.id === 'litellm' + const isActive = activeId === n.id return ( - - {/* Outer contour ring */} - + - {/* Inner island fill */} - ) })} - {/* Sea routes */} - {CONNECTIONS.map(([fromId, toId]) => { - const key = `${fromId}-${toId}` - const from = nodes.find(n => n.id === fromId)! - const to = nodes.find(n => n.id === toId)! - const isActive = activeId === fromId || activeId === toId - const color = isActive - ? (activeId === fromId ? from.color : to.color) + {/* Connection flows */} + {CONNS.map(c => { + const fromNode = nodes.find(n => n.id === c.from)! + const toNode = nodes.find(n => n.id === c.to)! + const isActive = activeId === c.from || activeId === c.to + const color = isActive + ? (activeId === c.from ? fromNode.color : toNode.color) : '#ffffff' return ( - + ) })} - {/* Node markers */} + {/* Node cards */} {nodes.map(node => ( - toggle(node.id)} + de={de} /> ))} {!activeId && ( -
+
{de ? 'Komponente anklicken' : 'Click any node'}
)}
- {/* ── DETAIL PANEL ─────────────────────────────────────────────── */} + {/* ── DETAIL PANEL ──────────────────────────────────────────────────── */} {active && ( + {/* Header */}
-
+

{active.title}

{active.badge && ( @@ -503,11 +569,17 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {active.badge} )} + + {active.tier === 'product' ? (de ? 'Anwendungsschicht' : 'Application Layer') : + active.tier === 'proxy' ? (de ? 'GenAI-Infrastruktur' : 'GenAI Infra') : + (de ? 'Inferenzschicht' : 'Inference Layer')} +
-

{active.subtitle}

+

{active.subtitle}

+ {/* Body */}

@@ -539,23 +611,26 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) {

+ {/* Connects-to */}
{de ? 'Verbunden mit:' : 'Connects to:'} - {CONNECTIONS - .filter(([a, b]) => a === active.id || b === active.id) - .map(([a, b]) => { - const peerId = a === active.id ? b : a + {CONNS + .filter(c => c.from === active.id || c.to === active.id) + .map(c => { + const peerId = c.from === active.id ? c.to : c.from const peer = nodes.find(n => n.id === peerId)! + const label = c.type === 'mcp' ? 'MCP' : c.type === 'embed' ? 'Embed' : c.type === 'tool' ? 'Tool' : 'API' return ( ) })} @@ -566,15 +641,12 @@ export default function ArchitectureSlide({ lang }: ArchitectureSlideProps) { {/* Bottom legend */} {!active && ( - + {[ - { icon: Lock, text: de ? 'DSGVO-konform · BSI-zertifizierbar' : 'GDPR-compliant · BSI-certifiable' }, - { icon: Server, text: de ? 'On-Premise oder EU-Cloud' : 'On-Premise or EU Cloud' }, - { icon: Layers, text: de ? 'Air-Gap fähig' : 'Air-Gap Capable' }, + { icon: Lock, text: de ? 'Kein US-Anbieter · 100% DSGVO' : 'No US providers · 100% GDPR' }, + { icon: Server, text: de ? 'BSI-zertifiziertes Rechenzentrum' : 'BSI-certified data center' }, + { icon: BadgeCheck, text: de ? 'EU-souveräne Inferenz' : 'EU-sovereign inference' }, ].map(({ icon: Icon, text }) => (