fix: simplify agent graph to show the 4 tiles and fix compilation errors

This commit is contained in:
Hitonabi
2026-06-26 08:12:30 +02:00
parent 18f361d485
commit 4acea97f03
7 changed files with 453 additions and 717 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title> <title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-izDdu6A_.js"></script> <script type="module" crossorigin src="/assets/index-CiscfPzK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-mQ1DUIFB.css"> <link rel="stylesheet" crossorigin href="/assets/index-ChiXEUW1.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+77 -341
View File
@@ -1,6 +1,6 @@
import { useEffect, useState, useRef, useCallback } from "react" import { useEffect, useState, useRef, useCallback } from "react"
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, X, Check, Copy } from "lucide-react" import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield } from "lucide-react"
import { api, type AgentStatus, type ModelInfo, type RoutingResp, type ConnectResp } from "@/lib/api" import { api, type AgentStatus } from "@/lib/api"
import { cn, resolveExternalUrl } from "@/lib/utils" import { cn, resolveExternalUrl } from "@/lib/utils"
function Tile({ label, ok, detail, icon: Icon }: { label: string; ok: boolean; detail?: string; icon: any }) { function Tile({ label, ok, detail, icon: Icon }: { label: string; ok: boolean; detail?: string; icon: any }) {
@@ -30,22 +30,11 @@ function Tile({ label, ok, detail, icon: Icon }: { label: string; ok: boolean; d
) )
} }
const ROLES = ["fast", "heavy", "coder", "reasoning", "agent", "vision", "scout"]
export function AgentView() { export function AgentView() {
const [s, setS] = useState<AgentStatus | null>(null) const [s, setS] = useState<AgentStatus | null>(null)
const [error, setError] = useState("") const [error, setError] = useState("")
// Graph states // Graph UI state
const [models, setModels] = useState<ModelInfo[]>([])
const [running, setRunning] = useState<string[]>([])
const [routing, setRouting] = useState<RoutingResp | null>(null)
const [connectData, setConnectData] = useState<ConnectResp | null>(null)
// Graph UI states
const [activeClient, setActiveClient] = useState<"roocode" | "cursor" | "opencode" | "zed" | "continue" | null>(null)
const [activeRoleDrop, setActiveRoleDrop] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
const [hoveredNode, setHoveredNode] = useState<string | null>(null) const [hoveredNode, setHoveredNode] = useState<string | null>(null)
// Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling // Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling
@@ -71,47 +60,14 @@ export function AgentView() {
const w = dimensions.width const w = dimensions.width
const h = dimensions.height const h = dimensions.height
// Helper to generate curve from client to gateway // Helper to generate curve between any two points
const getClientPath = (yPercent: number) => { const getCurvePath = (startX: number, startY: number, endX: number, endY: number) => {
const startX = w * 0.1 const midX = (startX + endX) / 2
const startY = h * yPercent return `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`
const endX = w * 0.5
const endY = h * 0.5
const cp1X = w * 0.3
const cp1Y = startY
const cp2X = w * 0.3
const cp2Y = endY
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
}
// Helper to generate curve from gateway to role
const getRolePath = (yPercent: number) => {
const startX = w * 0.5
const startY = h * 0.5
const endX = w * 0.9
const endY = h * yPercent
const cp1X = w * 0.7
const cp1Y = startY
const cp2X = w * 0.7
const cp2Y = endY
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
} }
function loadData() { function loadData() {
api<AgentStatus>("/api/agent/status").then(setS).catch((e) => setError(String(e))) api<AgentStatus>("/api/agent/status").then(setS).catch((e) => setError(String(e)))
Promise.all([
api<{ models: ModelInfo[]; running?: string[] }>("/api/models"),
api<RoutingResp>("/api/routing"),
api<ConnectResp>("/api/connect")
])
.then(([mResp, rResp, cResp]) => {
setModels(mResp.models || [])
setRunning(mResp.running || [])
setRouting(rResp)
setConnectData(cResp)
})
.catch((e) => console.error("Error loading graph data in AgentView", e))
} }
useEffect(() => { useEffect(() => {
@@ -120,32 +76,6 @@ export function AgentView() {
return () => clearInterval(t) return () => clearInterval(t)
}, []) }, [])
const getModelForRole = (role: string) => models.find((m) => m.role === role)
const isRoleRunning = (role: string) => {
const m = getModelForRole(role)
return m ? running.includes(m.name) : false
}
async function handleRoleChange(role: string, modelName: string) {
setActiveRoleDrop(null)
try {
await api(`/api/models/${encodeURIComponent(modelName)}/role`, {
method: "POST",
body: JSON.stringify({ role: role || null }),
})
loadData()
} catch (e) {
alert(`Fehler beim Zuweisen der Rolle: ${e}`)
}
}
async function copySnippet(snippet?: string) {
if (!snippet) return
await navigator.clipboard.writeText(snippet)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Styles inside AgentView for dash flow animations */} {/* Styles inside AgentView for dash flow animations */}
@@ -155,7 +85,7 @@ export function AgentView() {
stroke-dashoffset: -20; stroke-dashoffset: -20;
} }
} }
.animate-flow-cyan { .svg-flow-path {
stroke-dasharray: 4 6; stroke-dasharray: 4 6;
animation: flow-dash 1s linear infinite; animation: flow-dash 1s linear infinite;
} }
@@ -229,9 +159,9 @@ export function AgentView() {
{/* Interactive Gateway Graph */} {/* Interactive Gateway Graph */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 shadow-lg shadow-black/10 space-y-4"> <div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-6 shadow-lg shadow-black/10 space-y-4">
<div> <div>
<span className="text-xs font-bold uppercase tracking-wider text-foreground">Interactive Gateway Graph (Duplikat)</span> <span className="text-xs font-bold uppercase tracking-wider text-foreground">Interactive Agent Flow Graph</span>
<p className="text-[10px] text-muted-foreground mt-0.5 leading-relaxed"> <p className="text-[10px] text-muted-foreground mt-0.5 leading-relaxed">
Visualisiert die Kopplung der Client-Editoren mit dem OpenAI-Gateway und den aktiven Modell-Rollen. Visualisiert das Zusammenspiel und die Verbindungspfade zwischen den Hermes-Systemkomponenten.
</p> </p>
</div> </div>
@@ -249,309 +179,115 @@ export function AgentView() {
</linearGradient> </linearGradient>
</defs> </defs>
{/* Bezier Curves: Clients to Gateway */} {/* Connection: WebUI to Gateway */}
{/* Roo Code (y=10) */} <path d={getCurvePath(w * 0.15, h * 0.5, w * 0.5, h * 0.5)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> {(hoveredNode === "webui" || s.webui_reachable) && (
{(hoveredNode === "roocode" || activeClient === "roocode") && ( <path d={getCurvePath(w * 0.15, h * 0.5, w * 0.5, h * 0.5)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
<path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)} )}
{/* Cursor (y=30) */} {/* Connection: Gateway to Gehirn */}
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getCurvePath(w * 0.5, h * 0.5, w * 0.85, h * 0.3)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "cursor" || activeClient === "cursor") && ( {(hoveredNode === "gateway" || hoveredNode === "brain" || s.gateway_reachable) && (
<path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getCurvePath(w * 0.5, h * 0.5, w * 0.85, h * 0.3)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* OpenCode (y=50) */} {/* Connection: Gateway to Verdrahtung */}
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getCurvePath(w * 0.5, h * 0.5, w * 0.85, h * 0.7)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "opencode" || activeClient === "opencode") && ( {(hoveredNode === "gateway" || hoveredNode === "wiring" || s.gateway_reachable) && (
<path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getCurvePath(w * 0.5, h * 0.5, w * 0.85, h * 0.7)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)}
{/* Zed (y=70) */}
<path d={getClientPath(0.70)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "zed" || activeClient === "zed") && (
<path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* Continue (y=90) */}
<path d={getClientPath(0.90)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "continue" || activeClient === "continue") && (
<path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* Bezier Curves: Gateway to Roles */}
{/* fast (y=12) */}
<path d={getRolePath(0.12)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("fast") && (
<path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* heavy (y=31) */}
<path d={getRolePath(0.31)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("heavy") && (
<path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* coder (y=50) */}
<path d={getRolePath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("coder") && (
<path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* vision (y=69) */}
<path d={getRolePath(0.69)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("vision") && (
<path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)}
{/* scout (y=88) */}
<path d={getRolePath(0.88)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("scout") && (
<path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
)} )}
</svg> </svg>
{/* Client Nodes */} {/* COLUMN 1: Interface */}
<div <div
className="absolute cursor-pointer select-none z-10 w-28 h-8 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20" className="absolute cursor-pointer select-none z-10 w-36 h-9 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2.5 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20"
style={{ left: "10%", top: "10%" }} style={{ left: "15%", top: "50%" }}
onMouseEnter={() => setHoveredNode("roocode")} onMouseEnter={() => setHoveredNode("webui")}
onMouseLeave={() => setHoveredNode(null)} onMouseLeave={() => setHoveredNode(null)}
onClick={() => setActiveClient(c => c === "roocode" ? null : "roocode")} onClick={() => s.webui_reachable && window.open(resolveExternalUrl(s.webui_url), "_blank")}
title={s.webui_reachable ? "Klicken um Chat-WebUI zu öffnen" : "WebUI Offline"}
> >
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" /> <Activity className={cn("h-3.5 w-3.5", s.webui_reachable ? "text-emerald-400" : "text-amber-500")} />
<span>Roo Code</span> <span>Agent WebUI</span>
<span className={cn("w-1.5 h-1.5 rounded-full animate-pulse ml-auto", s.webui_reachable ? "bg-emerald-500" : "bg-amber-500")} />
</div> </div>
{/* COLUMN 2: Gateway */}
<div <div
className="absolute cursor-pointer select-none z-10 w-28 h-8 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20" className="absolute select-none z-10 w-40 py-2.5 px-3 -translate-y-1/2 -translate-x-1/2 rounded-2xl border border-primary/50 bg-card/90 backdrop-blur-md flex flex-col items-center justify-center text-center shadow-lg shadow-primary/5 cursor-pointer"
style={{ left: "10%", top: "30%" }}
onMouseEnter={() => setHoveredNode("cursor")}
onMouseLeave={() => setHoveredNode(null)}
onClick={() => setActiveClient(c => c === "cursor" ? null : "cursor")}
>
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
<span>Cursor IDE</span>
</div>
<div
className="absolute cursor-pointer select-none z-10 w-28 h-8 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20"
style={{ left: "10%", top: "50%" }}
onMouseEnter={() => setHoveredNode("opencode")}
onMouseLeave={() => setHoveredNode(null)}
onClick={() => setActiveClient(c => c === "opencode" ? null : "opencode")}
>
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
<span>OpenCode</span>
</div>
<div
className="absolute cursor-pointer select-none z-10 w-28 h-8 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20"
style={{ left: "10%", top: "70%" }}
onMouseEnter={() => setHoveredNode("zed")}
onMouseLeave={() => setHoveredNode(null)}
onClick={() => setActiveClient(c => c === "zed" ? null : "zed")}
>
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
<span>Zed</span>
</div>
<div
className="absolute cursor-pointer select-none z-10 w-28 h-8 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20"
style={{ left: "10%", top: "90%" }}
onMouseEnter={() => setHoveredNode("continue")}
onMouseLeave={() => setHoveredNode(null)}
onClick={() => setActiveClient(c => c === "continue" ? null : "continue")}
>
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
<span>Continue</span>
</div>
{/* Gateway Node */}
<div
className="absolute select-none z-10 w-36 py-3 px-4 -translate-y-1/2 -translate-x-1/2 rounded-2xl border border-primary/50 bg-card/90 backdrop-blur-md flex flex-col items-center justify-center text-center shadow-lg shadow-primary/5"
style={{ left: "50%", top: "50%" }} style={{ left: "50%", top: "50%" }}
onMouseEnter={() => setHoveredNode("gateway")}
onMouseLeave={() => setHoveredNode(null)}
> >
<div className="text-[10px] uppercase font-bold text-primary font-space tracking-wide">Gateway Auto</div> <div className="flex items-center gap-1">
<div className="text-[10px] font-mono text-muted-foreground mt-0.5"> <Bot className="h-3.5 w-3.5 text-primary" />
Schwelle: &gt; {routing?.heavy_threshold_chars ? (routing.heavy_threshold_chars / 1000) : "4"}k Zeichen <span className="text-[10px] uppercase font-bold text-primary font-space tracking-wide">Agent Gateway</span>
</div> </div>
<div className="mt-1 px-1.5 py-0.5 rounded bg-primary/10 border border-primary/20 text-[9px] font-semibold text-primary uppercase font-mono"> <div className="text-[9px] font-mono text-muted-foreground mt-0.5">Port :8642</div>
Auto-Swap <div className={cn("mt-1 px-1.5 py-0.5 rounded text-[8px] font-semibold uppercase font-mono border",
s.gateway_reachable
? "bg-emerald-500/10 border-emerald-500/20 text-emerald-400"
: "bg-red-500/10 border-red-500/20 text-red-400"
)}>
{s.gateway_reachable ? "Online" : "Offline"}
</div> </div>
</div> </div>
{/* Role Nodes */} {/* COLUMN 3: Brain & Wiring */}
{ROLES.map((role) => {
const yPositions = ["12%", "31%", "50%", "69%", "88%"]
const activeModel = getModelForRole(role)
const isWarm = activeModel ? running.includes(activeModel.name) : false
if (role === "reasoning" || role === "agent") return null
const indexMap = { fast: 0, heavy: 1, coder: 2, vision: 3, scout: 4 }[role] as number
return (
<div <div
key={role}
className={cn( className={cn(
"absolute z-10 w-44 py-1.5 px-3 -translate-y-1/2 -translate-x-1/2 rounded-xl border flex flex-col justify-center hover:border-primary/50 transition-all text-left shadow select-none cursor-pointer", "absolute z-10 w-44 py-1.5 px-3 -translate-y-1/2 -translate-x-1/2 rounded-xl border flex flex-col justify-center hover:border-primary/50 transition-all text-left shadow select-none",
isWarm s.gateway_reachable ? "border-emerald-500/50 bg-emerald-500/5 shadow-emerald-500/5" : "border-border/60 bg-card/75"
? "border-emerald-500/50 bg-emerald-500/5 shadow-emerald-500/5"
: activeModel
? "border-border/60 bg-card/75"
: "border-dashed border-border/40 bg-background/20"
)} )}
style={{ left: "90%", top: yPositions[indexMap] }} style={{ left: "85%", top: "30%" }}
onClick={() => setActiveRoleDrop(activeRoleDrop === role ? null : role)} onMouseEnter={() => setHoveredNode("brain")}
onMouseLeave={() => setHoveredNode(null)}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">{role}</span> <div className="flex items-center gap-1 text-muted-foreground">
{isWarm && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />} <Cpu className="h-3 w-3 text-primary" />
<span className="text-[10px] font-bold uppercase tracking-wider">Aktives Gehirn</span>
</div>
{s.gateway_reachable && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
</div>
<div className="text-[10px] font-mono font-medium truncate mt-0.5 text-foreground font-space" title={s.brain_model}>
{s.brain_model || "auto"}
</div> </div>
<div className="text-[10px] font-mono font-medium truncate mt-0.5 text-foreground max-w-[150px]">
{activeModel ? activeModel.name.split("/").pop()?.replace(".gguf", "") : "Keine Zuweisung"}
</div> </div>
{/* Zuweisung Popover */}
{activeRoleDrop === role && (
<div className="absolute right-0 top-full mt-1 z-35 w-52 rounded-xl border border-border/80 bg-popover/95 backdrop-blur-md p-1.5 shadow-2xl space-y-1">
<div className="text-[9px] uppercase font-bold text-muted-foreground/60 px-2 py-1 select-none">Modell zuweisen:</div>
<button
onClick={() => handleRoleChange(role, "")}
className="w-full text-left px-2.5 py-1.5 rounded-lg text-[10px] hover:bg-accent text-red-400 font-semibold cursor-pointer"
>
Zuweisung entfernen
</button>
{models.map((m) => (
<button
key={m.name}
onClick={() => handleRoleChange(role, m.name)}
className={cn(
"w-full text-left px-2.5 py-1.5 rounded-lg text-[10px] hover:bg-accent flex items-center justify-between font-mono cursor-pointer",
m.role === role ? "text-primary font-bold" : "text-foreground"
)}
>
<span className="truncate max-w-[150px]">{m.name.split("/").pop()?.replace(".gguf", "")}</span>
{m.role === role && <Check className="h-3 w-3 shrink-0" />}
</button>
))}
</div>
)}
</div>
)
})}
{/* Floating Setup Popover */}
{activeClient && connectData && (
<div <div
className="absolute z-30 md:w-96 w-[90%] rounded-2xl border border-border/80 bg-card/95 backdrop-blur-xl p-4 shadow-2xl space-y-3 flex flex-col justify-between" className={cn(
style={{ "absolute z-10 w-44 py-1.5 px-3 -translate-y-1/2 -translate-x-1/2 rounded-xl border flex flex-col justify-center hover:border-primary/50 transition-all text-left shadow select-none",
left: "22%", s.has_config ? "border-emerald-500/50 bg-emerald-500/5 shadow-emerald-500/5" : "border-border/60 bg-card/75"
top: activeClient === "roocode" ? "5%" )}
: activeClient === "cursor" ? "20%" style={{ left: "85%", top: "70%" }}
: activeClient === "opencode" ? "40%" onMouseEnter={() => setHoveredNode("wiring")}
: activeClient === "zed" ? "55%" onMouseLeave={() => setHoveredNode(null)}
: "65%"
}}
> >
<div className="flex items-center justify-between border-b border-border/20 pb-2"> <div className="flex items-center justify-between">
<span className="text-[11px] font-bold uppercase tracking-wider text-primary"> <div className="flex items-center gap-1 text-muted-foreground">
{activeClient === "roocode" && "Roo Code Setup"} <Wrench className="h-3 w-3 text-primary" />
{activeClient === "cursor" && "Cursor Setup"} <span className="text-[10px] font-bold uppercase tracking-wider">Verdrahtung</span>
{activeClient === "opencode" && "OpenCode Setup"}
{activeClient === "zed" && "Zed Setup"}
{activeClient === "continue" && "Continue Setup"}
</span>
<button
onClick={() => setActiveClient(null)}
className="p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer"
>
<X className="h-3.5 w-3.5" />
</button>
</div> </div>
{s.has_config && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
<div className="text-[10px] text-muted-foreground leading-relaxed space-y-2">
{activeClient === "roocode" && (
<ul className="list-decimal pl-4 space-y-1">
<li>Suche in VS Code nach der Erweiterung <strong>Roo Code</strong> und installiere sie.</li>
<li>Wähle in den Roo Code Einstellungen: Provider: <strong>OpenAI Compatible</strong>.</li>
<li>Füge das untenstehende JSON-Snippet in die <code>settings.json</code> ein.</li>
</ul>
)}
{activeClient === "cursor" && (
<ul className="list-decimal pl-4 space-y-1">
<li>Öffne Cursor Settings <strong>Models</strong>.</li>
<li>Deaktiviere Cloud-Modelle, klappe <strong>OpenAI API</strong> auf.</li>
<li>Trage die Base URL unten ein und aktiviere das Modell <strong>auto</strong>.</li>
</ul>
)}
{activeClient === "opencode" && (
<ul className="list-decimal pl-4 space-y-1">
<li>Öffne die <code>opencode.jsonc</code> Konfigurationsdatei.</li>
<li>Ersetze den Provider-Eintrag unter <code>provider</code> mit dem Snippet unten.</li>
</ul>
)}
{activeClient === "zed" && (
<ul className="list-decimal pl-4 space-y-1">
<li>Öffne die Zed Settings (<code>ctrl+,</code>).</li>
<li>Füge das untenstehende JSON-Segment unter <code>language_models</code> ein.</li>
</ul>
)}
{activeClient === "continue" && (
<ul className="list-decimal pl-4 space-y-1">
<li>Klicke auf das Zahnrad-Symbol in der Continue-Erweiterung.</li>
<li>Füge den Gateway-Eintrag zum <code>models</code>-Array hinzu.</li>
</ul>
)}
</div> </div>
<div className="text-[9px] font-mono mt-0.5 text-muted-foreground flex gap-1 justify-between">
{connectData.tools && ( <span>Config: {s.has_config ? "✓" : "—"}</span>
<div className="relative rounded-xl border border-border/40 bg-black/40 overflow-hidden mt-1 shrink-0"> <span>Skills: {s.has_skills ? "✓" : "—"}</span>
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/20 bg-black/20"> <span>Memory: {s.has_memories ? "✓" : "—"}</span>
<span className="text-[8px] font-mono text-muted-foreground">JSON Config</span>
<button
onClick={() => copySnippet(
activeClient === "roocode" ? connectData.tools.cline?.snippet :
activeClient === "cursor" ? connectData.tools.cursor?.snippet :
activeClient === "opencode" ? connectData.tools.opencode?.snippet :
activeClient === "zed" ? connectData.tools.zed?.snippet :
connectData.tools.continue?.snippet
)}
className="text-[9px] font-semibold text-primary hover:text-primary-foreground flex items-center gap-1 cursor-pointer"
>
{copied ? <Check className="h-3 w-3 text-emerald-400" /> : <Copy className="h-3 w-3" />}
<span>{copied ? "Kopiert" : "Kopieren"}</span>
</button>
</div> </div>
<pre className="p-3 max-h-36 overflow-y-auto text-[9px] font-mono text-cyan-200/90 whitespace-pre scrollbar-thin select-text">
<code>
{activeClient === "roocode" && connectData.tools.cline?.snippet}
{activeClient === "cursor" && connectData.tools.cursor?.snippet}
{activeClient === "opencode" && connectData.tools.opencode?.snippet}
{activeClient === "zed" && connectData.tools.zed?.snippet}
{activeClient === "continue" && connectData.tools.continue?.snippet}
</code>
</pre>
</div> </div>
)}
</div>
)}
</div> </div>
{/* Legend */} {/* Legend */}
<div className="flex flex-wrap items-center gap-6 mt-3 text-[10px] text-muted-foreground border-t border-border/20 pt-3"> <div className="flex flex-wrap items-center gap-6 mt-3 text-[10px] text-muted-foreground border-t border-border/20 pt-3">
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-cyan-400" /> <span className="h-2 w-2 rounded-full bg-cyan-400" />
<span>Cyan-Fluss: Client-Anfrage an Gateway</span> <span>Cyan-Fluss: Client-Anfrage an Agenten</span>
</span> </span>
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" /> <span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
<span>Grüner Puls: Aktive Verbindung / Warmes Modell geladen</span> <span>Grüner Puls: Verbindung hergestellt / Modul online</span>
</span>
<span className="flex items-center gap-1.5">
<span className="h-2 w-2 rounded bg-gradient-to-r from-teal-500 to-emerald-500" />
<span>VRAM-Verlauf: Modellspezifischer Speicheranteil</span>
</span> </span>
</div> </div>
</div> </div>
+11 -11
View File
@@ -327,7 +327,7 @@ function Cockpit() {
stroke-dashoffset: -20; stroke-dashoffset: -20;
} }
} }
.animate-flow-cyan { .svg-flow-path {
stroke-dasharray: 4 6; stroke-dasharray: 4 6;
animation: flow-dash 1s linear infinite; animation: flow-dash 1s linear infinite;
} }
@@ -420,62 +420,62 @@ function Cockpit() {
{/* Roo Code (y=10) */} {/* Roo Code (y=10) */}
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "roocode" || activeClient === "roocode") && ( {(hoveredNode === "roocode" || activeClient === "roocode") && (
<path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* Cursor (y=30) */} {/* Cursor (y=30) */}
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "cursor" || activeClient === "cursor") && ( {(hoveredNode === "cursor" || activeClient === "cursor") && (
<path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* OpenCode (y=50) */} {/* OpenCode (y=50) */}
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "opencode" || activeClient === "opencode") && ( {(hoveredNode === "opencode" || activeClient === "opencode") && (
<path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* Zed (y=70) */} {/* Zed (y=70) */}
<path d={getClientPath(0.70)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getClientPath(0.70)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "zed" || activeClient === "zed") && ( {(hoveredNode === "zed" || activeClient === "zed") && (
<path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* Continue (y=90) */} {/* Continue (y=90) */}
<path d={getClientPath(0.90)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getClientPath(0.90)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{(hoveredNode === "continue" || activeClient === "continue") && ( {(hoveredNode === "continue" || activeClient === "continue") && (
<path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* Bezier Curves: Gateway to Roles (50% -> 90%) */} {/* Bezier Curves: Gateway to Roles (50% -> 90%) */}
{/* fast (y=12) */} {/* fast (y=12) */}
<path d={getRolePath(0.12)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getRolePath(0.12)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("fast") && ( {isRoleRunning("fast") && (
<path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* heavy (y=31) */} {/* heavy (y=31) */}
<path d={getRolePath(0.31)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getRolePath(0.31)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("heavy") && ( {isRoleRunning("heavy") && (
<path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* coder (y=50) */} {/* coder (y=50) */}
<path d={getRolePath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getRolePath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("coder") && ( {isRoleRunning("coder") && (
<path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* vision (y=69) */} {/* vision (y=69) */}
<path d={getRolePath(0.69)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getRolePath(0.69)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("vision") && ( {isRoleRunning("vision") && (
<path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
{/* scout (y=88) */} {/* scout (y=88) */}
<path d={getRolePath(0.88)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" /> <path d={getRolePath(0.88)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
{isRoleRunning("scout") && ( {isRoleRunning("scout") && (
<path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" /> <path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
)} )}
</svg> </svg>