ltcai 6.6.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 (42) hide show
  1. package/README.md +32 -36
  2. package/docs/CHANGELOG.md +50 -2
  3. package/frontend/src/App.tsx +78 -3
  4. package/frontend/src/api/client.ts +5 -0
  5. package/frontend/src/features/brain/BrainConversation.tsx +182 -2
  6. package/frontend/src/features/brain/BrainHome.tsx +108 -1
  7. package/frontend/src/features/brain/types.ts +13 -0
  8. package/frontend/src/i18n.ts +80 -0
  9. package/frontend/src/routes.ts +31 -17
  10. package/frontend/src/styles.css +295 -0
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/runtime/multi_agent.py +1 -1
  13. package/latticeai/__init__.py +1 -1
  14. package/latticeai/core/marketplace.py +1 -1
  15. package/latticeai/core/workspace_os.py +1 -1
  16. package/package.json +1 -1
  17. package/scripts/brain_quality_eval.py +118 -0
  18. package/src-tauri/Cargo.lock +1 -1
  19. package/src-tauri/Cargo.toml +1 -1
  20. package/src-tauri/tauri.conf.json +1 -1
  21. package/static/app/asset-manifest.json +84 -6
  22. package/static/app/assets/Act-DhLaHnrT.js +2 -0
  23. package/static/app/assets/Act-DhLaHnrT.js.map +1 -0
  24. package/static/app/assets/Brain-DS7NULyY.js +322 -0
  25. package/static/app/assets/Brain-DS7NULyY.js.map +1 -0
  26. package/static/app/assets/Capture-CAKouj52.js +2 -0
  27. package/static/app/assets/Capture-CAKouj52.js.map +1 -0
  28. package/static/app/assets/Library-B_hW4AwR.js +2 -0
  29. package/static/app/assets/Library-B_hW4AwR.js.map +1 -0
  30. package/static/app/assets/System-9OAoXWPz.js +2 -0
  31. package/static/app/assets/System-9OAoXWPz.js.map +1 -0
  32. package/static/app/assets/index-C-7aHabu.js +17 -0
  33. package/static/app/assets/index-C-7aHabu.js.map +1 -0
  34. package/static/app/assets/index-DKFpn5Kn.css +2 -0
  35. package/static/app/assets/primitives-DcO2H3yh.js +2 -0
  36. package/static/app/assets/primitives-DcO2H3yh.js.map +1 -0
  37. package/static/app/assets/textarea-DR2Tyy_6.js +2 -0
  38. package/static/app/assets/textarea-DR2Tyy_6.js.map +1 -0
  39. package/static/app/index.html +2 -2
  40. package/static/app/assets/index-C8toxDpv.css +0 -2
  41. package/static/app/assets/index-CbvcAQ6B.js +0 -16
  42. package/static/app/assets/index-CbvcAQ6B.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.5.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.5.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.5.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.5.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.5.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.5.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.5.0 Living Brain walkthrough](output/release/v6.5.0/gifs/v6.5.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.5.0/SCREENSHOT_INDEX.md](output/release/v6.5.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
 
@@ -198,34 +198,28 @@ npm run docs:check-links
198
198
 
199
199
  See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
200
200
 
201
- ## Current Release Preparation
202
-
203
- The current development target is **6.6.0 Brain Proof Runtime**:
204
-
205
- - Brain Home now shows backend-owned proof that the Brain is a durable context
206
- store: saved context count, active-model continuity, graph/vector depth, and
207
- the most recent recallable item.
208
- - Brain Home now gives first-time users a direct document upload CTA in the
209
- empty state and composer, backed by workspace-scoped document ingestion.
210
- - `/api/memory/brain-proof` combines Memory Manager readiness, unified recall,
211
- graph/vector state, and the active model id so the first screen can say
212
- "model changed, context stayed" without guessing in the frontend.
213
- - App-factory interaction wiring now passes active-model state into the Memory
214
- router through typed runtime context, keeping Brain proof assembly out of the
215
- monolithic factory body.
216
- - The release keeps the 6.4.0/6.5.0 Brain readiness and quality hardening intact
217
- while making the first-five-minute value proposition visible.
218
- - The release remains local-first: no automatic web/email/calendar ingestion,
219
- no package publishing, no production deployment, and no external reranker or
220
- embedding API use without explicit opt-in.
221
-
222
- Expected artifacts for 6.6.0 release must use exact filenames:
223
-
224
- - `dist/ltcai-6.6.0-py3-none-any.whl`
225
- - `dist/ltcai-6.6.0.tar.gz`
226
- - `ltcai-6.6.0.tgz`
227
- - `dist/ltcai-6.6.0.vsix`
228
- - `src-tauri/target/release/bundle/dmg/Lattice AI_6.6.0_aarch64.dmg`
201
+ ## Current Release
202
+
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`
229
223
 
230
224
  Do not upload `dist/*`. Package registry publishing remains owner-run.
231
225
 
@@ -244,6 +238,8 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
244
238
 
245
239
  | Version | Theme |
246
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 |
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 |
247
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 |
248
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 |
249
245
  | 6.4.0 | Digital Brain Quality Hardening: workspace-scoped graph/search/memory reads and mutations, Brain quality primitives, structured context guardrails, and retrieval benchmark coverage |
package/docs/CHANGELOG.md CHANGED
@@ -1,7 +1,54 @@
1
1
  # Changelog
2
2
 
3
- The top entry is the current release-preparation target. Older entries are
4
- historical and may describe behavior as it existed at that release.
3
+ The top entry is either the current unreleased main-branch work or the current
4
+ release line. Older entries are historical and may describe behavior as it
5
+ existed at that release.
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
+
32
+ ## [6.7.0] - 2026-06-18
33
+
34
+ > Brain IA Cleanup. Makes rich product pages reachable from Brain Home,
35
+ > separates visible product IA from legacy compatibility aliases, and
36
+ > code-splits heavy pages out of the first Brain bundle.
37
+
38
+ ### Changed
39
+ - Brain Home now exposes direct action routes for adding documents, searching
40
+ the knowledge graph, changing models, and opening settings, so the rich
41
+ Capture, Brain explorer, Library, and System pages are reachable from the
42
+ first Brain flow.
43
+ - The post-onboarding app shell now mounts Capture, Brain explorer/graph,
44
+ Library, System, and Act pages inside a shared Brain shell navigation instead
45
+ of leaving those rich pages as compatibility-only routes.
46
+ - `routes.ts` now keeps product shell routes and compatibility aliases in
47
+ separate maps while preserving old hash/entry URLs.
48
+ - Rich pages now load through `React.lazy`, splitting Brain explorer, Capture,
49
+ Library, System, and Act chunks away from the initial Brain Home bundle.
50
+ - Package/runtime/static metadata is synchronized to 6.7.0; package publish and
51
+ deployment remain owner-run only.
5
52
 
6
53
  ## [6.6.0] - 2026-06-18
7
54
 
@@ -19,6 +66,7 @@ historical and may describe behavior as it existed at that release.
19
66
  bring back after a useful exchange.
20
67
  - Brain Home document upload CTA in the empty state and composer, giving the
21
68
  first session a direct way to grow the Brain with files.
69
+ - Refreshed README-linked release evidence under `output/release/v6.6.0/`.
22
70
  - Unit coverage for model-independent Brain proof recall behavior.
23
71
  - Honest empty-state Brain proof: model-independent capability remains visible,
24
72
  but continuity is only marked proven after durable evidence exists.
@@ -2,11 +2,16 @@ import * as React from "react";
2
2
  import { type BrainState } from "@/components/LivingBrain";
3
3
  import { ProductFlow, readProductFlowComplete } from "@/components/ProductFlow";
4
4
  import { useAppStore } from "@/store/appStore";
5
- import { parseHash } from "@/routes";
6
- import { ActPage } from "@/pages/Act";
5
+ import { parseHash, productShellRoutes } from "@/routes";
7
6
  import { BrainHome } from "@/features/brain/BrainHome";
8
7
  import { AdminConsole } from "@/features/admin/AdminConsole";
9
8
 
9
+ const ActPage = React.lazy(() => import("@/pages/Act").then((module) => ({ default: module.ActPage })));
10
+ const BrainPage = React.lazy(() => import("@/pages/Brain").then((module) => ({ default: module.BrainPage })));
11
+ const CapturePage = React.lazy(() => import("@/pages/Capture").then((module) => ({ default: module.CapturePage })));
12
+ const LibraryPage = React.lazy(() => import("@/pages/Library").then((module) => ({ default: module.LibraryPage })));
13
+ const SystemPage = React.lazy(() => import("@/pages/System").then((module) => ({ default: module.SystemPage })));
14
+
10
15
  export default function App() {
11
16
  const theme = useAppStore((state) => state.theme);
12
17
  const language = useAppStore((state) => state.language);
@@ -41,7 +46,41 @@ export default function App() {
41
46
  {rawRoute.startsWith("/admin") ? (
42
47
  <AdminConsole onBack={() => navigateHash("/brain")} />
43
48
  ) : parsed.primary === "act" ? (
44
- <ActPage initialTab={parsed.tab} />
49
+ <BrainShell active={parsed.primary}>
50
+ <React.Suspense fallback={<PageLoader />}>
51
+ <ActPage initialTab={parsed.tab} />
52
+ </React.Suspense>
53
+ </BrainShell>
54
+ ) : parsed.primary === "capture" ? (
55
+ <BrainShell active={parsed.primary}>
56
+ <React.Suspense fallback={<PageLoader />}>
57
+ <CapturePage initialTab={parsed.tab} />
58
+ </React.Suspense>
59
+ </BrainShell>
60
+ ) : parsed.primary === "library" ? (
61
+ <BrainShell active={parsed.primary}>
62
+ <React.Suspense fallback={<PageLoader />}>
63
+ <LibraryPage initialTab={parsed.tab} />
64
+ </React.Suspense>
65
+ </BrainShell>
66
+ ) : parsed.primary === "system" ? (
67
+ <BrainShell active={parsed.primary}>
68
+ <React.Suspense fallback={<PageLoader />}>
69
+ <SystemPage initialTab={parsed.tab} />
70
+ </React.Suspense>
71
+ </BrainShell>
72
+ ) : parsed.primary === "memory" ? (
73
+ <BrainShell active="memory">
74
+ <React.Suspense fallback={<PageLoader />}>
75
+ <BrainPage initialTab="memory" />
76
+ </React.Suspense>
77
+ </BrainShell>
78
+ ) : parsed.primary === "brain" && parsed.tab && parsed.tab !== "conversation" ? (
79
+ <BrainShell active="brain">
80
+ <React.Suspense fallback={<PageLoader />}>
81
+ <BrainPage initialTab={parsed.tab} />
82
+ </React.Suspense>
83
+ </BrainShell>
45
84
  ) : (
46
85
  <BrainHome brainState={brainState} intensity={intensity} onBrainChange={setBrain} />
47
86
  )}
@@ -49,6 +88,42 @@ export default function App() {
49
88
  );
50
89
  }
51
90
 
91
+ function BrainShell({
92
+ active,
93
+ children,
94
+ }: {
95
+ active: string;
96
+ children: React.ReactNode;
97
+ }) {
98
+ return (
99
+ <main className="brain-shell-page" aria-label="Lattice workspace">
100
+ <nav className="brain-shell-nav" aria-label="Brain workspace navigation">
101
+ {productShellRoutes.map((item) => (
102
+ <button
103
+ key={item.id}
104
+ type="button"
105
+ className={item.id === active ? "is-active" : ""}
106
+ onClick={() => navigateHash(`/${item.path}`)}
107
+ >
108
+ {item.label}
109
+ </button>
110
+ ))}
111
+ </nav>
112
+ <section className="brain-shell-content">
113
+ {children}
114
+ </section>
115
+ </main>
116
+ );
117
+ }
118
+
119
+ function PageLoader() {
120
+ return (
121
+ <div className="brain-shell-loader" role="status">
122
+ Loading Brain workspace...
123
+ </div>
124
+ );
125
+ }
126
+
52
127
  function useHashRoute() {
53
128
  const read = React.useCallback(() => {
54
129
  const hash = window.location.hash.replace(/^#/, "");
@@ -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 { DatabaseZap, 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 (
@@ -63,7 +71,25 @@ export function BrainConversation({
63
71
  <span>{t(language, "brain.portable")}</span>
64
72
  <span>{t(language, "brain.private")}</span>
65
73
  </div>
66
- <div>{modelName}</div>
74
+ <div className="brain-flow-actions" aria-label={t(language, "brain.aria.actions")}>
75
+ <button type="button" onClick={() => navigateHash("/capture")}>
76
+ <FileUp className="h-3.5 w-3.5" />
77
+ {t(language, "brain.action.add")}
78
+ </button>
79
+ <button type="button" onClick={() => navigateHash("/knowledge-graph")}>
80
+ <Search className="h-3.5 w-3.5" />
81
+ {t(language, "brain.action.find")}
82
+ </button>
83
+ <button type="button" onClick={() => navigateHash("/models")}>
84
+ <Cpu className="h-3.5 w-3.5" />
85
+ {t(language, "brain.action.model")}
86
+ </button>
87
+ <button type="button" onClick={() => navigateHash("/settings")}>
88
+ <Settings className="h-3.5 w-3.5" />
89
+ {t(language, "brain.action.settings")}
90
+ </button>
91
+ </div>
92
+ <div className="brain-model-pill">{modelName}</div>
67
93
  <button className="brain-admin-link" type="button" onClick={() => navigateHash("/admin")}>
68
94
  <ShieldCheck className="h-3.5 w-3.5" />
69
95
  {t(language, "brain.admin")}
@@ -71,6 +97,14 @@ export function BrainConversation({
71
97
  </div>
72
98
 
73
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
+ />
74
108
  <BrainOverviewPanel
75
109
  memories={memories}
76
110
  concepts={concepts}
@@ -78,6 +112,12 @@ export function BrainConversation({
78
112
  proof={proof}
79
113
  onOpenDepth={onOpenDepth}
80
114
  />
115
+ <ModelContinuityDemo
116
+ language={language}
117
+ proof={proof}
118
+ modelName={modelName}
119
+ onVerify={onVerifyModelContinuity}
120
+ />
81
121
  {messages.length === 0 ? (
82
122
  <BrainEmptyState
83
123
  language={language}
@@ -90,6 +130,9 @@ export function BrainConversation({
90
130
  messages.map((message, index) => (
91
131
  <div key={`${message.role}-${index}`} className={`brain-message ${message.role}`}>
92
132
  <div className="brain-message-bubble">{message.content}</div>
133
+ {message.role === "assistant" && message.proof ? (
134
+ <AnswerProofCard language={language} proof={message.proof} />
135
+ ) : null}
93
136
  </div>
94
137
  ))
95
138
  )}
@@ -120,6 +163,143 @@ export function BrainConversation({
120
163
  );
121
164
  }
122
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
+
123
303
  function BrainEmptyState({
124
304
  language,
125
305
  starterPrompts,