fix: remove Memory (Gehirn) completely from frontend widgets
Ampel / ampel (push) Successful in 22s

This commit is contained in:
Hitonabi
2026-08-07 11:05:59 +02:00
parent c2319739e2
commit d45f2fc6cb
34 changed files with 585 additions and 1303 deletions
+2 -18
View File
@@ -1,19 +1,18 @@
import { useState } from "react"
import {
Boxes, Brain, Server, ShieldAlert, Plug,
Boxes, Server, ShieldAlert, Plug,
HeartPulse, AlertTriangle, Loader2, Wrench, Check, HardDrive, ChevronRight,
Inbox, HelpCircle,
} from "lucide-react"
import { api } from "@/lib/api"
import {
useModels, useServices, useUpdates, useMemory, useAgentStatus, useConnectHealth,
useModels, useServices, useUpdates, useAgentStatus, useConnectHealth,
useAuftragsbuch, useIdeen, useQueryClient, qk,
} from "@/lib/queries"
import { useLucyHealth, type Repair } from "@/lib/useLucyHealth"
import { useDialog } from "@/lib/useDialog"
import { cn } from "@/lib/utils"
import { CockpitTile } from "./CockpitTile"
import { ErinnerungenCard } from "./ErinnerungenCard"
import { SystemStatusCard } from "@/components/dashboard/SystemStatusCard"
import { LatencyCard } from "@/components/dashboard/LatencyCard"
@@ -28,7 +27,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
const { data: models } = useModels()
const { data: svc } = useServices()
const { data: updates } = useUpdates()
const { data: memory } = useMemory()
const { data: agent } = useAgentStatus()
const { data: connect } = useConnectHealth()
const { data: auftraege } = useAuftragsbuch()
@@ -66,7 +64,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
const svcOk = svc?.services.filter((s) => s.ok).length ?? 0
const svcTotal = svc?.services.length ?? 0
const svcErrors = svcTotal - svcOk
const notes = memory?.length ?? 0
const pendingCount =
(updates ? (updates.os > 0 ? 1 : 0) + (updates.engine > 0 ? 1 : 0) + (updates.swap > 0 ? 1 : 0) + (updates.models > 0 ? 1 : 0) : 0) +
(updates?.components?.filter((c) => c.update === true).length ?? 0)
@@ -85,7 +82,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
// Zusätzliche reaktive Livedaten für die Kacheln
const activeBrain = models?.models?.find((m: any) => m.role === "hermes")
const brainName = activeBrain?.name ? activeBrain.name.split("/").pop()?.replace(/\.gguf$/i, "") : ""
const autoNotes = memory ? memory.filter((m: any) => ["auto", "agent", "hermes"].includes(m.source)).length : 0
const failedSvc = svc?.services.filter((s: any) => !s.ok).map((s: any) => s.name).join(", ")
const upParts = updates ? [
@@ -143,12 +139,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
hint={brainName ? `Hirn: ${brainName}` : "Kein Hermes-Modell geladen"}
onClick={() => onNavigate("models")}
/>
<CockpitTile
icon={Brain} title="Gedächtnis" value={memory ? notes : "…"} unit="Notizen"
tone={memory ? "ok" : "loading"}
hint={memory ? `${notes} Fakten (${autoNotes} auto gelernt)` : "Gedächtnis-Pool laden..."}
onClick={() => onNavigate("memory")}
/>
<CockpitTile
icon={Server} title="Dienste" value={svc ? `${svcOk}/${svcTotal}` : "…"} unit="laufen"
tone={!svc ? "loading" : svcErrors > 0 ? "warn" : "ok"}
@@ -186,12 +176,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
onNavigate={onNavigate}
/>
{/* Anstehendes — Erinnerungen & Routinen */}
<section>
<ZoneLabel>Anstehendes</ZoneLabel>
<ErinnerungenCard />
</section>
{/* Werkzeuge & Shortcuts */}
<section>
<ZoneLabel>Werkzeuge & Diagnose</ZoneLabel>
@@ -1,117 +0,0 @@
import { useState } from "react"
import { AlarmClock, Plus, Trash2, Loader2, X, Repeat } from "lucide-react"
import { api } from "@/lib/api"
import { useReminders, useQueryClient, qk } from "@/lib/queries"
import { useDialog } from "@/lib/useDialog"
import { cn } from "@/lib/utils"
// „Anstehendes": Erinnerungen & Routinen sichtbar und klickbar machen. Die API kann
// längst alles (Lucy legt per Stimme an) — hier kommt nur die GUI-Hälfte dazu.
const REPEAT_LABEL: Record<string, string> = {
"": "einmalig", daily: "täglich", weekdays: "werktags", weekly: "wöchentlich",
}
export function ErinnerungenCard() {
const { data: items = [], isLoading } = useReminders()
const qc = useQueryClient()
const { showAlert, showConfirm, dialogElement } = useDialog()
const [showAdd, setShowAdd] = useState(false)
const [text, setText] = useState("")
const [when, setWhen] = useState("")
const [repeat, setRepeat] = useState("")
const [saving, setSaving] = useState(false)
const reload = () => qc.invalidateQueries({ queryKey: qk.reminders })
async function add() {
if (!text.trim() || !when) return
setSaving(true)
try {
await api("/api/reminders", { method: "POST", body: JSON.stringify({ text, when, repeat }) })
setText(""); setWhen(""); setRepeat(""); setShowAdd(false)
reload()
} catch (e: any) {
showAlert("Anlegen fehlgeschlagen", String(e?.message || e))
} finally {
setSaving(false)
}
}
function del(id: number, t: string) {
showConfirm("Erinnerung löschen?", `${t}" wird entfernt.`, async () => {
try { await api(`/api/reminders/${id}`, { method: "DELETE" }); reload() }
catch (e: any) { showAlert("Fehler", String(e?.message || e)) }
})
}
const sorted = [...items].sort((a, b) => a.next_ts - b.next_ts)
return (
<div className="mc-card p-4">
<div className="mb-3 flex items-center justify-between">
<span className="flex items-center gap-2 text-sm font-bold text-foreground">
<span className="flex h-8 w-8 items-center justify-center rounded-lg border border-border/40 bg-background/30 text-amber-300">
<AlarmClock className="h-4 w-4" />
</span>
Anstehendes
</span>
<button onClick={() => setShowAdd((s) => !s)}
className="flex h-8 items-center gap-1 rounded-lg border border-border/60 bg-background/30 px-2.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground hover:border-primary/40 transition-all cursor-pointer">
{showAdd ? <X className="h-3.5 w-3.5" /> : <Plus className="h-3.5 w-3.5" />} {showAdd ? "Abbrechen" : "Neu"}
</button>
</div>
{showAdd && (
<div className="mb-3 space-y-2 rounded-xl border border-border/50 bg-background/30 p-3">
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="Woran soll Lucy erinnern?"
aria-label="Erinnerungstext"
className="h-9 w-full rounded-lg border border-border/60 bg-card/45 px-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50" />
<div className="flex flex-wrap items-center gap-2">
<input type="datetime-local" value={when} onChange={(e) => setWhen(e.target.value)}
aria-label="Zeitpunkt"
className="h-9 rounded-lg border border-border/60 bg-card/45 px-2 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50 [color-scheme:dark]" />
<select value={repeat} onChange={(e) => setRepeat(e.target.value)} aria-label="Wiederholung"
className="h-9 cursor-pointer rounded-lg border border-border/60 bg-card/45 px-2 text-xs font-semibold text-foreground outline-none">
{Object.entries(REPEAT_LABEL).map(([v, l]) => <option key={v} value={v} className="bg-popover">{l}</option>)}
</select>
<button onClick={add} disabled={saving || !text.trim() || !when}
className="ml-auto flex h-9 items-center gap-1.5 rounded-lg bg-primary px-3 text-xs font-semibold text-primary-foreground hover:opacity-90 transition-all cursor-pointer disabled:opacity-50">
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />} Anlegen
</button>
</div>
</div>
)}
{isLoading ? (
<p className="text-xs text-muted-foreground">Wird geladen </p>
) : sorted.length === 0 ? (
<p className="text-xs text-muted-foreground">Keine Erinnerungen. Lucy sagt dir hier (und per Stimme) Bescheid, wenn etwas ansteht.</p>
) : (
<div className="space-y-1.5">
{sorted.slice(0, 6).map((r) => {
const overdue = r.next_ts * 1000 < Date.now() && !r.repeat
return (
<div key={r.id} className="group flex items-center justify-between gap-2 rounded-lg border border-border/40 bg-background/20 px-2.5 py-2">
<div className="min-w-0">
<p className="truncate text-xs text-foreground">{r.text}</p>
<p className={cn("flex items-center gap-1.5 text-[10px]", overdue ? "text-amber-300" : "text-muted-foreground/70")}>
{new Date(r.next_ts * 1000).toLocaleString("de-DE", { weekday: "short", day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })}
{r.repeat && <span className="flex items-center gap-0.5"><Repeat className="h-2.5 w-2.5" />{REPEAT_LABEL[r.repeat] ?? r.repeat}</span>}
{overdue && "· überfällig"}
</p>
</div>
<button onClick={() => del(r.id, r.text)} aria-label="Erinnerung löschen"
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg border border-border/40 text-muted-foreground opacity-0 transition-all hover:bg-red-500/5 hover:text-red-400 group-hover:opacity-100 cursor-pointer">
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
)
})}
{sorted.length > 6 && <p className="text-[10px] text-muted-foreground/60">+{sorted.length - 6} weitere</p>}
</div>
)}
{dialogElement}
</div>
)
}