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
@@ -1,4 +1,4 @@
import{r as K,a as Hv,g as Hr,R as Um,c as q2,j as Ne,l as Z2,S as K2,b as Vx,T as $2}from"./index-D7EBqqTV.js";/**
import{r as K,a as Hv,g as Hr,R as Um,c as q2,j as Ne,l as Z2,S as K2,b as Vx,T as $2}from"./index-BVXzL-Al.js";/**
* @license
* Copyright 2010-2023 Three.js Authors
* SPDX-License-Identifier: MIT
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="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-D7EBqqTV.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DNH8F21o.css">
<script type="module" crossorigin src="/assets/index-BVXzL-Al.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DGdkjhdp.css">
</head>
<body>
<div id="root"></div>
+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>