ltcai 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,232 +1,5 @@
1
- import * as React from "react";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { ImagePlus, MessageSquare, Send, Sparkles, Trash2 } from "lucide-react";
4
- import { latticeApi } from "@/api/client";
5
- import { DataPanel, EmptyState, EntityList, SourceBadge, StructuredView } from "@/components/primitives";
6
- import { Badge } from "@/components/ui/badge";
7
- import { Button } from "@/components/ui/button";
8
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
9
- import { Textarea } from "@/components/ui/textarea";
10
- import { useAppStore } from "@/store/appStore";
11
- import { asArray } from "@/lib/utils";
12
-
13
- type Msg = { role?: string; content?: string; timestamp?: string };
14
-
15
- function fileToDataUrl(file: File) {
16
- return new Promise<string>((resolve, reject) => {
17
- const reader = new FileReader();
18
- reader.onload = () => resolve(String(reader.result || ""));
19
- reader.onerror = () => reject(reader.error);
20
- reader.readAsDataURL(file);
21
- });
22
- }
1
+ import { BrainConversation } from "@/components/BrainConversation";
23
2
 
24
3
  export function AskPage() {
25
- const qc = useQueryClient();
26
- const history = useQuery({ queryKey: ["chatHistory"], queryFn: latticeApi.chatHistory });
27
- const models = useQuery({ queryKey: ["models"], queryFn: latticeApi.models });
28
- const [conversationId, setConversationId] = React.useState<string | null>(null);
29
- const conversation = useQuery({
30
- queryKey: ["conversation", conversationId],
31
- queryFn: () => latticeApi.conversation(conversationId || ""),
32
- enabled: !!conversationId,
33
- });
34
- const [messages, setMessages] = React.useState<Msg[]>([]);
35
- const [draft, setDraft] = React.useState("");
36
- const [imageData, setImageData] = React.useState<string | null>(null);
37
- const [trace, setTrace] = React.useState<unknown>(null);
38
- const [streaming, setStreaming] = React.useState(false);
39
-
40
- React.useEffect(() => {
41
- if (conversation.data?.ok) setMessages(asArray<Msg>((conversation.data.data as Record<string, unknown>).messages || conversation.data.data));
42
- }, [conversation.data]);
43
-
44
- const send = async () => {
45
- const message = draft.trim();
46
- if (!message || streaming) return;
47
- setDraft("");
48
- setMessages((items) => [...items, { role: "user", content: message }, { role: "assistant", content: "" }]);
49
- setStreaming(true);
50
- try {
51
- const result = await latticeApi.streamChat(
52
- { message, conversation_id: conversationId || undefined, image_data: imageData || undefined },
53
- {
54
- onChunk: (_delta, fullText) => {
55
- setMessages((items) => {
56
- const next = [...items];
57
- const last = next[next.length - 1] || { role: "assistant" };
58
- next[next.length - 1] = { ...last, role: "assistant", content: fullText };
59
- return next;
60
- });
61
- },
62
- onTrace: setTrace,
63
- },
64
- );
65
- if (result.error) {
66
- setMessages((items) => {
67
- const next = [...items];
68
- next[next.length - 1] = { role: "assistant", content: `Unavailable: ${result.error}` };
69
- return next;
70
- });
71
- }
72
- } finally {
73
- setStreaming(false);
74
- setImageData(null);
75
- await qc.invalidateQueries({ queryKey: ["chatHistory"] });
76
- }
77
- };
78
-
79
- const deleteMutation = useMutation({
80
- mutationFn: (id: string) => latticeApi.deleteConversation(id),
81
- onSuccess: () => qc.invalidateQueries({ queryKey: ["chatHistory"] }),
82
- });
83
-
84
- return (
85
- <div className="space-y-5">
86
- <header className="page-hero">
87
- <div className="page-kicker"><MessageSquare className="h-4 w-4" /> Ask</div>
88
- <h1 className="page-title">Think out loud with Lattice.</h1>
89
- <p className="page-copy">Ask in plain language, attach an image, and let your private memory shape the answer.</p>
90
- </header>
91
- <div className="grid min-h-[calc(100vh-13rem)] gap-4 xl:grid-cols-[18rem_minmax(0,1fr)_22rem]">
92
- <Card className="overflow-hidden">
93
- <CardHeader>
94
- <CardTitle className="flex items-center gap-2"><MessageSquare className="h-4 w-4" /> Conversations</CardTitle>
95
- <CardDescription>Pick up where you left off.</CardDescription>
96
- </CardHeader>
97
- <CardContent className="soft-scrollbar max-h-[42rem] space-y-2 overflow-auto">
98
- <SourceBadge result={history.data} />
99
- {asArray<Record<string, unknown>>(history.data?.data).length ? asArray<Record<string, unknown>>(history.data?.data).map((item) => (
100
- <button
101
- key={String(item.id)}
102
- onClick={() => setConversationId(String(item.id))}
103
- className="block w-full rounded-lg border border-border bg-background/55 p-3 text-left text-sm transition hover:bg-muted"
104
- >
105
- <div className="font-medium">{String(item.title || item.id)}</div>
106
- <div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
107
- <span>{String(item.updated_at || "")}</span>
108
- <Trash2
109
- className="h-3.5 w-3.5"
110
- onClick={(e) => {
111
- e.stopPropagation();
112
- deleteMutation.mutate(String(item.id));
113
- }}
114
- />
115
- </div>
116
- </button>
117
- )) : <EmptyState title="No conversations" detail="Start a new exchange or sign in to load history." />}
118
- </CardContent>
119
- </Card>
120
-
121
- <section className="premium-surface flex min-h-[42rem] flex-col overflow-hidden rounded-lg">
122
- <div className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-5">
123
- <div>
124
- <h2 className="text-xl font-semibold">New conversation</h2>
125
- <p className="text-sm text-muted-foreground">Lattice answers only when a real model is available.</p>
126
- </div>
127
- <div className="flex flex-wrap items-center gap-2">
128
- <Badge variant="muted">{String((models.data?.data as Record<string, unknown>)?.current || "no model loaded")}</Badge>
129
- <SourceBadge result={models.data} />
130
- </div>
131
- </div>
132
- <div className="soft-scrollbar flex-1 space-y-4 overflow-auto p-5">
133
- {messages.length ? messages.map((msg, index) => (
134
- <div key={index} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
135
- <div className={`max-w-[78%] rounded-lg border p-4 text-sm leading-6 ${msg.role === "user" ? "border-primary/30 bg-primary/15" : "border-border bg-background/68"}`}>
136
- <div className="mb-1 text-xs uppercase text-muted-foreground">{msg.role || "message"}</div>
137
- <div className="whitespace-pre-wrap">{msg.content}</div>
138
- </div>
139
- </div>
140
- )) : (
141
- <div className="grid min-h-full place-items-center">
142
- <EmptyState
143
- title="What should we think through?"
144
- detail="Ask about a document, a project, a memory, or a question you want Lattice to connect across your workspace."
145
- />
146
- </div>
147
- )}
148
- </div>
149
- <div className="border-t border-border bg-background/28 p-5">
150
- {imageData ? <Badge variant="success" className="mb-2">image attached</Badge> : null}
151
- <Textarea
152
- value={draft}
153
- onChange={(e) => setDraft(e.target.value)}
154
- onKeyDown={(e) => {
155
- if (e.key === "Enter" && !e.shiftKey) {
156
- e.preventDefault();
157
- void send();
158
- }
159
- }}
160
- placeholder="Ask anything about your work..."
161
- />
162
- <div className="mt-2 flex flex-wrap items-center justify-between gap-2">
163
- <label className="inline-flex h-9 cursor-pointer items-center gap-2 rounded-md border border-border px-3 text-sm hover:bg-muted">
164
- <ImagePlus className="h-4 w-4" />
165
- Image
166
- <input
167
- type="file"
168
- accept="image/*"
169
- className="sr-only"
170
- onChange={async (e) => {
171
- const file = e.target.files?.[0];
172
- if (file) setImageData(await fileToDataUrl(file));
173
- }}
174
- />
175
- </label>
176
- <Button disabled={!draft.trim() || streaming} onClick={() => void send()}>
177
- <Send className="h-4 w-4" /> Send
178
- </Button>
179
- </div>
180
- </div>
181
- </section>
182
-
183
- <aside className="space-y-4">
184
- <ContextPreview question={draft || [...messages].reverse().find((m: Msg) => m.role === "user")?.content || ""} trace={trace} />
185
- </aside>
186
- </div>
187
- </div>
188
- );
189
- }
190
-
191
- function ContextPreview({ question, trace }: { question: string; trace: unknown }) {
192
- const mode = useAppStore((state) => state.mode);
193
- const hybrid = useQuery({
194
- queryKey: ["askHybrid", question],
195
- queryFn: () => latticeApi.hybridSearch(question),
196
- enabled: question.trim().length > 2,
197
- });
198
- const graph = useQuery({ queryKey: ["graph"], queryFn: latticeApi.graph });
199
- if (mode === "basic") {
200
- return (
201
- <>
202
- <DataPanel title="Relevant memories" result={hybrid.data}>
203
- {(data) => <EntityList items={(data as Record<string, unknown>).matches || data} titleKey="title" metaKey="type" limit={5} />}
204
- </DataPanel>
205
- <Card>
206
- <CardHeader>
207
- <CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Sources</CardTitle>
208
- <CardDescription>Lattice shows supporting memories when an answer uses them.</CardDescription>
209
- </CardHeader>
210
- <CardContent>{trace ? <StructuredView value={trace} /> : <EmptyState title="Ask to see sources" />}</CardContent>
211
- </Card>
212
- </>
213
- );
214
- }
215
- return (
216
- <>
217
- <DataPanel title="Memory preview" result={hybrid.data}>
218
- {(data) => <EntityList items={(data as Record<string, unknown>).matches || data} titleKey="title" metaKey="type" limit={5} />}
219
- </DataPanel>
220
- <DataPanel title="Graph context" result={graph.data}>
221
- {(data) => <EntityList items={(data as Record<string, unknown>).nodes} titleKey="title" metaKey="type" limit={5} />}
222
- </DataPanel>
223
- <Card>
224
- <CardHeader>
225
- <CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Why this context</CardTitle>
226
- <CardDescription>Signals Lattice used to choose supporting memories.</CardDescription>
227
- </CardHeader>
228
- <CardContent>{trace ? <StructuredView value={trace} /> : <EmptyState title="Ask to see context" />}</CardContent>
229
- </Card>
230
- </>
231
- );
4
+ return <BrainConversation />;
232
5
  }
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import cytoscape, { Core, ElementDefinition } from "cytoscape";
4
4
  import { BrainCircuit, DatabaseBackup, Filter, Focus, Layers3, LocateFixed, Search, Sparkles } from "lucide-react";
5
5
  import { latticeApi } from "@/api/client";
6
+ import { BrainConversation } from "@/components/BrainConversation";
6
7
  import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, LoadingPanel, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
7
8
  import { Badge } from "@/components/ui/badge";
8
9
  import { Button } from "@/components/ui/button";
@@ -12,7 +13,7 @@ import { Textarea } from "@/components/ui/textarea";
12
13
  import { useAppStore } from "@/store/appStore";
13
14
  import { asArray, fmtNumber, pct, shortId, titleize } from "@/lib/utils";
14
15
 
15
- type BrainTab = "overview" | "graph" | "search" | "memory" | "provenance" | "portability";
16
+ type BrainTab = "conversation" | "memory" | "knowledge" | "relationships" | "graph" | "portability";
16
17
  type LabelMode = "important" | "all" | "off";
17
18
 
18
19
  type GraphNode = {
@@ -61,12 +62,12 @@ type ExplorerModel = ParsedGraph & {
61
62
  };
62
63
 
63
64
  const tabs: Array<{ id: BrainTab; label: string }> = [
64
- { id: "overview", label: "Today" },
65
- { id: "graph", label: "Map" },
66
- { id: "search", label: "Search" },
67
- { id: "memory", label: "Memory" },
68
- { id: "provenance", label: "Sources" },
69
- { id: "portability", label: "Portability" },
65
+ { id: "conversation", label: "Brain" },
66
+ { id: "memory", label: "Memories" },
67
+ { id: "knowledge", label: "Knowledge" },
68
+ { id: "relationships", label: "Relationships" },
69
+ { id: "graph", label: "Graph" },
70
+ { id: "portability", label: "Care" },
70
71
  ];
71
72
 
72
73
  const groupDefinitions = [
@@ -413,9 +414,10 @@ function CytoscapeGraph({
413
414
 
414
415
  export function BrainPage({ initialTab }: { initialTab?: string }) {
415
416
  const mode = useAppStore((state) => state.mode);
416
- const [tab, setTab] = React.useState<BrainTab>((initialTab as BrainTab) || "overview");
417
+ const normalizedInitialTab = normalizeBrainTab(initialTab);
418
+ const [tab, setTab] = React.useState<BrainTab>(normalizedInitialTab);
417
419
  React.useEffect(() => {
418
- if (initialTab && tabs.some((item) => item.id === initialTab)) setTab(initialTab as BrainTab);
420
+ setTab(normalizeBrainTab(initialTab));
419
421
  }, [initialTab]);
420
422
  const graph = useQuery({ queryKey: ["graph"], queryFn: latticeApi.graph });
421
423
  const stats = useQuery({ queryKey: ["graphStats"], queryFn: latticeApi.graphStats });
@@ -426,31 +428,32 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
426
428
 
427
429
  return (
428
430
  <div className="space-y-5">
429
- <header className="page-hero grid gap-4 xl:grid-cols-[1.4fr_0.6fr]">
430
- <div>
431
- <div className="page-kicker"><BrainCircuit className="h-4 w-4" /> Home</div>
432
- <h1 className="page-title">Start from the shape of your work.</h1>
433
- <p className="page-copy">
434
- Lattice turns what you add into a living memory map, then keeps every answer tied back to its source.
435
- </p>
436
- </div>
437
- <div className="rounded-lg border border-border bg-background/58 p-4">
438
- <div className="text-xs uppercase text-muted-foreground">Source coverage</div>
439
- <div className="mt-2 text-3xl font-semibold">{pct((coverage.data?.data as Record<string, unknown>)?.coverage_ratio)}</div>
440
- <div className="mt-2 text-sm text-muted-foreground">{coverage.data?.ok ? "Sources are linked to graph records." : "Checking source coverage."}</div>
441
- </div>
442
- </header>
431
+ {tab === "conversation" ? null : (
432
+ <header className="brain-layer-header">
433
+ <div>
434
+ <div className="page-kicker"><BrainCircuit className="h-4 w-4" /> {tabLabel(tab)}</div>
435
+ <h1>{tabHeadline(tab)}</h1>
436
+ </div>
437
+ <div className="brain-layer-meter">
438
+ <span>Source coverage</span>
439
+ <strong>{pct((coverage.data?.data as Record<string, unknown>)?.coverage_ratio)}</strong>
440
+ </div>
441
+ </header>
442
+ )}
443
443
  <Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as BrainTab)} />
444
444
 
445
- {tab === "overview" ? (
445
+ {tab === "conversation" ? <BrainConversation /> : null}
446
+ {tab === "memory" ? <MemoryPanel /> : null}
447
+ {tab === "knowledge" ? <HybridSearch /> : null}
448
+ {tab === "relationships" ? (
446
449
  <div className="grid gap-4 xl:grid-cols-2">
447
- <DataPanel title="Brain status" result={stats.data}>
450
+ <DataPanel title="Brain activity" result={stats.data}>
448
451
  {(data) => <GraphStatus data={data as Record<string, unknown>} />}
449
452
  </DataPanel>
450
- <DataPanel title="Retrieval index" result={index.data}>
453
+ <DataPanel title="Retrieval rhythm" result={index.data}>
451
454
  {(data) => <RetrievalStatus data={data as Record<string, unknown>} />}
452
455
  </DataPanel>
453
- <DataPanel title="Memory tiers" result={memory.data}>
456
+ <DataPanel title="Memory layers" result={memory.data}>
454
457
  {(data) => <MemoryStatus data={data as Record<string, unknown>} />}
455
458
  </DataPanel>
456
459
  <DataPanel title="Recent sources" result={provenance.data}>
@@ -460,21 +463,37 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
460
463
  ) : null}
461
464
 
462
465
  {tab === "graph" ? (
463
- graph.isLoading ? <LoadingPanel title="Knowledge graph" /> : (
464
- <DataPanel title="Brain map" description={mode === "basic" ? "Search, focus, and filter the ideas Lattice has learned from your workspace." : "Explore relationships, sources, and graph structure."} result={graph.data}>
466
+ graph.isLoading ? <LoadingPanel title="Deep graph" /> : (
467
+ <DataPanel title="Advanced relationship graph" description={mode === "basic" ? "Open the deepest layer when you want to inspect the underlying relationships." : "Explore relationships, sources, and graph structure."} result={graph.data}>
465
468
  {(data) => <DigitalBrainExplorer data={data} />}
466
469
  </DataPanel>
467
470
  )
468
471
  ) : null}
469
-
470
- {tab === "search" ? <HybridSearch /> : null}
471
- {tab === "memory" ? <MemoryPanel /> : null}
472
- {tab === "provenance" ? <ProvenancePanel /> : null}
473
472
  {tab === "portability" ? <PortabilityPanel /> : null}
474
473
  </div>
475
474
  );
476
475
  }
477
476
 
477
+ function normalizeBrainTab(tab?: string): BrainTab {
478
+ if (tab === "overview" || tab === "chat" || tab === "ask") return "conversation";
479
+ if (tab === "search") return "knowledge";
480
+ if (tab === "provenance" || tab === "sources") return "relationships";
481
+ return tabs.some((item) => item.id === tab) ? tab as BrainTab : "conversation";
482
+ }
483
+
484
+ function tabLabel(tab: BrainTab) {
485
+ return tabs.find((item) => item.id === tab)?.label || "Brain";
486
+ }
487
+
488
+ function tabHeadline(tab: BrainTab) {
489
+ if (tab === "memory") return "Memories, before mechanics.";
490
+ if (tab === "knowledge") return "Knowledge, gathered into recall.";
491
+ if (tab === "relationships") return "Relationships, when you need the why.";
492
+ if (tab === "graph") return "The graph, intentionally opened.";
493
+ if (tab === "portability") return "Care for the Brain.";
494
+ return "Talk to your Brain.";
495
+ }
496
+
478
497
  function GraphStatus({ data }: { data: Record<string, unknown> }) {
479
498
  const mode = useAppStore((state) => state.mode);
480
499
  const nodeTypes = Object.keys((data.nodes as Record<string, unknown>) || {});
@@ -482,16 +501,16 @@ function GraphStatus({ data }: { data: Record<string, unknown> }) {
482
501
  return (
483
502
  <div className="space-y-3">
484
503
  <StatGrid stats={[
485
- { label: "Nodes", value: data.total_nodes ?? nodeTypes.reduce((sum, key) => sum + Number(((data.nodes as Record<string, unknown>) || {})[key] || 0), 0) },
486
- { label: "Edges", value: data.total_edges ?? edgeTypes.reduce((sum, key) => sum + Number(((data.edges as Record<string, unknown>) || {})[key] || 0), 0) },
487
- { label: "Node types", value: nodeTypes.length },
488
- { label: "Edge types", value: edgeTypes.length },
504
+ { label: "Memories", value: data.total_nodes ?? nodeTypes.reduce((sum, key) => sum + Number(((data.nodes as Record<string, unknown>) || {})[key] || 0), 0) },
505
+ { label: "Links", value: data.total_edges ?? edgeTypes.reduce((sum, key) => sum + Number(((data.edges as Record<string, unknown>) || {})[key] || 0), 0) },
506
+ { label: "Memory kinds", value: nodeTypes.length },
507
+ { label: "Link kinds", value: edgeTypes.length },
489
508
  ]} />
490
509
  {mode === "basic" ? (
491
510
  <div className="flex flex-wrap gap-1">
492
511
  {[...nodeTypes, ...edgeTypes].slice(0, 10).map((item) => <Badge key={item} variant="muted">{titleize(item)}</Badge>)}
493
512
  </div>
494
- ) : <StructuredView value={{ node_types: nodeTypes, edge_types: edgeTypes }} />}
513
+ ) : <StructuredView value={{ memory_kinds: nodeTypes, link_kinds: edgeTypes }} />}
495
514
  </div>
496
515
  );
497
516
  }
@@ -558,8 +577,8 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
558
577
  if (!parsed.nodes.length) {
559
578
  return (
560
579
  <EmptyState
561
- title="No graph records yet"
562
- detail="Capture a document, note, or local folder to create connected ideas with sources."
580
+ title="No relationship records yet"
581
+ detail="Capture a document, note, or local folder to create connected memories with sources."
563
582
  />
564
583
  );
565
584
  }
@@ -585,7 +604,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
585
604
  <Card>
586
605
  <CardHeader className="flex-row items-start justify-between gap-3">
587
606
  <div>
588
- <CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" /> Knowledge map</CardTitle>
607
+ <CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" /> Deep graph</CardTitle>
589
608
  <CardDescription>
590
609
  Showing {fmtNumber(model.visibleNodes.length)} ideas and {fmtNumber(model.visibleEdges.length)} connections from {fmtNumber(model.totalNodes)} saved items.
591
610
  </CardDescription>
@@ -605,7 +624,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
605
624
  value={minImportance}
606
625
  onChange={(event) => setMinImportance(Number(event.target.value))}
607
626
  className="w-44"
608
- aria-label="Minimum graph importance"
627
+ aria-label="Minimum relationship importance"
609
628
  />
610
629
  <Badge variant="muted">{Math.round(minImportance * 100)}%+</Badge>
611
630
  {selectedId ? <Button variant="outline" size="sm" onClick={() => setSelectedId(null)}>Clear focus</Button> : null}
@@ -664,7 +683,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
664
683
  <div className="text-lg font-semibold">{selectedGroup.label}</div>
665
684
  <Button variant="outline" onClick={() => toggleGroup(selectedGroup.id)}>Expand group</Button>
666
685
  </div>
667
- ) : <EmptyState title="No node selected" detail="Select a node or collapsed group in the graph." />}
686
+ ) : <EmptyState title="Nothing selected" detail="Select an item or collapsed group in the graph." />}
668
687
  </CardContent>
669
688
  </Card>
670
689
  <Card>
@@ -710,7 +729,7 @@ function HybridSearch() {
710
729
  </CardHeader>
711
730
  <CardContent className="space-y-3">
712
731
  <div className="flex flex-col gap-2 sm:flex-row">
713
- <Input placeholder="Search memories, graph nodes, and indexed documents" value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && search.mutate()} />
732
+ <Input placeholder="Search memories, indexed documents, and relationships" value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && search.mutate()} />
714
733
  <Button onClick={() => search.mutate()} disabled={!query.trim() || search.isPending}>Search</Button>
715
734
  </div>
716
735
  {search.data ? (
@@ -792,10 +811,10 @@ function PortabilityPanel() {
792
811
  </CardHeader>
793
812
  <CardContent className="space-y-3">
794
813
  <div className="flex flex-wrap gap-2">
795
- <ActionButton label="Export brain map" action={() => latticeApi.graphExport()} />
814
+ <ActionButton label="Export Brain" action={() => latticeApi.graphExport()} />
796
815
  <ActionButton label="Create backup" action={() => latticeApi.graphBackup()} />
797
816
  </div>
798
- <Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported brain map to preview import" />
817
+ <Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported Brain artifact to preview import" />
799
818
  <Button
800
819
  variant="outline"
801
820
  disabled={!artifact.trim() || importMutation.isPending}
@@ -816,9 +835,9 @@ function PortabilityStatus({ data }: { data: Record<string, unknown> }) {
816
835
  return (
817
836
  <div className="space-y-3">
818
837
  <StatGrid stats={[
819
- { label: "Graph version", value: data.graph_schema_version || data.schema_version || "reported" },
820
- { label: "Nodes", value: (stats.total_nodes as number) || Object.values((stats.nodes as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
821
- { label: "Edges", value: (stats.total_edges as number) || Object.values((stats.edges as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
838
+ { label: "Brain format", value: data.graph_schema_version || data.schema_version || "reported" },
839
+ { label: "Memories", value: (stats.total_nodes as number) || Object.values((stats.nodes as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
840
+ { label: "Links", value: (stats.total_edges as number) || Object.values((stats.edges as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
822
841
  { label: "Storage", value: storage.engine || "reported" },
823
842
  ]} />
824
843
  <StructuredView value={data} />
@@ -1,35 +1,31 @@
1
1
  import {
2
- Activity,
3
2
  Brain,
4
3
  Database,
5
4
  FolderInput,
6
5
  Library,
7
- MessageSquare,
8
- Network,
9
6
  Settings,
10
- Shield,
11
7
  Workflow,
12
- Zap,
13
8
  } from "lucide-react";
14
9
 
15
- export type PrimaryRoute = "brain" | "ask" | "capture" | "act" | "library" | "system";
10
+ export type PrimaryRoute = "brain" | "memory" | "capture" | "act" | "library" | "system";
16
11
 
17
12
  export const primaryRoutes = [
18
- { id: "brain", label: "Home", icon: Brain, description: "The living map of what Lattice knows" },
19
- { id: "ask", label: "Ask", icon: MessageSquare, description: "Think with remembered context" },
20
- { id: "capture", label: "Add", icon: FolderInput, description: "Bring in files, folders, and pages" },
21
- { id: "act", label: "Automate", icon: Workflow, description: "Turn goals into supervised runs" },
22
- { id: "library", label: "Library", icon: Library, description: "Choose models, skills, and tools" },
23
- { id: "system", label: "Care", icon: Settings, description: "Keep your brain safe and portable" },
13
+ { id: "brain", label: "Brain", icon: Brain, description: "Talk with your living Brain" },
14
+ { id: "memory", label: "Memory", icon: Database, description: "Recall what your Brain remembers" },
15
+ { id: "capture", label: "Files", icon: FolderInput, description: "Bring in files, folders, and pages" },
16
+ { id: "act", label: "Automations", icon: Workflow, description: "Turn goals into supervised runs" },
17
+ { id: "library", label: "Models", icon: Library, description: "Choose the local model powering your Brain" },
18
+ { id: "system", label: "Settings", icon: Settings, description: "Keep your Brain safe and portable" },
24
19
  ] as const;
25
20
 
26
21
  export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string }> = {
27
- home: { primary: "brain", tab: "overview" },
22
+ home: { primary: "brain", tab: "conversation" },
28
23
  onboarding: { primary: "system", tab: "account" },
29
24
  "knowledge-graph": { primary: "brain", tab: "graph" },
30
- "hybrid-search": { primary: "brain", tab: "search" },
31
- memory: { primary: "brain", tab: "memory" },
32
- chat: { primary: "ask", tab: "chat" },
25
+ "hybrid-search": { primary: "brain", tab: "knowledge" },
26
+ memory: { primary: "memory", tab: "memory" },
27
+ ask: { primary: "brain", tab: "conversation" },
28
+ chat: { primary: "brain", tab: "conversation" },
33
29
  files: { primary: "capture", tab: "files" },
34
30
  pipeline: { primary: "capture", tab: "pipeline" },
35
31
  "my-computer": { primary: "capture", tab: "local" },
@@ -58,19 +54,12 @@ export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string
58
54
  };
59
55
 
60
56
  export const commandRoutes = [
61
- { key: "brain", label: "Home", icon: Brain },
62
- { key: "onboarding", label: "First 10 Minutes", icon: Settings },
63
- { key: "knowledge-graph", label: "Memory Map", icon: Network },
64
- { key: "hybrid-search", label: "Search Everything", icon: Zap },
57
+ { key: "brain", label: "Brain", icon: Brain },
65
58
  { key: "memory", label: "Memory", icon: Database },
66
- { key: "chat", label: "Ask", icon: MessageSquare },
67
- { key: "files", label: "Add Files", icon: FolderInput },
68
- { key: "agents", label: "Start a Run", icon: Workflow },
59
+ { key: "files", label: "Files", icon: FolderInput },
69
60
  { key: "workflows", label: "Automations", icon: Workflow },
70
61
  { key: "models", label: "Models", icon: Library },
71
- { key: "network", label: "Trusted Devices", icon: Network },
72
- { key: "activity", label: "Activity", icon: Activity },
73
- { key: "admin/security", label: "Security", icon: Shield },
62
+ { key: "settings", label: "Settings", icon: Settings },
74
63
  ];
75
64
 
76
65
  export function parseHash() {