ltcai 8.0.0 → 8.2.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 (183) hide show
  1. package/README.md +33 -72
  2. package/docs/CHANGELOG.md +52 -874
  3. package/docs/DEVELOPMENT.md +26 -3
  4. package/docs/LEGACY_COMPATIBILITY.md +3 -1
  5. package/docs/TRUST_MODEL.md +26 -26
  6. package/docs/WHY_LATTICE.md +28 -25
  7. package/docs/kg-schema.md +6 -4
  8. package/lattice_brain/__init__.py +1 -1
  9. package/lattice_brain/embeddings.py +9 -1
  10. package/lattice_brain/graph/schema.py +6 -0
  11. package/lattice_brain/runtime/multi_agent.py +1 -1
  12. package/latticeai/__init__.py +1 -1
  13. package/latticeai/api/computer_use.py +2 -0
  14. package/latticeai/api/memory.py +13 -0
  15. package/latticeai/app_factory.py +43 -162
  16. package/latticeai/brain/__init__.py +1 -1
  17. package/latticeai/core/config.py +10 -0
  18. package/latticeai/core/local_embeddings.py +8 -0
  19. package/latticeai/core/marketplace.py +1 -1
  20. package/latticeai/core/mcp_registry.py +140 -1
  21. package/latticeai/core/tool_registry.py +10 -0
  22. package/latticeai/core/workspace_graph_trace.py +132 -0
  23. package/latticeai/core/workspace_memory.py +75 -0
  24. package/latticeai/core/workspace_os.py +135 -1283
  25. package/latticeai/core/workspace_os_utils.py +132 -0
  26. package/latticeai/core/workspace_permissions.py +99 -0
  27. package/latticeai/core/workspace_plugins.py +97 -0
  28. package/latticeai/core/workspace_runs.py +612 -0
  29. package/latticeai/core/workspace_skills.py +114 -0
  30. package/latticeai/core/workspace_snapshots.py +195 -0
  31. package/latticeai/core/workspace_timeline.py +107 -0
  32. package/latticeai/runtime/audit_runtime.py +64 -0
  33. package/latticeai/runtime/config_runtime.py +4 -0
  34. package/latticeai/runtime/sso_runtime.py +52 -0
  35. package/latticeai/services/architecture_readiness.py +6 -5
  36. package/latticeai/services/memory_service.py +215 -7
  37. package/latticeai/services/model_capability_registry.py +68 -0
  38. package/latticeai/services/model_engines.py +603 -0
  39. package/latticeai/services/model_loading.py +482 -0
  40. package/latticeai/services/model_recommendation.py +5 -0
  41. package/latticeai/services/model_runtime.py +146 -837
  42. package/latticeai/services/product_readiness.py +19 -18
  43. package/package.json +2 -2
  44. package/scripts/build_frontend_assets.mjs +11 -2
  45. package/src-tauri/Cargo.lock +1 -1
  46. package/src-tauri/Cargo.toml +1 -1
  47. package/src-tauri/tauri.conf.json +1 -1
  48. package/static/app/asset-manifest.json +11 -102
  49. package/static/app/assets/Act-D9jIknFd.js +1 -0
  50. package/static/app/assets/Brain-CFOtWbPN.js +321 -0
  51. package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-Q4WYzwr5.js} +1 -2
  52. package/static/app/assets/Library-C5Q2yWee.js +1 -0
  53. package/static/app/assets/System-BLbjdr1_.js +1 -0
  54. package/static/app/assets/core-CwxXejkd.js +1 -2
  55. package/static/app/assets/index-BqammyNu.js +16 -0
  56. package/static/app/assets/index-ty1iGgZu.css +2 -0
  57. package/static/app/assets/primitives-Br8uSfZ4.js +1 -0
  58. package/static/app/assets/{textarea-BZk6ybp5.js → textarea-BnhNs1_X.js} +1 -2
  59. package/static/app/index.html +3 -10
  60. package/static/app/theme-boot.js +8 -0
  61. package/static/sw.js +1 -1
  62. package/tools/__init__.py +2 -1
  63. package/tools/computer.py +21 -0
  64. package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
  65. package/docs/HANDOVER_v3.6.0.md +0 -46
  66. package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
  67. package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
  68. package/docs/V2_ARCHITECTURE.md +0 -561
  69. package/docs/V3_2_AUDIT.md +0 -82
  70. package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
  71. package/docs/V3_FRONTEND.md +0 -140
  72. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
  73. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
  74. package/docs/V4_1_VALIDATION_REPORT.md +0 -47
  75. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
  76. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
  77. package/docs/V4_2_VALIDATION_REPORT.md +0 -89
  78. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
  79. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
  80. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
  81. package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
  82. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
  83. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
  84. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
  85. package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
  86. package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
  87. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
  88. package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
  89. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
  90. package/docs/V4_3_VALIDATION_REPORT.md +0 -58
  91. package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
  92. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
  93. package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
  94. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
  95. package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
  96. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
  97. package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
  98. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
  99. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
  100. package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
  101. package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
  102. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
  103. package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
  104. package/docs/V4_5_1_UX_REPORT.md +0 -45
  105. package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
  106. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
  107. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
  108. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
  109. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
  110. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
  111. package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
  112. package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
  113. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
  114. package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
  115. package/frontend/index.html +0 -24
  116. package/frontend/openapi.json +0 -15425
  117. package/frontend/src/App.tsx +0 -243
  118. package/frontend/src/api/client.ts +0 -580
  119. package/frontend/src/api/openapi.ts +0 -17892
  120. package/frontend/src/components/AdminAccessGate.tsx +0 -70
  121. package/frontend/src/components/FeedbackState.tsx +0 -45
  122. package/frontend/src/components/LanguageSwitcher.tsx +0 -23
  123. package/frontend/src/components/LivingBrain.tsx +0 -220
  124. package/frontend/src/components/ProductFlow.tsx +0 -171
  125. package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
  126. package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
  127. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
  128. package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
  129. package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
  130. package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
  131. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
  132. package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
  133. package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
  134. package/frontend/src/components/primitives.tsx +0 -392
  135. package/frontend/src/components/ui/badge.tsx +0 -27
  136. package/frontend/src/components/ui/button.tsx +0 -37
  137. package/frontend/src/components/ui/card.tsx +0 -22
  138. package/frontend/src/components/ui/input.tsx +0 -16
  139. package/frontend/src/components/ui/textarea.tsx +0 -16
  140. package/frontend/src/features/admin/AdminConsole.tsx +0 -334
  141. package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
  142. package/frontend/src/features/brain/BrainComposer.tsx +0 -85
  143. package/frontend/src/features/brain/BrainConversation.tsx +0 -688
  144. package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
  145. package/frontend/src/features/brain/BrainHome.tsx +0 -624
  146. package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
  147. package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
  148. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
  149. package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
  150. package/frontend/src/features/brain/brainData.ts +0 -246
  151. package/frontend/src/features/brain/graphLayout.ts +0 -37
  152. package/frontend/src/features/brain/types.ts +0 -150
  153. package/frontend/src/features/review/ReviewCard.tsx +0 -100
  154. package/frontend/src/features/review/ReviewInbox.tsx +0 -127
  155. package/frontend/src/features/review/reviewHelpers.ts +0 -69
  156. package/frontend/src/i18n.ts +0 -1303
  157. package/frontend/src/lib/utils.ts +0 -33
  158. package/frontend/src/main.tsx +0 -23
  159. package/frontend/src/pages/Act.tsx +0 -458
  160. package/frontend/src/pages/Ask.tsx +0 -14
  161. package/frontend/src/pages/Brain.tsx +0 -914
  162. package/frontend/src/pages/Capture.tsx +0 -258
  163. package/frontend/src/pages/Library.tsx +0 -486
  164. package/frontend/src/pages/System.tsx +0 -621
  165. package/frontend/src/routes.ts +0 -92
  166. package/frontend/src/store/appStore.ts +0 -94
  167. package/frontend/src/styles.css +0 -6579
  168. package/static/app/assets/Act-DOvf59ru.js +0 -2
  169. package/static/app/assets/Act-DOvf59ru.js.map +0 -1
  170. package/static/app/assets/Brain-C7_0mEiI.js +0 -322
  171. package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
  172. package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
  173. package/static/app/assets/Library-BJPEEm5O.js +0 -2
  174. package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
  175. package/static/app/assets/System-D6t9jo9V.js +0 -2
  176. package/static/app/assets/System-D6t9jo9V.js.map +0 -1
  177. package/static/app/assets/core-CwxXejkd.js.map +0 -1
  178. package/static/app/assets/index-C7g26IF6.css +0 -2
  179. package/static/app/assets/index-DbcEYJQ2.js +0 -17
  180. package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
  181. package/static/app/assets/primitives-CD38lt4n.js +0 -2
  182. package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
  183. 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
- }