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
|
* @license
|
||||||
* Copyright 2010-2023 Three.js Authors
|
* Copyright 2010-2023 Three.js Authors
|
||||||
* SPDX-License-Identifier: MIT
|
* 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="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-D7EBqqTV.js"></script>
|
<script type="module" crossorigin src="/assets/index-BVXzL-Al.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DNH8F21o.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DGdkjhdp.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ export default function App() {
|
|||||||
return () => window.removeEventListener("open-system-drawer", handleOpen)
|
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)!
|
const active = NAV.find((n) => n.id === view)!
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useMemo, lazy, Suspense } from "react"
|
import { useState, useMemo, lazy, Suspense } from "react"
|
||||||
import {
|
import {
|
||||||
Trash2, Sparkles, User, Scroll, Shield, Tag, Clock, Search, Plus, BookOpen,
|
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"
|
} from "lucide-react"
|
||||||
import { api, type DedupeResult } from "@/lib/api"
|
import { api, type DedupeResult } from "@/lib/api"
|
||||||
import { useMemory, useMemoryGraph, useQueryClient } from "@/lib/queries"
|
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",
|
versioned: "border-l-amber-500/80", ephemeral: "border-l-pink-500/80",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starter-Vorlage („Beginner Template"): wird NUR auf Knopfdruck eingefügt (source=template),
|
// Onboarding-Prompt: der Nutzer startet damit ein Gespräch mit Hermes (Terminal/Telegram).
|
||||||
// jeder Eintrag einzeln editier-/löschbar. Bewusst knapp & atomar gehalten.
|
// Hermes interviewt ihn Schritt für Schritt — der Auto-Lern-Hook füllt dabei das Gedächtnis.
|
||||||
const STARTER_TEMPLATE: { content: string; category: string }[] = [
|
// Bewusst GENERISCH (nicht auf eine Person zugeschnitten).
|
||||||
{ content: "Der Nutzer heißt Tobi — Homelab-Entwickler, bevorzugt pragmatische Lösungen.", category: "user" },
|
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:
|
||||||
{ content: "Bevorzugt knappe, deutsche Antworten.", category: "user" },
|
1) wer ich bin und woran ich gerade arbeite,
|
||||||
{ content: "Deploy nur via PowerShell-Push (Git Credential Manager).", category: "instruction" },
|
2) wie ich angesprochen werden möchte,
|
||||||
{ content: "Tests bei Deployments nie überspringen.", category: "instruction" },
|
3) meine bevorzugten Tools, Sprachen und Arbeitsweise,
|
||||||
{ content: "AI-Box: 192.168.178.151 (Ryzen AI MAX+ 395, 122 GB Unified-RAM).", category: "stable" },
|
4) wichtige Regeln/Konventionen, die du beachten sollst,
|
||||||
{ content: "Agent-Hirn = fast (Qwen3.6-35B-A3B), immer warm.", category: "stable" },
|
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() {
|
export function MemoryView() {
|
||||||
const [filter, setFilter] = useState("")
|
const [filter, setFilter] = useState("")
|
||||||
@@ -48,7 +48,7 @@ export function MemoryView() {
|
|||||||
const [content, setContent] = useState("")
|
const [content, setContent] = useState("")
|
||||||
const [category, setCategory] = useState("stable")
|
const [category, setCategory] = useState("stable")
|
||||||
const [deduping, setDeduping] = useState(false)
|
const [deduping, setDeduping] = useState(false)
|
||||||
const [seeding, setSeeding] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [view, setView] = useState<"liste" | "graph">("graph")
|
const [view, setView] = useState<"liste" | "graph">("graph")
|
||||||
const [showAdd, setShowAdd] = useState(false)
|
const [showAdd, setShowAdd] = useState(false)
|
||||||
|
|
||||||
@@ -92,24 +92,19 @@ export function MemoryView() {
|
|||||||
reloadMemory()
|
reloadMemory()
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTemplate() {
|
async function copyPrompt() {
|
||||||
showConfirm(
|
try {
|
||||||
"Starter-Vorlage einfügen",
|
await navigator.clipboard.writeText(ONBOARDING_PROMPT)
|
||||||
`${STARTER_TEMPLATE.length} Beispiel-Fakten über dein Setup einfügen? Du kannst jeden einzeln bearbeiten oder löschen.`,
|
setCopied(true)
|
||||||
async () => {
|
setTimeout(() => setCopied(false), 1800)
|
||||||
setSeeding(true)
|
} catch {
|
||||||
try {
|
showAlert("Kopieren fehlgeschlagen", "Markiere den Prompt und kopiere ihn manuell (Strg+C).")
|
||||||
for (const t of STARTER_TEMPLATE) {
|
}
|
||||||
await api("/api/memory", { method: "POST", body: JSON.stringify({ content: t.content, category: t.category, source: "template" }) })
|
}
|
||||||
}
|
|
||||||
reloadMemory()
|
function goTerminal() {
|
||||||
} catch (e: any) {
|
copyPrompt()
|
||||||
showAlert("Fehler", `Vorlage konnte nicht eingefügt werden: ${e.message}`)
|
window.dispatchEvent(new CustomEvent("mc-navigate", { detail: { view: "terminal" } }))
|
||||||
} finally {
|
|
||||||
setSeeding(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanup() {
|
async function cleanup() {
|
||||||
@@ -219,41 +214,43 @@ export function MemoryView() {
|
|||||||
|
|
||||||
{/* Inhalt: Empty State · Graph · Liste */}
|
{/* Inhalt: Empty State · Graph · Liste */}
|
||||||
{empty ? (
|
{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">
|
<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>
|
||||||
<div className="space-y-1.5 max-w-md">
|
<div className="space-y-1.5 max-w-lg">
|
||||||
<h3 className="text-base font-semibold text-foreground">Dein Gedächtnis ist noch leer</h3>
|
<h3 className="text-base font-semibold text-foreground">Lass Hermes dich kennenlernen</h3>
|
||||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
<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.
|
Statt Fakten einzutippen: führ ein kurzes Onboarding-Gespräch mit Hermes. Was ihr besprecht,
|
||||||
Oder leg jetzt mit einer Vorlage los:
|
merkt sich das Gedächtnis automatisch — du musst nichts manuell pflegen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
|
||||||
<button onClick={applyTemplate} disabled={seeding}
|
{/* Vorgeschlagener Prompt */}
|
||||||
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">
|
<div className="w-full max-w-lg text-left">
|
||||||
<Wand2 className="h-4 w-4" /> {seeding ? "Füge ein…" : "Starter-Vorlage einfügen"}
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
</button>
|
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60">Sende das an Hermes</span>
|
||||||
<button onClick={() => setShowAdd(true)}
|
<button onClick={copyPrompt} className="flex items-center gap-1 text-[10px] font-semibold text-muted-foreground hover:text-foreground transition-colors">
|
||||||
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-3 w-3 text-emerald-400" /> Kopiert</> : <><Copy className="h-3 w-3" /> Kopieren</>}
|
||||||
<Plus className="h-4 w-4" /> Eigener Eintrag
|
</button>
|
||||||
</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>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
) : view === "graph" ? (
|
) : view === "graph" ? (
|
||||||
<GraphErrorBoundary>
|
<GraphErrorBoundary>
|
||||||
|
|||||||
Reference in New Issue
Block a user