diff --git a/frontend/src/app/error.tsx b/frontend/src/app/error.tsx new file mode 100644 index 0000000..6275525 --- /dev/null +++ b/frontend/src/app/error.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useEffect } from "react"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+

Something went wrong

+

+ {error.message || "An unexpected client-side error occurred."} +

+ +
+ ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 39acc92..402d60e 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -200,7 +200,7 @@ export default function Dashboard() { {memory.pinned && } - {memory.hscore === null ? "—" : memory.hscore.toFixed(2)} + {memory.hscore == null ? "—" : memory.hscore.toFixed(2)} ))} @@ -233,7 +233,7 @@ export default function Dashboard() {
{people.map((person, index) => ( - {person.name.slice(0, 2).toUpperCase()} + {(person.name || "?").slice(0, 2).toUpperCase()} {person.name} {person.memory_count} memories @@ -242,7 +242,7 @@ export default function Dashboard() { ))} {people.length === 0 && organizations.slice(0, 4).map((org, index) => ( - {org.name.slice(0, 2).toUpperCase()} + {(org.name || "?").slice(0, 2).toUpperCase()} {org.name} {org.memory_count} memories diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 114f269..3d3f9d4 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -52,7 +52,11 @@ async function fetchApi(path: string, options?: RequestInit): Promise { } catch {} throw new Error(`API error ${detail}`); } - return res.json(); + try { + return (await res.json()) as T; + } catch { + throw new Error(`API error: invalid response from ${path}`); + } } export function wsUrl(): string { diff --git a/server/cli.py b/server/cli.py index bca1e33..e6089c7 100644 --- a/server/cli.py +++ b/server/cli.py @@ -1229,6 +1229,29 @@ async def _run() -> dict: return 0 +def cmd_remove_demo(args: argparse.Namespace) -> int: + """Remove all demo-tagged memories, leaving real data untouched.""" + import asyncio + + from server.core import engine_provider + + async def _run() -> dict: + engine = engine_provider.get_engine() + await engine.initialize() + try: + return await engine.remove_demo_data() + finally: + await engine.shutdown() + + result = asyncio.run(_run()) + removed = result.get("removed", 0) + if removed == 0: + print(" No demo data found — nothing to remove.") + else: + print(f" Removed {removed} demo memories.") + return 0 + + # ── mcp config ─────────────────────────────────────────────────── def cmd_mcp_config(args: argparse.Namespace) -> int: @@ -1576,6 +1599,12 @@ def main() -> int: help="Seed even if the store already has memories", ) + # remove-demo (onboarding: strip the demo corpus back out) + sub.add_parser( + "remove-demo", + help="Remove demo-tagged memories, leaving real data untouched", + ) + # entities (persistent entity knowledge graph) ent_p = sub.add_parser("entities", help="Persistent entity knowledge graph") ent_sub = ent_p.add_subparsers(dest="entities_command") @@ -1654,6 +1683,8 @@ def main() -> int: return cmd_purge(args) elif args.command == "seed-demo": return cmd_seed_demo(args) + elif args.command == "remove-demo": + return cmd_remove_demo(args) elif args.command == "entities": if args.entities_command in ("reindex", "list", "about"): return cmd_entities(args)