Refactor: TanStack-Query-Daten-Layer + useDialog (Phase 3b)
- @tanstack/react-query (v5) als zentraler Daten-Layer; QueryClientProvider in main.tsx (retry 1, kein refetchOnWindowFocus, staleTime 5s). - lib/queries.ts: Domänen-Hooks (useHealth/useSystemStatus/useServices/ useModels/useRouting/useJobs/useTokenStats/useAgentStatus/useUpdates/ useDiscover/useConnect/useMemory) + zentrale Query-Keys (qk) + invalidate. Gleicher Key = eine Anfrage über alle Views (Dedup), einheitliches Polling. - lib/useDialog.tsx: ein Hook für Alert/Confirm/Prompt statt 5x dupliziertem Dialog-State + showAlert/showConfirm. - api.ts: TokenStats + ModelsResp typisiert. - Migriert auf Hooks/useDialog: App, SystemView, AgentView, ConnectView, MemoryView (manuelles useEffect+setInterval entfernt; Mutationen invalidieren gezielt die Query-Keys). Verifiziert: tsc grün, Build grün, alle migrierten Views ohne Konsolenfehler, Live-Daten (CPU/RAM/Dienste) rendern korrekt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState, useRef, useCallback } from "react"
|
||||
import { useState, useRef, useCallback, useMemo } from "react"
|
||||
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, Check, X } from "lucide-react"
|
||||
import { api, type AgentStatus } from "@/lib/api"
|
||||
import { api } from "@/lib/api"
|
||||
import { useAgentStatus, useModels, useQueryClient, qk } from "@/lib/queries"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
import { cn, resolveExternalUrl } from "@/lib/utils"
|
||||
import { CustomDialog } from "@/components/CustomDialog"
|
||||
|
||||
|
||||
function Tile({ label, ok, detail, icon: Icon, onClick }: { label: string; ok: boolean; detail?: string; icon: any; onClick?: () => void }) {
|
||||
@@ -49,26 +50,21 @@ function Tile({ label, ok, detail, icon: Icon, onClick }: { label: string; ok: b
|
||||
}
|
||||
|
||||
export function AgentView() {
|
||||
const [s, setS] = useState<AgentStatus | null>(null)
|
||||
const [error, setError] = useState("")
|
||||
const { data: s, error: sErr } = useAgentStatus(5_000)
|
||||
const { data: modelsData } = useModels()
|
||||
const { showAlert, dialogElement } = useDialog()
|
||||
const qc = useQueryClient()
|
||||
const error = sErr ? String(sErr) : ""
|
||||
|
||||
const availableModels = useMemo(() => {
|
||||
const names = (modelsData?.models ?? []).map(
|
||||
(m) => m.name.split("/").pop()?.replace(".gguf", "") || m.name)
|
||||
return ["auto", "fast", "heavy", ...names]
|
||||
}, [modelsData])
|
||||
|
||||
// Graph UI state
|
||||
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
||||
const [showBrainSelect, setShowBrainSelect] = useState(false)
|
||||
const [availableModels, setAvailableModels] = useState<string[]>([])
|
||||
|
||||
// Custom Dialog State
|
||||
const [dialog, setDialog] = useState<{
|
||||
type: "alert" | "confirm"
|
||||
title: string
|
||||
message: string
|
||||
onConfirm?: () => void
|
||||
onCancel?: () => void
|
||||
} | null>(null)
|
||||
|
||||
function showAlert(title: string, message: string, onConfirm?: () => void) {
|
||||
setDialog({ type: "alert", title, message, onConfirm })
|
||||
}
|
||||
|
||||
// Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling
|
||||
const [dimensions, setDimensions] = useState({ width: 800, height: 360 })
|
||||
@@ -99,19 +95,6 @@ export function AgentView() {
|
||||
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)))
|
||||
}
|
||||
|
||||
function loadModels() {
|
||||
api<{ models: { name: string }[] }>("/api/models")
|
||||
.then((res) => {
|
||||
const names = res.models.map(m => m.name.split("/").pop()?.replace(".gguf", "") || m.name)
|
||||
setAvailableModels(["auto", "fast", "heavy", ...names])
|
||||
})
|
||||
.catch((e) => console.error("Error loading models", e))
|
||||
}
|
||||
|
||||
async function changeBrainModel(model: string) {
|
||||
try {
|
||||
await api("/api/agent/brain", {
|
||||
@@ -119,20 +102,13 @@ export function AgentView() {
|
||||
body: JSON.stringify({ model })
|
||||
})
|
||||
showAlert("Erfolgreich", `Hermes-Gehirn wurde auf '${model}' geändert. Der Gateway-Dienst wurde neu gestartet.`)
|
||||
loadData()
|
||||
qc.invalidateQueries({ queryKey: qk.agentStatus })
|
||||
setShowBrainSelect(false)
|
||||
} catch (e: any) {
|
||||
showAlert("Fehler", `Fehler beim Wechseln des Gehirns: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
loadModels()
|
||||
const t = setInterval(loadData, 5000)
|
||||
return () => clearInterval(t)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Styles inside AgentView for dash flow animations */}
|
||||
@@ -471,15 +447,7 @@ export function AgentView() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dialog && (
|
||||
<CustomDialog
|
||||
type={dialog.type}
|
||||
title={dialog.title}
|
||||
message={dialog.message}
|
||||
onConfirm={() => dialog.onConfirm && dialog.onConfirm()}
|
||||
onCancel={dialog.onCancel}
|
||||
/>
|
||||
)}
|
||||
{dialogElement}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user