ltcai 8.0.0 → 8.1.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 (103) hide show
  1. package/README.md +32 -50
  2. package/docs/CHANGELOG.md +14 -0
  3. package/docs/V3_FRONTEND.md +11 -5
  4. package/lattice_brain/__init__.py +1 -1
  5. package/lattice_brain/embeddings.py +9 -1
  6. package/lattice_brain/runtime/multi_agent.py +1 -1
  7. package/latticeai/__init__.py +1 -1
  8. package/latticeai/app_factory.py +13 -134
  9. package/latticeai/core/local_embeddings.py +8 -0
  10. package/latticeai/core/marketplace.py +1 -1
  11. package/latticeai/core/mcp_registry.py +140 -1
  12. package/latticeai/core/workspace_os.py +1 -1
  13. package/latticeai/services/architecture_readiness.py +6 -5
  14. package/latticeai/services/model_runtime.py +36 -2
  15. package/latticeai/services/product_readiness.py +18 -18
  16. package/package.json +2 -2
  17. package/scripts/build_frontend_assets.mjs +11 -2
  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 +11 -102
  22. package/static/app/assets/Act-BOO66G-c.js +1 -0
  23. package/static/app/assets/Brain-C6lEYiD7.js +321 -0
  24. package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-TATXBRDw.js} +1 -2
  25. package/static/app/assets/Library-DK4FIp8a.js +1 -0
  26. package/static/app/assets/System-Bgs6Ql7x.js +1 -0
  27. package/static/app/assets/core-CwxXejkd.js +1 -2
  28. package/static/app/assets/index-Bvv79nre.js +16 -0
  29. package/static/app/assets/index-Dslqglia.css +2 -0
  30. package/static/app/assets/primitives-B70WOra0.js +1 -0
  31. package/static/app/assets/{textarea-BZk6ybp5.js → textarea-Czrd9gwM.js} +1 -2
  32. package/static/app/index.html +3 -10
  33. package/static/app/theme-boot.js +8 -0
  34. package/static/sw.js +1 -1
  35. package/frontend/index.html +0 -24
  36. package/frontend/openapi.json +0 -15425
  37. package/frontend/src/App.tsx +0 -243
  38. package/frontend/src/api/client.ts +0 -580
  39. package/frontend/src/api/openapi.ts +0 -17892
  40. package/frontend/src/components/AdminAccessGate.tsx +0 -70
  41. package/frontend/src/components/FeedbackState.tsx +0 -45
  42. package/frontend/src/components/LanguageSwitcher.tsx +0 -23
  43. package/frontend/src/components/LivingBrain.tsx +0 -220
  44. package/frontend/src/components/ProductFlow.tsx +0 -171
  45. package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
  46. package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
  47. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
  48. package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
  49. package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
  50. package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
  51. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
  52. package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
  53. package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
  54. package/frontend/src/components/primitives.tsx +0 -392
  55. package/frontend/src/components/ui/badge.tsx +0 -27
  56. package/frontend/src/components/ui/button.tsx +0 -37
  57. package/frontend/src/components/ui/card.tsx +0 -22
  58. package/frontend/src/components/ui/input.tsx +0 -16
  59. package/frontend/src/components/ui/textarea.tsx +0 -16
  60. package/frontend/src/features/admin/AdminConsole.tsx +0 -334
  61. package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
  62. package/frontend/src/features/brain/BrainComposer.tsx +0 -85
  63. package/frontend/src/features/brain/BrainConversation.tsx +0 -688
  64. package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
  65. package/frontend/src/features/brain/BrainHome.tsx +0 -624
  66. package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
  67. package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
  68. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
  69. package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
  70. package/frontend/src/features/brain/brainData.ts +0 -246
  71. package/frontend/src/features/brain/graphLayout.ts +0 -37
  72. package/frontend/src/features/brain/types.ts +0 -150
  73. package/frontend/src/features/review/ReviewCard.tsx +0 -100
  74. package/frontend/src/features/review/ReviewInbox.tsx +0 -127
  75. package/frontend/src/features/review/reviewHelpers.ts +0 -69
  76. package/frontend/src/i18n.ts +0 -1303
  77. package/frontend/src/lib/utils.ts +0 -33
  78. package/frontend/src/main.tsx +0 -23
  79. package/frontend/src/pages/Act.tsx +0 -458
  80. package/frontend/src/pages/Ask.tsx +0 -14
  81. package/frontend/src/pages/Brain.tsx +0 -914
  82. package/frontend/src/pages/Capture.tsx +0 -258
  83. package/frontend/src/pages/Library.tsx +0 -486
  84. package/frontend/src/pages/System.tsx +0 -621
  85. package/frontend/src/routes.ts +0 -92
  86. package/frontend/src/store/appStore.ts +0 -94
  87. package/frontend/src/styles.css +0 -6579
  88. package/static/app/assets/Act-DOvf59ru.js +0 -2
  89. package/static/app/assets/Act-DOvf59ru.js.map +0 -1
  90. package/static/app/assets/Brain-C7_0mEiI.js +0 -322
  91. package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
  92. package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
  93. package/static/app/assets/Library-BJPEEm5O.js +0 -2
  94. package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
  95. package/static/app/assets/System-D6t9jo9V.js +0 -2
  96. package/static/app/assets/System-D6t9jo9V.js.map +0 -1
  97. package/static/app/assets/core-CwxXejkd.js.map +0 -1
  98. package/static/app/assets/index-C7g26IF6.css +0 -2
  99. package/static/app/assets/index-DbcEYJQ2.js +0 -17
  100. package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
  101. package/static/app/assets/primitives-CD38lt4n.js +0 -2
  102. package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
  103. package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
@@ -1,688 +0,0 @@
1
- import * as React from "react";
2
- import {
3
- BrainCircuit,
4
- CheckCircle2,
5
- Cpu,
6
- DatabaseZap,
7
- FileText,
8
- FileUp,
9
- FolderPlus,
10
- Globe2,
11
- HardDrive,
12
- Loader2,
13
- Repeat2,
14
- Search,
15
- ShieldCheck,
16
- Sparkles,
17
- } from "lucide-react";
18
-
19
- const INGESTION_TYPE_LABEL_KEY: Record<IngestionSourceType, string> = {
20
- file: "brain.ingest.type.file",
21
- folder: "brain.ingest.type.folder",
22
- note: "brain.ingest.type.note",
23
- web: "brain.ingest.type.web",
24
- };
25
- import { LanguageSwitcher } from "@/components/LanguageSwitcher";
26
- import { t, type Language } from "@/i18n";
27
- import {
28
- INGESTION_STAGE_ORDER,
29
- type BrainDepth,
30
- type BrainProof,
31
- type BrainReadiness,
32
- type EmergenceEvent,
33
- type IngestionPipelineStage,
34
- type IngestionSourceType,
35
- type IngestionState,
36
- type KnowledgeConcept,
37
- type MemoryFragment,
38
- type Message,
39
- } from "./types";
40
- import { BrainCarePanel } from "./BrainCarePanel";
41
- import { BrainComposer } from "./BrainComposer";
42
- import { BrainOverviewPanel } from "./BrainOverviewPanel";
43
-
44
- export function BrainConversation({
45
- language,
46
- modelName,
47
- messages,
48
- starterPrompts,
49
- memoryFeedback,
50
- ingestionStates,
51
- emergenceEvents,
52
- draft,
53
- streaming,
54
- imageData,
55
- streamRef,
56
- memories,
57
- concepts,
58
- readiness,
59
- proof,
60
- uploadingDocument,
61
- onOpenDepth,
62
- onDraftChange,
63
- onImageDataChange,
64
- onUploadDocument,
65
- onConnectFolder,
66
- onIngestNote,
67
- onIngestWeb,
68
- onVerifyModelContinuity,
69
- onSend,
70
- }: {
71
- language: Language;
72
- explorationDepth: BrainDepth;
73
- modelName: string;
74
- messages: Message[];
75
- starterPrompts: string[];
76
- memoryFeedback: string | null;
77
- ingestionStates: Record<IngestionSourceType, IngestionState | null>;
78
- emergenceEvents: EmergenceEvent[];
79
- draft: string;
80
- streaming: boolean;
81
- imageData: string | null;
82
- streamRef: React.RefObject<HTMLDivElement | null>;
83
- memories: MemoryFragment[];
84
- concepts: KnowledgeConcept[];
85
- readiness: BrainReadiness;
86
- proof: BrainProof;
87
- uploadingDocument: boolean;
88
- onOpenDepth: (depth: BrainDepth) => void;
89
- onDraftChange: (value: string) => void;
90
- onImageDataChange: (value: string | null) => void;
91
- onUploadDocument: (file: File) => void;
92
- onConnectFolder: (path: string) => void;
93
- onIngestNote: (note: string) => void;
94
- onIngestWeb: (url: string) => void;
95
- onVerifyModelContinuity: () => void;
96
- onSend: () => void;
97
- }) {
98
- return (
99
- <section className="brain-conversation" aria-label={t(language, "brain.aria.conversation")}>
100
- <div className="brain-conversation-header">
101
- <div className="brain-conversation-title">
102
- <h1>
103
- {t(language, "brain.title")}
104
- <span className="brain-edition-badge" title={t(language, "brain.edition.tip")}>
105
- {t(language, "brain.edition")}
106
- </span>
107
- </h1>
108
- <span>{t(language, "brain.chatHome.headerLine")}</span>
109
- </div>
110
- <div className="brain-header-tools">
111
- <LanguageSwitcher compact />
112
- <div className="brain-model-pill">{modelName}</div>
113
- <button className="brain-admin-link" type="button" onClick={() => navigateHash("/admin")}>
114
- <ShieldCheck className="h-3.5 w-3.5" />
115
- {t(language, "brain.admin")}
116
- </button>
117
- </div>
118
- </div>
119
-
120
- <div className="brain-chat-home-layout">
121
- <section className="brain-chat-home-card" aria-label={t(language, "brain.chatHome.aria")}>
122
- <div className="brain-chat-home-head">
123
- <div>
124
- <span>{t(language, "brain.chatHome.kicker")}</span>
125
- <h2>{t(language, "brain.chatHome.title")}</h2>
126
- <p>{t(language, "brain.chatHome.body")}</p>
127
- </div>
128
- <div className="brain-chat-home-proof" aria-label={t(language, "brain.aria.ownership")}>
129
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.local")}</span>
130
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.private")}</span>
131
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.portable")}</span>
132
- </div>
133
- </div>
134
-
135
- <div ref={streamRef} className="brain-stream">
136
- {messages.length === 0 ? (
137
- <BrainEmptyState
138
- language={language}
139
- starterPrompts={starterPrompts}
140
- uploadingDocument={uploadingDocument}
141
- onDraftChange={onDraftChange}
142
- onUploadDocument={onUploadDocument}
143
- />
144
- ) : (
145
- messages.map((message, index) => {
146
- const messageId = `brain-msg-${index}`;
147
- const proof = message.role === "assistant" ? message.proof : undefined;
148
- return (
149
- <div key={`${message.role}-${index}`} className={`brain-message ${message.role}`}>
150
- <div className="brain-message-bubble">
151
- {message.content}
152
- {proof && proof.citations.length ? (
153
- <InlineCitationMarkers language={language} proof={proof} messageId={messageId} />
154
- ) : null}
155
- </div>
156
- {proof ? <AnswerProofCard language={language} proof={proof} messageId={messageId} /> : null}
157
- </div>
158
- );
159
- })
160
- )}
161
- </div>
162
-
163
- {memoryFeedback ? (
164
- <div className="brain-save-feedback" role="status">
165
- <DatabaseZap className="h-3.5 w-3.5" />
166
- <span>{memoryFeedback}</span>
167
- <small>{t(language, "brain.saved.detail")}</small>
168
- </div>
169
- ) : null}
170
-
171
- <BrainComposer
172
- language={language}
173
- draft={draft}
174
- streaming={streaming}
175
- imageData={imageData}
176
- uploadingDocument={uploadingDocument}
177
- onDraftChange={onDraftChange}
178
- onImageDataChange={onImageDataChange}
179
- onUploadDocument={onUploadDocument}
180
- onSend={onSend}
181
- />
182
-
183
- <details className="brain-utility-drawer">
184
- <summary>{t(language, "brain.chatHome.utility")}</summary>
185
- <div className="brain-utility-grid">
186
- <BrainIngestionPanel
187
- language={language}
188
- uploadingDocument={uploadingDocument}
189
- ingestionStates={ingestionStates}
190
- onUploadDocument={onUploadDocument}
191
- onConnectFolder={onConnectFolder}
192
- onIngestNote={onIngestNote}
193
- onIngestWeb={onIngestWeb}
194
- />
195
- <ProductCommandCenter
196
- language={language}
197
- readiness={readiness}
198
- proof={proof}
199
- modelName={modelName}
200
- memories={memories}
201
- concepts={concepts}
202
- emergenceEvents={emergenceEvents}
203
- onOpenDepth={onOpenDepth}
204
- onVerifyModelContinuity={onVerifyModelContinuity}
205
- />
206
- <IngestionTimelineSection language={language} emergenceEvents={emergenceEvents} />
207
- <ModelContinuityDemo
208
- language={language}
209
- proof={proof}
210
- modelName={modelName}
211
- onVerify={onVerifyModelContinuity}
212
- />
213
- <BrainOverviewPanel
214
- memories={memories}
215
- concepts={concepts}
216
- readiness={readiness}
217
- proof={proof}
218
- onOpenDepth={onOpenDepth}
219
- />
220
- <BrainCarePanel language={language} />
221
- </div>
222
- </details>
223
- </section>
224
- </div>
225
- </section>
226
- );
227
- }
228
-
229
- function ProductCommandCenter({
230
- language,
231
- readiness,
232
- proof,
233
- modelName,
234
- memories,
235
- concepts,
236
- emergenceEvents,
237
- onOpenDepth,
238
- onVerifyModelContinuity,
239
- }: {
240
- language: Language;
241
- readiness: BrainReadiness;
242
- proof: BrainProof;
243
- modelName: string;
244
- memories: MemoryFragment[];
245
- concepts: KnowledgeConcept[];
246
- emergenceEvents: EmergenceEvent[];
247
- onOpenDepth: (depth: BrainDepth) => void;
248
- onVerifyModelContinuity: () => void;
249
- }) {
250
- const score = Math.max(0, Math.min(100, readiness.score));
251
- const nextKey =
252
- readiness.state === "alive"
253
- ? "brain.command.next.alive"
254
- : readiness.state === "forming"
255
- ? "brain.command.next.forming"
256
- : "brain.command.next.empty";
257
- const recallable = (proof.recall && proof.recall.items) ? proof.recall.items.length : (proof.proofs ? (proof.proofs.durableItems || 1) : 1);
258
- const latestSource = emergenceEvents[0]?.label ?? t(language, "brain.command.source.empty");
259
-
260
- return (
261
- <section className="brain-command-center" aria-label={t(language, "brain.command.aria")}>
262
- <div className="brain-command-head">
263
- <div>
264
- <span>{t(language, "brain.command.kicker")}</span>
265
- <strong>{t(language, "brain.command.title")}</strong>
266
- </div>
267
- <div className="brain-command-score" role="meter" aria-valuemin={0} aria-valuemax={100} aria-valuenow={score}>
268
- <span>{t(language, "brain.command.score")}</span>
269
- <strong>{score}%</strong>
270
- </div>
271
- </div>
272
-
273
- <div className="brain-command-next">
274
- <BrainCircuit className="h-4 w-4" />
275
- <span>{t(language, "brain.command.next")}</span>
276
- <strong>{t(language, nextKey)}</strong>
277
- </div>
278
-
279
- <div className="brain-command-metrics" aria-label={t(language, "brain.command.metrics")}>
280
- <span>{t(language, "brain.command.metric.memories", { count: memories.length })}</span>
281
- <span>{t(language, "brain.command.metric.topics", { count: concepts.length })}</span>
282
- <span>{t(language, "brain.command.metric.sources", { count: readiness.signals.healthySources })}</span>
283
- <span>{t(language, "brain.command.metric.proof", { count: recallable })}</span>
284
- </div>
285
-
286
- <div className="brain-command-actions">
287
- <button type="button" aria-label={t(language, "brain.command.action.add")} onClick={() => navigateHash("/capture")}>
288
- <FileUp className="h-4 w-4" />
289
- <span>{t(language, "brain.command.action.add")}</span>
290
- <small>{latestSource}</small>
291
- </button>
292
- <button type="button" aria-label={t(language, "brain.command.action.find")} onClick={() => onOpenDepth(3)}>
293
- <Search className="h-4 w-4" />
294
- <span>{t(language, "brain.command.action.find")}</span>
295
- <small>{t(language, "brain.command.action.find.detail")}</small>
296
- </button>
297
- <button type="button" aria-label={t(language, "brain.command.action.proof")} onClick={onVerifyModelContinuity}>
298
- <Repeat2 className="h-4 w-4" />
299
- <span>{t(language, "brain.command.action.proof")}</span>
300
- <small>{proof.modelContinuity.proven ? proof.modelContinuity.activeModel || modelName : modelName}</small>
301
- </button>
302
- <button type="button" aria-label={t(language, "brain.command.action.own")} onClick={() => navigateHash("/settings")}>
303
- <HardDrive className="h-4 w-4" />
304
- <span>{t(language, "brain.command.action.own")}</span>
305
- <small>{t(language, "brain.command.action.own.detail")}</small>
306
- </button>
307
- </div>
308
-
309
- <div className="brain-command-signals">
310
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.command.signal.local")}</span>
311
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.command.signal.private")}</span>
312
- <span><CheckCircle2 className="h-3.5 w-3.5" />{t(language, "brain.command.signal.portable")}</span>
313
- </div>
314
- </section>
315
- );
316
- }
317
-
318
- function BrainIngestionPanel({
319
- language,
320
- uploadingDocument,
321
- ingestionStates,
322
- onUploadDocument,
323
- onConnectFolder,
324
- onIngestNote,
325
- onIngestWeb,
326
- }: {
327
- language: Language;
328
- uploadingDocument: boolean;
329
- ingestionStates: Record<IngestionSourceType, IngestionState | null>;
330
- onUploadDocument: (file: File) => void;
331
- onConnectFolder: (path: string) => void;
332
- onIngestNote: (note: string) => void;
333
- onIngestWeb: (url: string) => void;
334
- }) {
335
- const [folderPath, setFolderPath] = React.useState("");
336
- const [note, setNote] = React.useState("");
337
- const [url, setUrl] = React.useState("");
338
-
339
- return (
340
- <section className="brain-ingestion-panel" aria-label={t(language, "brain.ingest.aria")}>
341
- <div className="brain-ingestion-head">
342
- <span>{t(language, "brain.ingest.kicker")}</span>
343
- <strong>{t(language, "brain.ingest.title")}</strong>
344
- </div>
345
- <div className="brain-ingestion-grid">
346
- <label
347
- className={`brain-ingest-tile is-primary ${tileStateClass(ingestionStates.file)} ${uploadingDocument ? "is-disabled" : ""}`}
348
- >
349
- <FileUp className="h-4 w-4" />
350
- <span>{uploadingDocument ? t(language, "brain.upload.uploading") : t(language, "brain.ingest.file")}</span>
351
- <small>{t(language, "brain.ingest.file.detail")}</small>
352
- <input
353
- type="file"
354
- accept=".pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,application/pdf,text/plain,text/markdown,text/csv"
355
- className="sr-only"
356
- disabled={uploadingDocument}
357
- onChange={(event) => {
358
- const file = event.target.files?.[0];
359
- event.currentTarget.value = "";
360
- if (file) onUploadDocument(file);
361
- }}
362
- />
363
- <IngestionStageTrack language={language} state={ingestionStates.file} ctaKey="brain.ingest.cta.file" />
364
- </label>
365
- <form
366
- className={`brain-ingest-tile ${tileStateClass(ingestionStates.folder)}`}
367
- onSubmit={(event) => {
368
- event.preventDefault();
369
- onConnectFolder(folderPath);
370
- setFolderPath("");
371
- }}
372
- >
373
- <FolderPlus className="h-4 w-4" />
374
- <span>{t(language, "brain.ingest.folder")}</span>
375
- <input value={folderPath} onChange={(event) => setFolderPath(event.target.value)} placeholder={t(language, "brain.ingest.folder.placeholder")} />
376
- <IngestionStageTrack language={language} state={ingestionStates.folder} ctaKey="brain.ingest.cta.folder" />
377
- </form>
378
- <form
379
- className={`brain-ingest-tile ${tileStateClass(ingestionStates.note)}`}
380
- onSubmit={(event) => {
381
- event.preventDefault();
382
- onIngestNote(note);
383
- setNote("");
384
- }}
385
- >
386
- <FileText className="h-4 w-4" />
387
- <span>{t(language, "brain.ingest.note")}</span>
388
- <input value={note} onChange={(event) => setNote(event.target.value)} placeholder={t(language, "brain.ingest.note.placeholder")} />
389
- <IngestionStageTrack language={language} state={ingestionStates.note} ctaKey="brain.ingest.cta.note" />
390
- </form>
391
- <form
392
- className={`brain-ingest-tile ${tileStateClass(ingestionStates.web)}`}
393
- onSubmit={(event) => {
394
- event.preventDefault();
395
- onIngestWeb(url);
396
- setUrl("");
397
- }}
398
- >
399
- <Globe2 className="h-4 w-4" />
400
- <span>{t(language, "brain.ingest.web")}</span>
401
- <input value={url} onChange={(event) => setUrl(event.target.value)} placeholder={t(language, "brain.ingest.web.placeholder")} />
402
- <IngestionStageTrack language={language} state={ingestionStates.web} ctaKey="brain.ingest.cta.web" />
403
- </form>
404
- </div>
405
- </section>
406
- );
407
- }
408
-
409
- function tileStateClass(state: IngestionState | null): string {
410
- if (!state) return "";
411
- if (state.stage === "error") return "is-failed";
412
- if (state.stage === "complete") return "is-emerged";
413
- return "is-ingesting";
414
- }
415
-
416
- const STAGE_HINT_KEY: Record<IngestionPipelineStage, string> = {
417
- preparing: "brain.ingest.stage.preparing.hint",
418
- parsing: "brain.ingest.stage.parsing.hint",
419
- embedding: "brain.ingest.stage.embedding.hint",
420
- indexing: "brain.ingest.stage.indexing.hint",
421
- complete: "brain.ingest.stage.complete.hint",
422
- error: "brain.ingest.stage.error",
423
- };
424
-
425
- // Progressive disclosure of the pipeline: collect -> parse -> embed -> memorize.
426
- function IngestionStageTrack({
427
- language,
428
- state,
429
- ctaKey,
430
- }: {
431
- language: Language;
432
- state: IngestionState | null;
433
- ctaKey: string;
434
- }) {
435
- if (!state) {
436
- return <small className="brain-ingest-cta">{t(language, ctaKey)}</small>;
437
- }
438
-
439
- const activeIndex = INGESTION_STAGE_ORDER.indexOf(state.stage);
440
- const isError = state.stage === "error";
441
- const isComplete = state.stage === "complete";
442
- const hasEmergence = state.newMemories > 0 || state.newEntities > 0;
443
-
444
- return (
445
- <div
446
- className="brain-ingest-progress"
447
- role="status"
448
- aria-live="polite"
449
- aria-label={t(language, "brain.ingest.progress.aria", { label: state.label })}
450
- >
451
- <div className="brain-ingest-stage-badges" aria-hidden="true">
452
- {INGESTION_STAGE_ORDER.filter((stage) => stage !== "complete").map((stage) => {
453
- const index = INGESTION_STAGE_ORDER.indexOf(stage);
454
- const done = !isError && !isComplete && index < activeIndex;
455
- const active = !isError && !isComplete && index === activeIndex;
456
- const passed = isComplete;
457
- const cls = isError
458
- ? "is-failed"
459
- : passed || done
460
- ? "is-done"
461
- : active
462
- ? "is-active"
463
- : "";
464
- return (
465
- <span key={stage} className={`brain-ingest-stage-badge ${cls}`}>
466
- {(active || (isError && index === activeIndex)) ? <Loader2 className="h-3 w-3 brain-ingest-spin" /> : null}
467
- {t(language, `brain.ingest.stage.${stage}`)}
468
- </span>
469
- );
470
- })}
471
- </div>
472
- <small className={`brain-ingest-stage-hint ${isError ? "is-failed" : isComplete ? "is-emerged" : ""}`}>
473
- {isError
474
- ? t(language, "brain.ingest.stage.error")
475
- : isComplete
476
- ? hasEmergence
477
- ? t(language, "brain.ingest.result", { memories: state.newMemories, entities: state.newEntities })
478
- : t(language, "brain.ingest.result.empty")
479
- : t(language, STAGE_HINT_KEY[state.stage])}
480
- </small>
481
- </div>
482
- );
483
- }
484
-
485
- function relativeTime(language: Language, at: number, now: number): string {
486
- const diffMinutes = Math.floor((now - at) / 60000);
487
- if (diffMinutes < 1) return t(language, "brain.timeline.justNow");
488
- return t(language, "brain.timeline.minutesAgo", { count: diffMinutes });
489
- }
490
-
491
- function IngestionTimelineSection({
492
- language,
493
- emergenceEvents,
494
- }: {
495
- language: Language;
496
- emergenceEvents: EmergenceEvent[];
497
- }) {
498
- // Re-read the clock on each render of the parent so relative labels stay fresh-enough.
499
- const now = Date.now();
500
- return (
501
- <section
502
- className="brain-emergence-timeline"
503
- aria-label={t(language, "brain.timeline.aria")}
504
- aria-live="polite"
505
- >
506
- <div className="brain-emergence-head">
507
- <Sparkles className="h-3.5 w-3.5" />
508
- <strong>{t(language, "brain.timeline.emergenceTitle")}</strong>
509
- <span>{t(language, "brain.timeline.recent")}</span>
510
- </div>
511
- {emergenceEvents.length === 0 ? (
512
- <p className="brain-emergence-empty">{t(language, "brain.timeline.empty")}</p>
513
- ) : (
514
- <ol className="brain-emergence-list">
515
- {emergenceEvents.map((event) => (
516
- <li key={event.id} className="brain-emergence-item">
517
- <span className="brain-emergence-type">{t(language, INGESTION_TYPE_LABEL_KEY[event.sourceType])}</span>
518
- <span className="brain-emergence-label">{event.label}</span>
519
- <span className="brain-emergence-counts">
520
- <strong>{t(language, "brain.timeline.newMemories", { count: event.newMemories })}</strong>
521
- <strong>{t(language, "brain.timeline.newEntities", { count: event.newEntities })}</strong>
522
- </span>
523
- <time className="brain-emergence-at">{relativeTime(language, event.at, now)}</time>
524
- </li>
525
- ))}
526
- </ol>
527
- )}
528
- </section>
529
- );
530
- }
531
-
532
- function ModelContinuityDemo({
533
- language,
534
- proof,
535
- modelName,
536
- onVerify,
537
- }: {
538
- language: Language;
539
- proof: BrainProof;
540
- modelName: string;
541
- onVerify: () => void;
542
- }) {
543
- return (
544
- <section className="brain-model-demo" aria-label={t(language, "brain.modelDemo.aria")}>
545
- <div>
546
- <span>{t(language, "brain.modelDemo.kicker")}</span>
547
- <strong>{proof.modelContinuity.proven ? t(language, "brain.modelDemo.proven") : t(language, "brain.modelDemo.pending")}</strong>
548
- <small>{t(language, "brain.modelDemo.detail", { model: proof.modelContinuity.activeModel || modelName })}</small>
549
- </div>
550
- <button type="button" onClick={onVerify}>
551
- <Repeat2 className="h-3.5 w-3.5" />
552
- {t(language, "brain.modelDemo.verify")}
553
- </button>
554
- <button type="button" onClick={() => navigateHash("/models")}>
555
- <Cpu className="h-3.5 w-3.5" />
556
- {t(language, "brain.modelDemo.change")}
557
- </button>
558
- </section>
559
- );
560
- }
561
-
562
- function citationDomId(messageId: string, citationId: string): string {
563
- return `${messageId}-cite-${citationId}`;
564
- }
565
-
566
- // Inline, keyboard-focusable citation markers rendered next to the answer text.
567
- // Activating one moves focus to the matching row in the evidence card below.
568
- function InlineCitationMarkers({
569
- language,
570
- proof,
571
- messageId,
572
- }: {
573
- language: Language;
574
- proof: NonNullable<Message["proof"]>;
575
- messageId: string;
576
- }) {
577
- const focusCitation = (citationId: string) => {
578
- const target = document.getElementById(citationDomId(messageId, citationId));
579
- if (target) {
580
- target.scrollIntoView({ behavior: "smooth", block: "nearest" });
581
- target.focus();
582
- }
583
- };
584
- return (
585
- <span className="brain-inline-citations" aria-label={t(language, "brain.answerProof.citationsLabel", { count: proof.citations.length })}>
586
- {proof.citations.map((citation, index) => (
587
- <button
588
- key={citation.id}
589
- type="button"
590
- className="brain-inline-citation"
591
- aria-label={t(language, "brain.answerProof.marker", { index: index + 1 })}
592
- aria-controls={citationDomId(messageId, citation.id)}
593
- title={citation.title}
594
- onClick={() => focusCitation(citation.id)}
595
- >
596
- {index + 1}
597
- </button>
598
- ))}
599
- </span>
600
- );
601
- }
602
-
603
- function AnswerProofCard({ language, proof, messageId }: { language: Language; proof: NonNullable<Message["proof"]>; messageId: string }) {
604
- return (
605
- <section className="brain-answer-proof" role="group" aria-label={t(language, "brain.answerProof.aria")}>
606
- <div className="brain-answer-proof-head">
607
- <span>{t(language, "brain.answerProof.title")}</span>
608
- <strong>{proof.provenAcrossModels ? t(language, "brain.answerProof.modelProven", { model: proof.model }) : t(language, "brain.answerProof.modelPending", { model: proof.model })}</strong>
609
- {proof.citations.length ? (
610
- <small className="brain-answer-proof-count">{t(language, "brain.answerProof.citationsLabel", { count: proof.citations.length })}</small>
611
- ) : null}
612
- </div>
613
- {proof.citations.length ? (
614
- <ol>
615
- {proof.citations.map((citation, index) => (
616
- <li
617
- key={citation.id}
618
- id={citationDomId(messageId, citation.id)}
619
- tabIndex={-1}
620
- aria-label={t(language, "brain.answerProof.citationItem", { index: index + 1, title: citation.title })}
621
- >
622
- <span className="brain-answer-proof-index" aria-hidden="true">{index + 1}</span>
623
- <span>{citation.source}</span>
624
- <strong>{citation.title}</strong>
625
- <small>{citation.snippet || proof.query}</small>
626
- </li>
627
- ))}
628
- </ol>
629
- ) : (
630
- <small>{t(language, "brain.answerProof.empty")}</small>
631
- )}
632
- </section>
633
- );
634
- }
635
-
636
- function BrainEmptyState({
637
- language,
638
- starterPrompts,
639
- uploadingDocument,
640
- onDraftChange,
641
- onUploadDocument,
642
- }: {
643
- language: Language;
644
- starterPrompts: string[];
645
- uploadingDocument: boolean;
646
- onDraftChange: (value: string) => void;
647
- onUploadDocument: (file: File) => void;
648
- }) {
649
- return (
650
- <div className="mind-empty">
651
- <div className="mind-empty-kicker">{t(language, "brain.empty.kicker")}</div>
652
- <div className="mind-empty-title">{t(language, "brain.empty.title")}</div>
653
- <p>{t(language, "brain.empty.body")}</p>
654
- <label className={`mind-empty-upload ${uploadingDocument ? "is-disabled" : ""}`}>
655
- <DatabaseZap className="h-3.5 w-3.5" />
656
- <span>{uploadingDocument ? t(language, "brain.upload.uploading") : t(language, "brain.upload.cta")}</span>
657
- <input
658
- type="file"
659
- accept=".pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,application/pdf,text/plain,text/markdown,text/csv"
660
- className="sr-only"
661
- disabled={uploadingDocument}
662
- onChange={(event) => {
663
- const file = event.target.files?.[0];
664
- event.currentTarget.value = "";
665
- if (file) onUploadDocument(file);
666
- }}
667
- />
668
- </label>
669
- <small className="mind-empty-upload-hint">{t(language, "brain.upload.hint")}</small>
670
- <div className="mind-empty-prompts" aria-label={t(language, "brain.aria.starterPrompts")}>
671
- {starterPrompts.map((prompt) => (
672
- <button key={prompt} type="button" onClick={() => onDraftChange(prompt)}>
673
- {prompt}
674
- </button>
675
- ))}
676
- </div>
677
- <div className="mind-empty-trail" aria-label={t(language, "brain.empty.trail.label")}>
678
- <span>{t(language, "brain.empty.trail.save")}</span>
679
- <span>{t(language, "brain.empty.trail.recall")}</span>
680
- <span>{t(language, "brain.empty.trail.backup")}</span>
681
- </div>
682
- </div>
683
- );
684
- }
685
-
686
- function navigateHash(route: string) {
687
- window.location.hash = route;
688
- }