ltcai 6.7.0 → 7.0.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.
Files changed (41) hide show
  1. package/README.md +29 -31
  2. package/docs/CHANGELOG.md +25 -0
  3. package/frontend/src/api/client.ts +5 -0
  4. package/frontend/src/features/brain/BrainConversation.tsx +163 -1
  5. package/frontend/src/features/brain/BrainHome.tsx +108 -1
  6. package/frontend/src/features/brain/types.ts +13 -0
  7. package/frontend/src/i18n.ts +70 -0
  8. package/frontend/src/styles.css +212 -0
  9. package/lattice_brain/__init__.py +1 -1
  10. package/lattice_brain/runtime/multi_agent.py +1 -1
  11. package/latticeai/__init__.py +1 -1
  12. package/latticeai/core/marketplace.py +1 -1
  13. package/latticeai/core/workspace_os.py +1 -1
  14. package/package.json +1 -1
  15. package/scripts/brain_quality_eval.py +118 -0
  16. package/src-tauri/Cargo.lock +1 -1
  17. package/src-tauri/Cargo.toml +1 -1
  18. package/src-tauri/tauri.conf.json +1 -1
  19. package/static/app/asset-manifest.json +28 -28
  20. package/static/app/assets/{Act-DGN37eR4.js → Act-DhLaHnrT.js} +2 -2
  21. package/static/app/assets/{Act-DGN37eR4.js.map → Act-DhLaHnrT.js.map} +1 -1
  22. package/static/app/assets/{Brain-HDcIwPHW.js → Brain-DS7NULyY.js} +3 -3
  23. package/static/app/assets/{Brain-HDcIwPHW.js.map → Brain-DS7NULyY.js.map} +1 -1
  24. package/static/app/assets/Capture-CAKouj52.js +2 -0
  25. package/static/app/assets/Capture-CAKouj52.js.map +1 -0
  26. package/static/app/assets/{Library-BbagRFyd.js → Library-B_hW4AwR.js} +2 -2
  27. package/static/app/assets/{Library-BbagRFyd.js.map → Library-B_hW4AwR.js.map} +1 -1
  28. package/static/app/assets/{System-C6pd3Mp_.js → System-9OAoXWPz.js} +2 -2
  29. package/static/app/assets/{System-C6pd3Mp_.js.map → System-9OAoXWPz.js.map} +1 -1
  30. package/static/app/assets/index-C-7aHabu.js +17 -0
  31. package/static/app/assets/index-C-7aHabu.js.map +1 -0
  32. package/static/app/assets/{index-ztppMg0c.css → index-DKFpn5Kn.css} +1 -1
  33. package/static/app/assets/{primitives-Bcm5lZRu.js → primitives-DcO2H3yh.js} +2 -2
  34. package/static/app/assets/{primitives-Bcm5lZRu.js.map → primitives-DcO2H3yh.js.map} +1 -1
  35. package/static/app/assets/{textarea-C2PLu6_O.js → textarea-DR2Tyy_6.js} +2 -2
  36. package/static/app/assets/{textarea-C2PLu6_O.js.map → textarea-DR2Tyy_6.js.map} +1 -1
  37. package/static/app/index.html +2 -2
  38. package/static/app/assets/Capture--eZfb_Ex.js +0 -2
  39. package/static/app/assets/Capture--eZfb_Ex.js.map +0 -1
  40. package/static/app/assets/index-C950aES_.js +0 -17
  41. package/static/app/assets/index-C950aES_.js.map +0 -1
package/README.md CHANGED
@@ -69,42 +69,42 @@ You need Lattice AI when:
69
69
  Choose the owner of the Brain. The profile is not a SaaS account by default; it
70
70
  is the local identity for the knowledge you keep.
71
71
 
72
- ![Login](output/release/v6.7.0/screenshots/01-login.png)
72
+ ![Login](output/release/v7.0.0/screenshots/01-login.png)
73
73
 
74
74
  ### 2. Environment Analysis
75
75
 
76
76
  See what kind of local AI experience this computer can support before choosing a
77
77
  model.
78
78
 
79
- ![Environment Analysis](output/release/v6.7.0/screenshots/02-environment-analysis.png)
79
+ ![Environment Analysis](output/release/v7.0.0/screenshots/02-environment-analysis.png)
80
80
 
81
81
  ### 3. Recommended Models
82
82
 
83
83
  Start with a short list: safest recommendation, faster model, stronger model.
84
84
  Advanced details stay available without overwhelming first-time users.
85
85
 
86
- ![Recommended Models](output/release/v6.7.0/screenshots/03-recommended-models.png)
86
+ ![Recommended Models](output/release/v7.0.0/screenshots/03-recommended-models.png)
87
87
 
88
88
  ### 4. Install And Load
89
89
 
90
90
  Download and load only after consent. Lattice explains model size, local
91
91
  execution, and network use before work starts.
92
92
 
93
- ![Install and Load](output/release/v6.7.0/screenshots/04-install-load-progress.png)
93
+ ![Install and Load](output/release/v7.0.0/screenshots/04-install-load-progress.png)
94
94
 
95
95
  ### 5. Brain Chat
96
96
 
97
97
  Talk normally. Useful decisions and context become memory, then appear later as
98
98
  topics, relationships, and graph structure.
99
99
 
100
- ![Brain Chat Home](output/release/v6.7.0/screenshots/05-brain-chat-home.png)
100
+ ![Brain Chat Home](output/release/v7.0.0/screenshots/05-brain-chat-home.png)
101
101
 
102
102
  ### 6. Review Center
103
103
 
104
104
  Automation results are staged for review before they become durable decisions.
105
105
  Snooze, unsnooze, run now, approve, and dismiss actions stay explicit.
106
106
 
107
- ![Review Center](output/release/v6.7.0/screenshots/13-review-center.png)
107
+ ![Review Center](output/release/v7.0.0/screenshots/13-review-center.png)
108
108
 
109
109
  ## Brain Depths
110
110
 
@@ -120,10 +120,10 @@ The user travels inward from everyday memory to deeper structure:
120
120
 
121
121
  Walkthrough:
122
122
 
123
- ![v6.7.0 Living Brain walkthrough](output/release/v6.7.0/gifs/v6.7.0-living-brain-walkthrough.gif)
123
+ ![v7.0.0 Living Brain walkthrough](output/release/v7.0.0/gifs/v7.0.0-living-brain-walkthrough.gif)
124
124
 
125
125
  Screenshot index and capture notes:
126
- [output/release/v6.7.0/SCREENSHOT_INDEX.md](output/release/v6.7.0/SCREENSHOT_INDEX.md)
126
+ [output/release/v7.0.0/SCREENSHOT_INDEX.md](output/release/v7.0.0/SCREENSHOT_INDEX.md)
127
127
 
128
128
  ## Install
129
129
 
@@ -200,29 +200,26 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
200
200
 
201
201
  ## Current Release
202
202
 
203
- The current release is **6.7.0 Brain IA Cleanup**:
204
-
205
- - Brain Home now surfaces direct actions for adding documents, searching the
206
- graph, changing models, and opening settings.
207
- - The rich Capture, Brain explorer, Library, System, and Act pages are mounted
208
- inside a shared Brain shell instead of existing only as hard-to-discover
209
- compatibility routes.
210
- - `routes.ts` now separates product shell routes from legacy compatibility
211
- aliases, so visible IA and old entry URLs can evolve independently.
212
- - Rich pages are code-split with lazy route loading. The first Brain Home bundle
213
- stays below the previous warning threshold while graph/search/model/settings
214
- surfaces load when opened.
215
- - The release keeps the 6.6.0 backend-owned Brain proof loop and
216
- workspace-scoped ingestion intact while making the product actions more
217
- obvious in the first five minutes.
218
-
219
- Expected artifacts for 6.7.0 release must use exact filenames:
220
-
221
- - `dist/ltcai-6.7.0-py3-none-any.whl`
222
- - `dist/ltcai-6.7.0.tar.gz`
223
- - `ltcai-6.7.0.tgz`
224
- - `dist/ltcai-6.7.0.vsix`
225
- - `src-tauri/target/release/bundle/dmg/Lattice AI_6.7.0_aarch64.dmg`
203
+ The current release is **7.0.0 Brain Productization Loop**:
204
+
205
+ - Brain Home starts with ingestion: files, folders, notes, and web pages can be
206
+ added directly from the first screen.
207
+ - Every Brain answer can show memory proof and source citations instead of
208
+ asking users to trust an ungrounded chat bubble.
209
+ - The model-continuity demo lets users recheck the same Brain evidence after
210
+ changing models.
211
+ - The first-run loop is optimized for five minutes: add source, ask, see proof,
212
+ then switch or deepen.
213
+ - CI now includes a deterministic recall/KG quality eval so the Brain proof
214
+ promise is guarded before release.
215
+
216
+ Expected artifacts for 7.0.0 release must use exact filenames:
217
+
218
+ - `dist/ltcai-7.0.0-py3-none-any.whl`
219
+ - `dist/ltcai-7.0.0.tar.gz`
220
+ - `ltcai-7.0.0.tgz`
221
+ - `dist/ltcai-7.0.0.vsix`
222
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_7.0.0_aarch64.dmg`
226
223
 
227
224
  Do not upload `dist/*`. Package registry publishing remains owner-run.
228
225
 
@@ -241,6 +238,7 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
241
238
 
242
239
  | Version | Theme |
243
240
  | --- | --- |
241
+ | 7.0.0 | Brain Productization Loop: first-screen ingestion for files/folders/notes/web, answer-level memory proof and source citations, model-continuity demo flow, five-minute first-run loop, and recall/KG quality eval in CI |
244
242
  | 6.7.0 | Brain IA Cleanup: reachable rich pages, separated product routes vs compatibility aliases, shared Brain shell navigation, and lazy-loaded rich pages |
245
243
  | 6.6.0 | Brain Proof Runtime: backend-owned Brain proof API, model-continuity wiring, first-screen proof that saved context can be recalled across model changes, and direct Brain Home document upload |
246
244
  | 6.5.0 | Brain Experience Readiness: Brain readiness signal, depth progress rail, source-aware memory-save feedback, and visual coverage for the first-memory loop |
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ The top entry is either the current unreleased main-branch work or the current
4
4
  release line. Older entries are historical and may describe behavior as it
5
5
  existed at that release.
6
6
 
7
+ ## [7.0.0] - 2026-06-18
8
+
9
+ > Brain Productization Loop. Turns the Brain proof work into a first-five-minute
10
+ > product flow: add sources, ask a question, see proof/citations, and verify the
11
+ > same Brain evidence after switching models.
12
+
13
+ ### Added
14
+ - Brain Home ingestion panel for files, local folder paths, notes, and web URLs,
15
+ all backed by existing workspace-scoped ingestion routes.
16
+ - Answer-level Memory proof and source citation cards rendered under assistant
17
+ responses after Brain proof refreshes for the user's query.
18
+ - Model-continuity demo strip that lets the user recheck the same Brain
19
+ evidence and jump to model switching from the Brain flow.
20
+ - Deterministic `scripts/brain_quality_eval.py` recall/KG quality gate, wired
21
+ into CI after the unit suite.
22
+ - Visual mock coverage for Brain proof, document upload, note ingest, folder
23
+ indexing, and web URL ingestion endpoints.
24
+
25
+ ### Changed
26
+ - Brain Home is now ingestion-first instead of chat-first: first screen action
27
+ labels are files/folders/notes/web, with deeper graph/model/settings still
28
+ reachable from the shell.
29
+ - Package/runtime/static metadata is synchronized to 7.0.0; package publish and
30
+ deployment remain owner-run only.
31
+
7
32
  ## [6.7.0] - 2026-06-18
8
33
 
9
34
  > Brain IA Cleanup. Makes rich product pages reachable from Brain Home,
@@ -444,6 +444,11 @@ export const latticeApi = {
444
444
  return res;
445
445
  },
446
446
  browserReadUrl: (url: string) => post("/api/browser/read-url", { url }, {}),
447
+ ingestNote: (content: string, title = "Brain note") => post(
448
+ "/knowledge-graph/ingest",
449
+ { type: "note", content, title, source: "brain_home" },
450
+ {},
451
+ ),
447
452
  memoryManager: () => get("/api/memory/manager", { sources: [], tiers: [], usage: {} }),
448
453
  memoryBrainQuality: () => get("/api/memory/brain-quality", {}),
449
454
  memoryBrainProof: (query = "", limit = 3) => get("/api/memory/brain-proof", { proofs: {}, recall: { items: [] }, model_continuity: {}, claims: {} }, { q: query, limit }),
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { Cpu, DatabaseZap, FileUp, Search, Settings, ShieldCheck } from "lucide-react";
2
+ import { Cpu, DatabaseZap, FileText, FileUp, FolderPlus, Globe2, Repeat2, Search, Settings, ShieldCheck } from "lucide-react";
3
3
  import { LanguageSwitcher } from "@/components/LanguageSwitcher";
4
4
  import { t, type Language } from "@/i18n";
5
5
  import type { BrainDepth, BrainProof, BrainReadiness, KnowledgeConcept, MemoryFragment, Message } from "./types";
@@ -27,6 +27,10 @@ export function BrainConversation({
27
27
  onDraftChange,
28
28
  onImageDataChange,
29
29
  onUploadDocument,
30
+ onConnectFolder,
31
+ onIngestNote,
32
+ onIngestWeb,
33
+ onVerifyModelContinuity,
30
34
  onSend,
31
35
  }: {
32
36
  language: Language;
@@ -48,6 +52,10 @@ export function BrainConversation({
48
52
  onDraftChange: (value: string) => void;
49
53
  onImageDataChange: (value: string | null) => void;
50
54
  onUploadDocument: (file: File) => void;
55
+ onConnectFolder: (path: string) => void;
56
+ onIngestNote: (note: string) => void;
57
+ onIngestWeb: (url: string) => void;
58
+ onVerifyModelContinuity: () => void;
51
59
  onSend: () => void;
52
60
  }) {
53
61
  return (
@@ -89,6 +97,14 @@ export function BrainConversation({
89
97
  </div>
90
98
 
91
99
  <div ref={streamRef} className="brain-stream">
100
+ <BrainIngestionPanel
101
+ language={language}
102
+ uploadingDocument={uploadingDocument}
103
+ onUploadDocument={onUploadDocument}
104
+ onConnectFolder={onConnectFolder}
105
+ onIngestNote={onIngestNote}
106
+ onIngestWeb={onIngestWeb}
107
+ />
92
108
  <BrainOverviewPanel
93
109
  memories={memories}
94
110
  concepts={concepts}
@@ -96,6 +112,12 @@ export function BrainConversation({
96
112
  proof={proof}
97
113
  onOpenDepth={onOpenDepth}
98
114
  />
115
+ <ModelContinuityDemo
116
+ language={language}
117
+ proof={proof}
118
+ modelName={modelName}
119
+ onVerify={onVerifyModelContinuity}
120
+ />
99
121
  {messages.length === 0 ? (
100
122
  <BrainEmptyState
101
123
  language={language}
@@ -108,6 +130,9 @@ export function BrainConversation({
108
130
  messages.map((message, index) => (
109
131
  <div key={`${message.role}-${index}`} className={`brain-message ${message.role}`}>
110
132
  <div className="brain-message-bubble">{message.content}</div>
133
+ {message.role === "assistant" && message.proof ? (
134
+ <AnswerProofCard language={language} proof={message.proof} />
135
+ ) : null}
111
136
  </div>
112
137
  ))
113
138
  )}
@@ -138,6 +163,143 @@ export function BrainConversation({
138
163
  );
139
164
  }
140
165
 
166
+ function BrainIngestionPanel({
167
+ language,
168
+ uploadingDocument,
169
+ onUploadDocument,
170
+ onConnectFolder,
171
+ onIngestNote,
172
+ onIngestWeb,
173
+ }: {
174
+ language: Language;
175
+ uploadingDocument: boolean;
176
+ onUploadDocument: (file: File) => void;
177
+ onConnectFolder: (path: string) => void;
178
+ onIngestNote: (note: string) => void;
179
+ onIngestWeb: (url: string) => void;
180
+ }) {
181
+ const [folderPath, setFolderPath] = React.useState("");
182
+ const [note, setNote] = React.useState("");
183
+ const [url, setUrl] = React.useState("");
184
+
185
+ return (
186
+ <section className="brain-ingestion-panel" aria-label={t(language, "brain.ingest.aria")}>
187
+ <div className="brain-ingestion-head">
188
+ <span>{t(language, "brain.ingest.kicker")}</span>
189
+ <strong>{t(language, "brain.ingest.title")}</strong>
190
+ </div>
191
+ <div className="brain-ingestion-grid">
192
+ <label className={`brain-ingest-tile is-primary ${uploadingDocument ? "is-disabled" : ""}`}>
193
+ <FileUp className="h-4 w-4" />
194
+ <span>{uploadingDocument ? t(language, "brain.upload.uploading") : t(language, "brain.ingest.file")}</span>
195
+ <small>{t(language, "brain.ingest.file.detail")}</small>
196
+ <input
197
+ type="file"
198
+ accept=".pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,application/pdf,text/plain,text/markdown,text/csv"
199
+ className="sr-only"
200
+ disabled={uploadingDocument}
201
+ onChange={(event) => {
202
+ const file = event.target.files?.[0];
203
+ event.currentTarget.value = "";
204
+ if (file) onUploadDocument(file);
205
+ }}
206
+ />
207
+ </label>
208
+ <form
209
+ className="brain-ingest-tile"
210
+ onSubmit={(event) => {
211
+ event.preventDefault();
212
+ onConnectFolder(folderPath);
213
+ setFolderPath("");
214
+ }}
215
+ >
216
+ <FolderPlus className="h-4 w-4" />
217
+ <span>{t(language, "brain.ingest.folder")}</span>
218
+ <input value={folderPath} onChange={(event) => setFolderPath(event.target.value)} placeholder={t(language, "brain.ingest.folder.placeholder")} />
219
+ </form>
220
+ <form
221
+ className="brain-ingest-tile"
222
+ onSubmit={(event) => {
223
+ event.preventDefault();
224
+ onIngestNote(note);
225
+ setNote("");
226
+ }}
227
+ >
228
+ <FileText className="h-4 w-4" />
229
+ <span>{t(language, "brain.ingest.note")}</span>
230
+ <input value={note} onChange={(event) => setNote(event.target.value)} placeholder={t(language, "brain.ingest.note.placeholder")} />
231
+ </form>
232
+ <form
233
+ className="brain-ingest-tile"
234
+ onSubmit={(event) => {
235
+ event.preventDefault();
236
+ onIngestWeb(url);
237
+ setUrl("");
238
+ }}
239
+ >
240
+ <Globe2 className="h-4 w-4" />
241
+ <span>{t(language, "brain.ingest.web")}</span>
242
+ <input value={url} onChange={(event) => setUrl(event.target.value)} placeholder={t(language, "brain.ingest.web.placeholder")} />
243
+ </form>
244
+ </div>
245
+ </section>
246
+ );
247
+ }
248
+
249
+ function ModelContinuityDemo({
250
+ language,
251
+ proof,
252
+ modelName,
253
+ onVerify,
254
+ }: {
255
+ language: Language;
256
+ proof: BrainProof;
257
+ modelName: string;
258
+ onVerify: () => void;
259
+ }) {
260
+ return (
261
+ <section className="brain-model-demo" aria-label={t(language, "brain.modelDemo.aria")}>
262
+ <div>
263
+ <span>{t(language, "brain.modelDemo.kicker")}</span>
264
+ <strong>{proof.modelContinuity.proven ? t(language, "brain.modelDemo.proven") : t(language, "brain.modelDemo.pending")}</strong>
265
+ <small>{t(language, "brain.modelDemo.detail", { model: proof.modelContinuity.activeModel || modelName })}</small>
266
+ </div>
267
+ <button type="button" onClick={onVerify}>
268
+ <Repeat2 className="h-3.5 w-3.5" />
269
+ {t(language, "brain.modelDemo.verify")}
270
+ </button>
271
+ <button type="button" onClick={() => navigateHash("/models")}>
272
+ <Cpu className="h-3.5 w-3.5" />
273
+ {t(language, "brain.modelDemo.change")}
274
+ </button>
275
+ </section>
276
+ );
277
+ }
278
+
279
+ function AnswerProofCard({ language, proof }: { language: Language; proof: NonNullable<Message["proof"]> }) {
280
+ return (
281
+ <div className="brain-answer-proof" aria-label={t(language, "brain.answerProof.aria")}>
282
+ <div className="brain-answer-proof-head">
283
+ <span>{t(language, "brain.answerProof.title")}</span>
284
+ <strong>{proof.provenAcrossModels ? t(language, "brain.answerProof.modelProven", { model: proof.model }) : t(language, "brain.answerProof.modelPending", { model: proof.model })}</strong>
285
+ </div>
286
+ {proof.citations.length ? (
287
+ <ol>
288
+ {proof.citations.map((citation) => (
289
+ <li key={citation.id}>
290
+ <span>{citation.source}</span>
291
+ <strong>{citation.title}</strong>
292
+ <small>{citation.snippet || proof.query}</small>
293
+ </li>
294
+ ))}
295
+ </ol>
296
+ ) : (
297
+ <small>{t(language, "brain.answerProof.empty")}</small>
298
+ )}
299
+ </div>
300
+ );
301
+ }
302
+
141
303
  function BrainEmptyState({
142
304
  language,
143
305
  starterPrompts,
@@ -7,7 +7,7 @@ import { t } from "@/i18n";
7
7
  import { BrainConversation } from "./BrainConversation";
8
8
  import { buildBrainProof, buildBrainReadiness, buildMemoryFragments, currentModelName, parseKnowledgeGraph } from "./brainData";
9
9
  import { DepthEmergence } from "./DepthEmergence";
10
- import { DEPTHS, type BrainDepth, type MemoryFragment, type Message } from "./types";
10
+ import { DEPTHS, type BrainDepth, type BrainProof, type MemoryFragment, type Message, type MessageProof } from "./types";
11
11
 
12
12
  export function BrainHome({
13
13
  brainState,
@@ -134,6 +134,7 @@ export function BrainHome({
134
134
  } else {
135
135
  setMemoryFeedback(t(language, "brain.saved", { topics: knowledgeConcepts.length, memories: memoryFragments.length }));
136
136
  setLastRecallQuery(text);
137
+ void attachAnswerProof(text);
137
138
  }
138
139
  } finally {
139
140
  setStreaming(false);
@@ -144,6 +145,22 @@ export function BrainHome({
144
145
  }
145
146
  }
146
147
 
148
+ async function attachAnswerProof(query: string) {
149
+ const proofResult = await latticeApi.memoryBrainProof(query, 4);
150
+ const proof = buildBrainProof(proofResult.data, modelName);
151
+ const messageProof = toMessageProof(proof, query, modelName);
152
+ setMessages((items) => {
153
+ const next = [...items];
154
+ for (let index = next.length - 1; index >= 0; index -= 1) {
155
+ if (next[index].role === "assistant") {
156
+ next[index] = { ...next[index], proof: messageProof };
157
+ break;
158
+ }
159
+ }
160
+ return next;
161
+ });
162
+ }
163
+
147
164
  async function uploadDocument(file: File) {
148
165
  if (uploadingDocument) return;
149
166
 
@@ -169,6 +186,78 @@ export function BrainHome({
169
186
  }
170
187
  }
171
188
 
189
+ async function connectFolder(path: string) {
190
+ const target = path.trim();
191
+ if (!target) return;
192
+ setMemoryFeedback(t(language, "brain.ingest.folder.pending", { path: target }));
193
+ onBrainChange("recalling", 0.84);
194
+ const result = await latticeApi.connectFolder(target);
195
+ if (result.error || !result.ok) {
196
+ setMemoryFeedback(t(language, "brain.ingest.folder.failed", { reason: result.error || "unavailable" }));
197
+ return;
198
+ }
199
+ setMemoryFeedback(t(language, "brain.ingest.folder.saved", { path: target }));
200
+ setLastRecallQuery(target);
201
+ triggerBrainRecall();
202
+ void refreshBrainProof(target);
203
+ }
204
+
205
+ async function ingestNote(note: string) {
206
+ const content = note.trim();
207
+ if (!content) return;
208
+ setMemoryFeedback(t(language, "brain.ingest.note.pending"));
209
+ onBrainChange("recalling", 0.84);
210
+ const result = await latticeApi.ingestNote(content, content.slice(0, 80));
211
+ if (result.error || !result.ok) {
212
+ setMemoryFeedback(t(language, "brain.ingest.note.failed", { reason: result.error || "unavailable" }));
213
+ return;
214
+ }
215
+ setMemoryFeedback(t(language, "brain.ingest.note.saved"));
216
+ setLastRecallQuery(content.slice(0, 120));
217
+ triggerBrainRecall();
218
+ void refreshBrainProof(content.slice(0, 120));
219
+ }
220
+
221
+ async function ingestWeb(url: string) {
222
+ const target = url.trim();
223
+ if (!target) return;
224
+ setMemoryFeedback(t(language, "brain.ingest.web.pending", { url: target }));
225
+ onBrainChange("recalling", 0.84);
226
+ const result = await latticeApi.browserReadUrl(target);
227
+ if (result.error || !result.ok) {
228
+ setMemoryFeedback(t(language, "brain.ingest.web.failed", { reason: result.error || "unavailable" }));
229
+ return;
230
+ }
231
+ setMemoryFeedback(t(language, "brain.ingest.web.saved", { url: target }));
232
+ setLastRecallQuery(target);
233
+ triggerBrainRecall();
234
+ void refreshBrainProof(target);
235
+ }
236
+
237
+ async function refreshBrainProof(query = lastRecallQuery) {
238
+ await Promise.all([
239
+ qc.invalidateQueries({ queryKey: ["memoryManager"] }),
240
+ qc.invalidateQueries({ queryKey: ["graph"] }),
241
+ qc.invalidateQueries({ queryKey: ["memoryBrainProof"] }),
242
+ ]);
243
+ if (query.trim()) {
244
+ await attachAnswerProof(query);
245
+ }
246
+ }
247
+
248
+ async function verifyModelContinuity() {
249
+ const lastUserMessage = [...messages].reverse().find((message: Message) => message.role === "user");
250
+ const query = lastRecallQuery || lastUserMessage?.content || "";
251
+ if (!query.trim()) {
252
+ setMemoryFeedback(t(language, "brain.modelDemo.needQuestion"));
253
+ return;
254
+ }
255
+ setMemoryFeedback(t(language, "brain.modelDemo.checking", { model: modelName }));
256
+ await attachAnswerProof(query);
257
+ setLastRecallQuery(query);
258
+ setMemoryFeedback(t(language, "brain.modelDemo.done", { model: modelName }));
259
+ }
260
+
172
261
  function deepen() {
173
262
  setExplorationDepth((depth) => {
174
263
  const next = Math.min(5, depth + 1) as BrainDepth;
@@ -285,8 +374,26 @@ export function BrainHome({
285
374
  onDraftChange={setDraft}
286
375
  onImageDataChange={setImageData}
287
376
  onUploadDocument={(file) => void uploadDocument(file)}
377
+ onConnectFolder={(path) => void connectFolder(path)}
378
+ onIngestNote={(note) => void ingestNote(note)}
379
+ onIngestWeb={(url) => void ingestWeb(url)}
380
+ onVerifyModelContinuity={() => void verifyModelContinuity()}
288
381
  onSend={() => void send()}
289
382
  />
290
383
  </main>
291
384
  );
292
385
  }
386
+
387
+ function toMessageProof(proof: BrainProof, query: string, fallbackModelName: string): MessageProof {
388
+ return {
389
+ query,
390
+ model: proof.modelContinuity.activeModel || fallbackModelName,
391
+ provenAcrossModels: proof.modelContinuity.proven && proof.claims.keepsContextAcrossModels,
392
+ citations: proof.recall.items.slice(0, 4).map((item) => ({
393
+ id: item.id,
394
+ source: item.source,
395
+ title: item.title,
396
+ snippet: item.snippet,
397
+ })),
398
+ };
399
+ }
@@ -6,6 +6,19 @@ export type BrainDepth = 1 | 2 | 3 | 4 | 5;
6
6
  export type Message = {
7
7
  role: "user" | "assistant";
8
8
  content: string;
9
+ proof?: MessageProof;
10
+ };
11
+
12
+ export type MessageProof = {
13
+ query: string;
14
+ model: string;
15
+ provenAcrossModels: boolean;
16
+ citations: Array<{
17
+ id: string;
18
+ source: string;
19
+ title: string;
20
+ snippet: string;
21
+ }>;
9
22
  };
10
23
 
11
24
  export type MemoryFragment = {
@@ -65,6 +65,26 @@ export const COPY: Record<Language, TextMap> = {
65
65
  "brain.upload.pending": "{name}을 Brain에 넣는 중입니다.",
66
66
  "brain.upload.saved": "{name}이 Brain에 저장됐습니다.",
67
67
  "brain.upload.failed": "문서를 넣지 못했습니다: {reason}",
68
+ "brain.ingest.aria": "Brain에 넣기",
69
+ "brain.ingest.kicker": "5분 안에 Brain 만들기",
70
+ "brain.ingest.title": "파일, 폴더, 노트, 웹을 바로 넣으세요.",
71
+ "brain.ingest.file": "파일 넣기",
72
+ "brain.ingest.file.detail": "PDF, 문서, 표, 메모",
73
+ "brain.ingest.folder": "폴더 넣기",
74
+ "brain.ingest.folder.placeholder": "로컬 폴더 경로",
75
+ "brain.ingest.note": "노트 넣기",
76
+ "brain.ingest.note.placeholder": "결정이나 맥락을 붙여넣기",
77
+ "brain.ingest.web": "웹 넣기",
78
+ "brain.ingest.web.placeholder": "https://...",
79
+ "brain.ingest.folder.pending": "{path} 폴더를 Brain에 연결 중입니다.",
80
+ "brain.ingest.folder.saved": "{path} 폴더가 Brain에 연결됐습니다.",
81
+ "brain.ingest.folder.failed": "폴더를 넣지 못했습니다: {reason}",
82
+ "brain.ingest.note.pending": "노트를 Brain에 저장 중입니다.",
83
+ "brain.ingest.note.saved": "노트가 Brain에 저장됐습니다.",
84
+ "brain.ingest.note.failed": "노트를 넣지 못했습니다: {reason}",
85
+ "brain.ingest.web.pending": "{url}을 읽어 Brain에 넣는 중입니다.",
86
+ "brain.ingest.web.saved": "{url}이 Brain에 저장됐습니다.",
87
+ "brain.ingest.web.failed": "웹 페이지를 넣지 못했습니다: {reason}",
68
88
  "brain.image": "이미지",
69
89
  "brain.unavailable": "지금은 답할 수 없음",
70
90
  "brain.imageAttached": "이미지 첨부됨",
@@ -104,6 +124,21 @@ export const COPY: Record<Language, TextMap> = {
104
124
  "brain.proof.recallEmpty.detail": "아직 저장된 기억이 없어서 가짜 예시는 보여주지 않습니다.",
105
125
  "brain.proof.recallPending": "기억은 저장됐고 recall 증거를 준비 중입니다.",
106
126
  "brain.proof.recallPending.detail": "인덱싱이 끝나면 이 자리에서 실제 기억을 다시 보여줍니다.",
127
+ "brain.modelDemo.aria": "모델 교체 증거",
128
+ "brain.modelDemo.kicker": "모델 독립 Brain",
129
+ "brain.modelDemo.proven": "현재 기억은 모델 밖에 남아 있습니다.",
130
+ "brain.modelDemo.pending": "첫 기억이 저장되면 모델 교체 증거가 생깁니다.",
131
+ "brain.modelDemo.detail": "현재 모델: {model}. 같은 질문으로 Brain 증거를 다시 확인할 수 있습니다.",
132
+ "brain.modelDemo.verify": "같은 증거 확인",
133
+ "brain.modelDemo.change": "모델 바꾸기",
134
+ "brain.modelDemo.needQuestion": "먼저 질문하거나 기억을 넣으면 같은 증거를 다시 확인할 수 있습니다.",
135
+ "brain.modelDemo.checking": "{model}에서 같은 Brain 증거를 다시 확인 중입니다.",
136
+ "brain.modelDemo.done": "{model}에서도 같은 Brain 증거를 확인했습니다.",
137
+ "brain.answerProof.aria": "답변 출처와 Memory proof",
138
+ "brain.answerProof.title": "Memory proof + source citation",
139
+ "brain.answerProof.modelProven": "{model} 밖에 남은 Brain 증거",
140
+ "brain.answerProof.modelPending": "{model} 응답 · 증거 저장 대기",
141
+ "brain.answerProof.empty": "아직 연결된 출처가 없습니다. 기억이 인덱싱되면 여기에 표시됩니다.",
107
142
  "brain.memory.empty.kicker": "첫 기억",
108
143
  "brain.memory.empty": "기억이 조용합니다.",
109
144
  "brain.knowledge.empty": "지식이 형성되는 중입니다.",
@@ -424,6 +459,26 @@ export const COPY: Record<Language, TextMap> = {
424
459
  "brain.upload.pending": "Adding {name} to Brain.",
425
460
  "brain.upload.saved": "{name} was saved to Brain.",
426
461
  "brain.upload.failed": "Could not add the document: {reason}",
462
+ "brain.ingest.aria": "Add to Brain",
463
+ "brain.ingest.kicker": "Build a Brain in 5 minutes",
464
+ "brain.ingest.title": "Add files, folders, notes, and web pages immediately.",
465
+ "brain.ingest.file": "Add file",
466
+ "brain.ingest.file.detail": "PDFs, docs, tables, notes",
467
+ "brain.ingest.folder": "Add folder",
468
+ "brain.ingest.folder.placeholder": "Local folder path",
469
+ "brain.ingest.note": "Add note",
470
+ "brain.ingest.note.placeholder": "Paste a decision or context",
471
+ "brain.ingest.web": "Add web",
472
+ "brain.ingest.web.placeholder": "https://...",
473
+ "brain.ingest.folder.pending": "Connecting {path} to Brain.",
474
+ "brain.ingest.folder.saved": "{path} is connected to Brain.",
475
+ "brain.ingest.folder.failed": "Could not add the folder: {reason}",
476
+ "brain.ingest.note.pending": "Saving note to Brain.",
477
+ "brain.ingest.note.saved": "Note was saved to Brain.",
478
+ "brain.ingest.note.failed": "Could not add the note: {reason}",
479
+ "brain.ingest.web.pending": "Reading {url} into Brain.",
480
+ "brain.ingest.web.saved": "{url} was saved to Brain.",
481
+ "brain.ingest.web.failed": "Could not add the web page: {reason}",
427
482
  "brain.image": "Image",
428
483
  "brain.unavailable": "Unavailable",
429
484
  "brain.imageAttached": "Image attached",
@@ -463,6 +518,21 @@ export const COPY: Record<Language, TextMap> = {
463
518
  "brain.proof.recallEmpty.detail": "No saved memory yet, so Lattice does not show a fake example.",
464
519
  "brain.proof.recallPending": "Memory is saved; recall proof is preparing.",
465
520
  "brain.proof.recallPending.detail": "When indexing finishes, this slot shows the actual memory Brain can bring back.",
521
+ "brain.modelDemo.aria": "Model switch proof",
522
+ "brain.modelDemo.kicker": "Model-independent Brain",
523
+ "brain.modelDemo.proven": "This memory lives outside the model.",
524
+ "brain.modelDemo.pending": "After the first memory is saved, model-switch proof appears.",
525
+ "brain.modelDemo.detail": "Current model: {model}. Recheck the same question against Brain evidence.",
526
+ "brain.modelDemo.verify": "Recheck same evidence",
527
+ "brain.modelDemo.change": "Switch model",
528
+ "brain.modelDemo.needQuestion": "Ask a question or add memory first, then recheck the same evidence.",
529
+ "brain.modelDemo.checking": "Rechecking the same Brain evidence with {model}.",
530
+ "brain.modelDemo.done": "The same Brain evidence is visible with {model}.",
531
+ "brain.answerProof.aria": "Answer sources and memory proof",
532
+ "brain.answerProof.title": "Memory proof + source citation",
533
+ "brain.answerProof.modelProven": "Brain evidence outside {model}",
534
+ "brain.answerProof.modelPending": "{model} response · evidence pending",
535
+ "brain.answerProof.empty": "No linked source yet. Indexed memory appears here.",
466
536
  "brain.memory.empty.kicker": "First memory",
467
537
  "brain.memory.empty": "Memory is quiet",
468
538
  "brain.knowledge.empty": "Knowledge is forming",