fix: simplify agent graph to show the 4 tiles and fix compilation errors
This commit is contained in:
+1
File diff suppressed because one or more lines are too long
+350
File diff suppressed because one or more lines are too long
-350
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-izDdu6A_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-mQ1DUIFB.css">
|
||||
<script type="module" crossorigin src="/assets/index-CiscfPzK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-ChiXEUW1.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, useRef, useCallback } from "react"
|
||||
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, X, Check, Copy } from "lucide-react"
|
||||
import { api, type AgentStatus, type ModelInfo, type RoutingResp, type ConnectResp } from "@/lib/api"
|
||||
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield } from "lucide-react"
|
||||
import { api, type AgentStatus } from "@/lib/api"
|
||||
import { cn, resolveExternalUrl } from "@/lib/utils"
|
||||
|
||||
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() {
|
||||
const [s, setS] = useState<AgentStatus | null>(null)
|
||||
const [error, setError] = useState("")
|
||||
|
||||
// Graph states
|
||||
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)
|
||||
// Graph UI state
|
||||
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
||||
|
||||
// Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling
|
||||
@@ -71,47 +60,14 @@ export function AgentView() {
|
||||
const w = dimensions.width
|
||||
const h = dimensions.height
|
||||
|
||||
// Helper to generate curve from client to gateway
|
||||
const getClientPath = (yPercent: number) => {
|
||||
const startX = w * 0.1
|
||||
const startY = h * yPercent
|
||||
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}`
|
||||
// Helper to generate curve between any two points
|
||||
const getCurvePath = (startX: number, startY: number, endX: number, endY: number) => {
|
||||
const midX = (startX + endX) / 2
|
||||
return `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
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(() => {
|
||||
@@ -120,32 +76,6 @@ export function AgentView() {
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{/* Styles inside AgentView for dash flow animations */}
|
||||
@@ -155,7 +85,7 @@ export function AgentView() {
|
||||
stroke-dashoffset: -20;
|
||||
}
|
||||
}
|
||||
.animate-flow-cyan {
|
||||
.svg-flow-path {
|
||||
stroke-dasharray: 4 6;
|
||||
animation: flow-dash 1s linear infinite;
|
||||
}
|
||||
@@ -229,9 +159,9 @@ export function AgentView() {
|
||||
{/* 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>
|
||||
<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">
|
||||
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>
|
||||
</div>
|
||||
|
||||
@@ -249,309 +179,115 @@ export function AgentView() {
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{/* Bezier Curves: Clients to Gateway */}
|
||||
{/* Roo Code (y=10) */}
|
||||
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(hoveredNode === "roocode" || activeClient === "roocode") && (
|
||||
<path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||
{/* Connection: WebUI to Gateway */}
|
||||
<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" />
|
||||
{(hoveredNode === "webui" || s.webui_reachable) && (
|
||||
<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" />
|
||||
)}
|
||||
|
||||
{/* Cursor (y=30) */}
|
||||
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(hoveredNode === "cursor" || activeClient === "cursor") && (
|
||||
<path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||
{/* Connection: Gateway to Gehirn */}
|
||||
<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 === "gateway" || hoveredNode === "brain" || s.gateway_reachable) && (
|
||||
<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) */}
|
||||
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(hoveredNode === "opencode" || activeClient === "opencode") && (
|
||||
<path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
{/* Connection: Gateway to Verdrahtung */}
|
||||
<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 === "gateway" || hoveredNode === "wiring" || s.gateway_reachable) && (
|
||||
<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" />
|
||||
)}
|
||||
</svg>
|
||||
|
||||
{/* Client Nodes */}
|
||||
{/* COLUMN 1: Interface */}
|
||||
<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: "10%" }}
|
||||
onMouseEnter={() => setHoveredNode("roocode")}
|
||||
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: "15%", top: "50%" }}
|
||||
onMouseEnter={() => setHoveredNode("webui")}
|
||||
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" />
|
||||
<span>Roo Code</span>
|
||||
<Activity className={cn("h-3.5 w-3.5", s.webui_reachable ? "text-emerald-400" : "text-amber-500")} />
|
||||
<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>
|
||||
|
||||
{/* COLUMN 2: Gateway */}
|
||||
<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: "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"
|
||||
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: "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="text-[10px] font-mono text-muted-foreground mt-0.5">
|
||||
Schwelle: > {routing?.heavy_threshold_chars ? (routing.heavy_threshold_chars / 1000) : "4"}k Zeichen
|
||||
<div className="flex items-center gap-1">
|
||||
<Bot className="h-3.5 w-3.5 text-primary" />
|
||||
<span className="text-[10px] uppercase font-bold text-primary font-space tracking-wide">Agent Gateway</span>
|
||||
</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">
|
||||
Auto-Swap
|
||||
<div className="text-[9px] font-mono text-muted-foreground mt-0.5">Port :8642</div>
|
||||
<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>
|
||||
|
||||
{/* Role Nodes */}
|
||||
{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 (
|
||||
{/* COLUMN 3: Brain & Wiring */}
|
||||
<div
|
||||
key={role}
|
||||
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",
|
||||
isWarm
|
||||
? "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"
|
||||
"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",
|
||||
s.gateway_reachable ? "border-emerald-500/50 bg-emerald-500/5 shadow-emerald-500/5" : "border-border/60 bg-card/75"
|
||||
)}
|
||||
style={{ left: "90%", top: yPositions[indexMap] }}
|
||||
onClick={() => setActiveRoleDrop(activeRoleDrop === role ? null : role)}
|
||||
style={{ left: "85%", top: "30%" }}
|
||||
onMouseEnter={() => setHoveredNode("brain")}
|
||||
onMouseLeave={() => setHoveredNode(null)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">{role}</span>
|
||||
{isWarm && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<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 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>
|
||||
|
||||
{/* 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
|
||||
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"
|
||||
style={{
|
||||
left: "22%",
|
||||
top: activeClient === "roocode" ? "5%"
|
||||
: activeClient === "cursor" ? "20%"
|
||||
: activeClient === "opencode" ? "40%"
|
||||
: activeClient === "zed" ? "55%"
|
||||
: "65%"
|
||||
}}
|
||||
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",
|
||||
s.has_config ? "border-emerald-500/50 bg-emerald-500/5 shadow-emerald-500/5" : "border-border/60 bg-card/75"
|
||||
)}
|
||||
style={{ left: "85%", top: "70%" }}
|
||||
onMouseEnter={() => setHoveredNode("wiring")}
|
||||
onMouseLeave={() => setHoveredNode(null)}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-border/20 pb-2">
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-primary">
|
||||
{activeClient === "roocode" && "Roo Code Setup"}
|
||||
{activeClient === "cursor" && "Cursor Setup"}
|
||||
{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 className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Wrench className="h-3 w-3 text-primary" />
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider">Verdrahtung</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
)}
|
||||
{s.has_config && <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />}
|
||||
</div>
|
||||
|
||||
{connectData.tools && (
|
||||
<div className="relative rounded-xl border border-border/40 bg-black/40 overflow-hidden mt-1 shrink-0">
|
||||
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/20 bg-black/20">
|
||||
<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 className="text-[9px] font-mono mt-0.5 text-muted-foreground flex gap-1 justify-between">
|
||||
<span>Config: {s.has_config ? "✓" : "—"}</span>
|
||||
<span>Skills: {s.has_skills ? "✓" : "—"}</span>
|
||||
<span>Memory: {s.has_memories ? "✓" : "—"}</span>
|
||||
</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>
|
||||
|
||||
{/* Legend */}
|
||||
<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="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 className="flex items-center gap-1.5">
|
||||
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span>Grüner Puls: Aktive Verbindung / Warmes Modell geladen</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>Grüner Puls: Verbindung hergestellt / Modul online</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -327,7 +327,7 @@ function Cockpit() {
|
||||
stroke-dashoffset: -20;
|
||||
}
|
||||
}
|
||||
.animate-flow-cyan {
|
||||
.svg-flow-path {
|
||||
stroke-dasharray: 4 6;
|
||||
animation: flow-dash 1s linear infinite;
|
||||
}
|
||||
@@ -420,62 +420,62 @@ function Cockpit() {
|
||||
{/* Roo Code (y=10) */}
|
||||
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(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) */}
|
||||
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(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) */}
|
||||
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(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) */}
|
||||
<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" />
|
||||
<path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
<path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* Bezier Curves: Gateway to Roles (50% -> 90%) */}
|
||||
{/* 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" />
|
||||
<path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
<path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
<path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
<path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
{/* 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" />
|
||||
<path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
</svg>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user