Feat: Memory-Onboarding via Hermes statt fester Starter-Vorlage

Empty State der Gedaechtnis-Seite fragt nicht mehr nach kanned Facts (war zu sehr
auf eine Person zugeschnitten), sondern leitet ein Onboarding-GESPRAECH mit Hermes
an: das UI zeigt einen fertigen, generischen Onboarding-Prompt (Hermes interviewt
den Nutzer in 5 Schritten). Buttons: "Im Terminal starten" (kopiert Prompt + springt
zum Terminal-Tab via mc-navigate-Event) und "Prompt kopieren"; Hinweis auf Telegram +
manuelles Anlegen. Das Gespraech fuellt das Gedaechtnis dann ueber den Auto-Lern-Hook.

App.tsx: mc-navigate CustomEvent -> setView (Tab-Wechsel aus Views heraus).

Live verifiziert (Onboarding-State, Prompt, Terminal-Navigation, kein Fehler).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 20:49:04 +02:00
parent 00209dedff
commit 689ea48d72
7 changed files with 194 additions and 177 deletions
+9
View File
@@ -39,6 +39,15 @@ export default function App() {
return () => window.removeEventListener("open-system-drawer", handleOpen)
}, [])
useEffect(() => {
const handleNav = (e: Event) => {
const v = (e as CustomEvent).detail?.view as ViewId | undefined
if (v) setView(v)
}
window.addEventListener("mc-navigate", handleNav)
return () => window.removeEventListener("mc-navigate", handleNav)
}, [])
const active = NAV.find((n) => n.id === view)!
return (
+55 -58
View File
@@ -1,7 +1,7 @@
import { useState, useMemo, lazy, Suspense } from "react"
import {
Trash2, Sparkles, User, Scroll, Shield, Tag, Clock, Search, Plus, BookOpen,
Share2, List, Brain, X, Wand2,
Share2, List, MessagesSquare, X, Copy, Check, Terminal as TerminalIcon, Send,
} from "lucide-react"
import { api, type DedupeResult } from "@/lib/api"
import { useMemory, useMemoryGraph, useQueryClient } from "@/lib/queries"
@@ -31,16 +31,16 @@ const BORDER_CLASSES: Record<string, string> = {
versioned: "border-l-amber-500/80", ephemeral: "border-l-pink-500/80",
}
// Starter-Vorlage („Beginner Template"): wird NUR auf Knopfdruck eingefügt (source=template),
// jeder Eintrag einzeln editier-/löschbar. Bewusst knapp & atomar gehalten.
const STARTER_TEMPLATE: { content: string; category: string }[] = [
{ content: "Der Nutzer heißt Tobi — Homelab-Entwickler, bevorzugt pragmatische Lösungen.", category: "user" },
{ content: "Bevorzugt knappe, deutsche Antworten.", category: "user" },
{ content: "Deploy nur via PowerShell-Push (Git Credential Manager).", category: "instruction" },
{ content: "Tests bei Deployments nie überspringen.", category: "instruction" },
{ content: "AI-Box: 192.168.178.151 (Ryzen AI MAX+ 395, 122 GB Unified-RAM).", category: "stable" },
{ content: "Agent-Hirn = fast (Qwen3.6-35B-A3B), immer warm.", category: "stable" },
]
// Onboarding-Prompt: der Nutzer startet damit ein Gespräch mit Hermes (Terminal/Telegram).
// Hermes interviewt ihn Schritt für Schritt — der Auto-Lern-Hook füllt dabei das Gedächtnis.
// Bewusst GENERISCH (nicht auf eine Person zugeschnitten).
const ONBOARDING_PROMPT = `Hi Hermes! Lass uns ein kurzes Onboarding machen, damit du dich künftig an mich erinnerst. Stell mir nacheinander ein paar kurze Fragen zu:
1) wer ich bin und woran ich gerade arbeite,
2) wie ich angesprochen werden möchte,
3) meine bevorzugten Tools, Sprachen und Arbeitsweise,
4) wichtige Regeln/Konventionen, die du beachten sollst,
5) meine Infrastruktur (Server, Dienste ohne Geheimnisse).
Frag immer nur eine Sache auf einmal und fass am Ende zusammen, was du dir gemerkt hast.`
export function MemoryView() {
const [filter, setFilter] = useState("")
@@ -48,7 +48,7 @@ export function MemoryView() {
const [content, setContent] = useState("")
const [category, setCategory] = useState("stable")
const [deduping, setDeduping] = useState(false)
const [seeding, setSeeding] = useState(false)
const [copied, setCopied] = useState(false)
const [view, setView] = useState<"liste" | "graph">("graph")
const [showAdd, setShowAdd] = useState(false)
@@ -92,24 +92,19 @@ export function MemoryView() {
reloadMemory()
}
function applyTemplate() {
showConfirm(
"Starter-Vorlage einfügen",
`${STARTER_TEMPLATE.length} Beispiel-Fakten über dein Setup einfügen? Du kannst jeden einzeln bearbeiten oder löschen.`,
async () => {
setSeeding(true)
try {
for (const t of STARTER_TEMPLATE) {
await api("/api/memory", { method: "POST", body: JSON.stringify({ content: t.content, category: t.category, source: "template" }) })
}
reloadMemory()
} catch (e: any) {
showAlert("Fehler", `Vorlage konnte nicht eingefügt werden: ${e.message}`)
} finally {
setSeeding(false)
}
},
)
async function copyPrompt() {
try {
await navigator.clipboard.writeText(ONBOARDING_PROMPT)
setCopied(true)
setTimeout(() => setCopied(false), 1800)
} catch {
showAlert("Kopieren fehlgeschlagen", "Markiere den Prompt und kopiere ihn manuell (Strg+C).")
}
}
function goTerminal() {
copyPrompt()
window.dispatchEvent(new CustomEvent("mc-navigate", { detail: { view: "terminal" } }))
}
async function cleanup() {
@@ -219,41 +214,43 @@ export function MemoryView() {
{/* Inhalt: Empty State · Graph · Liste */}
{empty ? (
<div className="rounded-2xl border border-dashed border-border/60 bg-card/20 p-10 flex flex-col items-center text-center gap-5">
<div className="rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 sm:p-10 flex flex-col items-center text-center gap-5">
<div className="h-14 w-14 rounded-2xl bg-primary/10 flex items-center justify-center">
<Brain className="h-7 w-7 text-primary" />
<MessagesSquare className="h-7 w-7 text-primary" />
</div>
<div className="space-y-1.5 max-w-md">
<h3 className="text-base font-semibold text-foreground">Dein Gedächtnis ist noch leer</h3>
<div className="space-y-1.5 max-w-lg">
<h3 className="text-base font-semibold text-foreground">Lass Hermes dich kennenlernen</h3>
<p className="text-xs text-muted-foreground leading-relaxed">
Es füllt sich automatisch, sobald Hermes mit dir spricht im Terminal, über Telegram oder aus der IDE.
Oder leg jetzt mit einer Vorlage los:
Statt Fakten einzutippen: führ ein kurzes Onboarding-Gespräch mit Hermes. Was ihr besprecht,
merkt sich das Gedächtnis automatisch du musst nichts manuell pflegen.
</p>
</div>
<div className="flex flex-wrap items-center justify-center gap-2">
<button onClick={applyTemplate} disabled={seeding}
className="h-9 px-4 rounded-lg bg-primary text-primary-foreground text-xs font-semibold hover:opacity-90 transition-all flex items-center gap-1.5 cursor-pointer disabled:opacity-50">
<Wand2 className="h-4 w-4" /> {seeding ? "Füge ein…" : "Starter-Vorlage einfügen"}
</button>
<button onClick={() => setShowAdd(true)}
className="h-9 px-4 rounded-lg border border-border/60 bg-card text-xs font-semibold hover:border-primary/50 transition-all flex items-center gap-1.5 cursor-pointer">
<Plus className="h-4 w-4" /> Eigener Eintrag
</button>
</div>
<div className="w-full max-w-md text-left">
<div className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60 mb-2">Die Vorlage enthält</div>
<div className="flex flex-col gap-1">
{STARTER_TEMPLATE.map((t, i) => {
const conf = CAT_CONFIG[t.category] || DEFAULT_CAT
return (
<div key={i} className="flex items-center gap-2 text-[11px] text-muted-foreground">
<span className={cn("text-[8px] font-bold uppercase px-1.5 py-0.5 rounded shrink-0", conf.bg, conf.text)}>{conf.label}</span>
<span className="truncate">{t.content}</span>
</div>
)
})}
{/* Vorgeschlagener Prompt */}
<div className="w-full max-w-lg text-left">
<div className="flex items-center justify-between mb-1.5">
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60">Sende das an Hermes</span>
<button onClick={copyPrompt} className="flex items-center gap-1 text-[10px] font-semibold text-muted-foreground hover:text-foreground transition-colors">
{copied ? <><Check className="h-3 w-3 text-emerald-400" /> Kopiert</> : <><Copy className="h-3 w-3" /> Kopieren</>}
</button>
</div>
<pre className="w-full rounded-xl border border-border/60 bg-background/40 p-3.5 text-[11px] leading-relaxed text-foreground/90 whitespace-pre-wrap font-mono">{ONBOARDING_PROMPT}</pre>
</div>
<div className="flex flex-wrap items-center justify-center gap-2">
<button onClick={goTerminal}
className="h-9 px-4 rounded-lg bg-primary text-primary-foreground text-xs font-semibold hover:opacity-90 transition-all flex items-center gap-1.5 cursor-pointer">
<TerminalIcon className="h-4 w-4" /> Im Terminal starten
</button>
<button onClick={copyPrompt}
className="h-9 px-4 rounded-lg border border-border/60 bg-card text-xs font-semibold hover:border-primary/50 transition-all flex items-center gap-1.5 cursor-pointer">
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />} Prompt kopieren
</button>
</div>
<p className="text-[11px] text-muted-foreground/70 flex items-center gap-1.5">
<Send className="h-3 w-3" /> Funktioniert genauso über Telegram {" "}
<button onClick={() => setShowAdd(true)} className="underline hover:text-foreground">oder lieber manuell anlegen</button>.
</p>
</div>
) : view === "graph" ? (
<GraphErrorBoundary>