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:
+1
-1
@@ -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
|
||||
+126
-115
File diff suppressed because one or more lines are too long
+1
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-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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user