ltcai 7.9.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 (107) hide show
  1. package/README.md +35 -49
  2. package/docs/CHANGELOG.md +35 -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/graph/projection.py +19 -5
  7. package/lattice_brain/graph/provenance.py +26 -4
  8. package/lattice_brain/graph/write_master.py +30 -3
  9. package/lattice_brain/runtime/multi_agent.py +1 -1
  10. package/latticeai/__init__.py +1 -1
  11. package/latticeai/app_factory.py +13 -134
  12. package/latticeai/core/local_embeddings.py +8 -0
  13. package/latticeai/core/marketplace.py +1 -1
  14. package/latticeai/core/mcp_registry.py +140 -1
  15. package/latticeai/core/tool_registry.py +7 -0
  16. package/latticeai/core/workspace_os.py +1 -1
  17. package/latticeai/services/architecture_readiness.py +50 -6
  18. package/latticeai/services/model_runtime.py +36 -2
  19. package/latticeai/services/product_readiness.py +19 -17
  20. package/package.json +2 -2
  21. package/scripts/build_frontend_assets.mjs +11 -2
  22. package/src-tauri/Cargo.lock +1 -1
  23. package/src-tauri/Cargo.toml +1 -1
  24. package/src-tauri/tauri.conf.json +1 -1
  25. package/static/app/asset-manifest.json +11 -102
  26. package/static/app/assets/Act-BOO66G-c.js +1 -0
  27. package/static/app/assets/Brain-C6lEYiD7.js +321 -0
  28. package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-TATXBRDw.js} +1 -2
  29. package/static/app/assets/Library-DK4FIp8a.js +1 -0
  30. package/static/app/assets/System-Bgs6Ql7x.js +1 -0
  31. package/static/app/assets/core-CwxXejkd.js +1 -2
  32. package/static/app/assets/index-Bvv79nre.js +16 -0
  33. package/static/app/assets/index-Dslqglia.css +2 -0
  34. package/static/app/assets/primitives-B70WOra0.js +1 -0
  35. package/static/app/assets/{textarea-BZk6ybp5.js → textarea-Czrd9gwM.js} +1 -2
  36. package/static/app/index.html +3 -10
  37. package/static/app/theme-boot.js +8 -0
  38. package/static/sw.js +1 -1
  39. package/frontend/index.html +0 -24
  40. package/frontend/openapi.json +0 -15425
  41. package/frontend/src/App.tsx +0 -243
  42. package/frontend/src/api/client.ts +0 -580
  43. package/frontend/src/api/openapi.ts +0 -17892
  44. package/frontend/src/components/AdminAccessGate.tsx +0 -70
  45. package/frontend/src/components/FeedbackState.tsx +0 -45
  46. package/frontend/src/components/LanguageSwitcher.tsx +0 -23
  47. package/frontend/src/components/LivingBrain.tsx +0 -220
  48. package/frontend/src/components/ProductFlow.tsx +0 -171
  49. package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
  50. package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
  51. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
  52. package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
  53. package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
  54. package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
  55. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
  56. package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
  57. package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
  58. package/frontend/src/components/primitives.tsx +0 -392
  59. package/frontend/src/components/ui/badge.tsx +0 -27
  60. package/frontend/src/components/ui/button.tsx +0 -37
  61. package/frontend/src/components/ui/card.tsx +0 -22
  62. package/frontend/src/components/ui/input.tsx +0 -16
  63. package/frontend/src/components/ui/textarea.tsx +0 -16
  64. package/frontend/src/features/admin/AdminConsole.tsx +0 -334
  65. package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
  66. package/frontend/src/features/brain/BrainComposer.tsx +0 -85
  67. package/frontend/src/features/brain/BrainConversation.tsx +0 -688
  68. package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
  69. package/frontend/src/features/brain/BrainHome.tsx +0 -624
  70. package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
  71. package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
  72. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
  73. package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
  74. package/frontend/src/features/brain/brainData.ts +0 -246
  75. package/frontend/src/features/brain/graphLayout.ts +0 -37
  76. package/frontend/src/features/brain/types.ts +0 -150
  77. package/frontend/src/features/review/ReviewCard.tsx +0 -100
  78. package/frontend/src/features/review/ReviewInbox.tsx +0 -127
  79. package/frontend/src/features/review/reviewHelpers.ts +0 -69
  80. package/frontend/src/i18n.ts +0 -1303
  81. package/frontend/src/lib/utils.ts +0 -33
  82. package/frontend/src/main.tsx +0 -23
  83. package/frontend/src/pages/Act.tsx +0 -458
  84. package/frontend/src/pages/Ask.tsx +0 -14
  85. package/frontend/src/pages/Brain.tsx +0 -914
  86. package/frontend/src/pages/Capture.tsx +0 -258
  87. package/frontend/src/pages/Library.tsx +0 -486
  88. package/frontend/src/pages/System.tsx +0 -621
  89. package/frontend/src/routes.ts +0 -92
  90. package/frontend/src/store/appStore.ts +0 -94
  91. package/frontend/src/styles.css +0 -6579
  92. package/static/app/assets/Act-DOvf59ru.js +0 -2
  93. package/static/app/assets/Act-DOvf59ru.js.map +0 -1
  94. package/static/app/assets/Brain-C7_0mEiI.js +0 -322
  95. package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
  96. package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
  97. package/static/app/assets/Library-BJPEEm5O.js +0 -2
  98. package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
  99. package/static/app/assets/System-D6t9jo9V.js +0 -2
  100. package/static/app/assets/System-D6t9jo9V.js.map +0 -1
  101. package/static/app/assets/core-CwxXejkd.js.map +0 -1
  102. package/static/app/assets/index-C7g26IF6.css +0 -2
  103. package/static/app/assets/index-DbcEYJQ2.js +0 -17
  104. package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
  105. package/static/app/assets/primitives-CD38lt4n.js +0 -2
  106. package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
  107. package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
@@ -1,624 +0,0 @@
1
- import * as React from "react";
2
- import { useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { latticeApi } from "@/api/client";
4
- import { type BrainState, LivingBrain, triggerBrainRecall } from "@/components/LivingBrain";
5
- import { useAppStore } from "@/store/appStore";
6
- import { t } from "@/i18n";
7
- import { BrainConversation } from "./BrainConversation";
8
- import { buildBrainProof, buildBrainReadiness, buildMemoryFragments, currentModelName, parseKnowledgeGraph } from "./brainData";
9
- import { DepthEmergence } from "./DepthEmergence";
10
- import {
11
- DEPTHS,
12
- INGESTION_STAGE_ORDER,
13
- type BrainDepth,
14
- type BrainProof,
15
- type EmergenceEvent,
16
- type IngestionPipelineStage,
17
- type IngestionSourceType,
18
- type IngestionState,
19
- type MemoryFragment,
20
- type Message,
21
- type MessageProof,
22
- } from "./types";
23
-
24
- export function BrainHome({
25
- brainState,
26
- intensity,
27
- onBrainChange,
28
- }: {
29
- brainState: BrainState;
30
- intensity: number;
31
- onBrainChange: (state: BrainState, intensity?: number) => void;
32
- }) {
33
- const qc = useQueryClient();
34
- const language = useAppStore((state) => state.language);
35
- const [messages, setMessages] = React.useState<Message[]>([]);
36
- const [draft, setDraft] = React.useState("");
37
- const [imageData, setImageData] = React.useState<string | null>(null);
38
- const [streaming, setStreaming] = React.useState(false);
39
- const [conversationId, setConversationId] = React.useState<string | null>(null);
40
- const [explorationDepth, setExplorationDepth] = React.useState<BrainDepth>(1);
41
- const [graphSearch, setGraphSearch] = React.useState("");
42
- const [selectedGraphId, setSelectedGraphId] = React.useState<string | null>(null);
43
- const [memoryFeedback, setMemoryFeedback] = React.useState<string | null>(null);
44
- const [uploadingDocument, setUploadingDocument] = React.useState(false);
45
- const [lastRecallQuery, setLastRecallQuery] = React.useState("");
46
- const [ingestionStates, setIngestionStates] = React.useState<Record<IngestionSourceType, IngestionState | null>>({
47
- file: null,
48
- folder: null,
49
- note: null,
50
- web: null,
51
- });
52
- const [emergenceEvents, setEmergenceEvents] = React.useState<EmergenceEvent[]>([]);
53
- const streamRef = React.useRef<HTMLDivElement>(null);
54
- const recallTimerRef = React.useRef<number | null>(null);
55
- const stageTimersRef = React.useRef<Record<IngestionSourceType, number[]>>({
56
- file: [],
57
- folder: [],
58
- note: [],
59
- web: [],
60
- });
61
- const pendingBaselineRef = React.useRef<
62
- Partial<Record<IngestionSourceType, { memories: number; entities: number; label: string }>>
63
- >({});
64
- // Source types whose request has resolved and are awaiting count settle to record emergence.
65
- const awaitingEmergenceRef = React.useRef<Set<IngestionSourceType>>(new Set());
66
- const settleTimerRef = React.useRef<number | null>(null);
67
-
68
- const memoriesQ = useQuery({ queryKey: ["memoryManager"], queryFn: latticeApi.memoryManager });
69
- const historyQ = useQuery({ queryKey: ["chatHistory"], queryFn: latticeApi.chatHistory });
70
- const graphQ = useQuery({ queryKey: ["graph"], queryFn: latticeApi.graph });
71
- const modelsQ = useQuery({ queryKey: ["models"], queryFn: latticeApi.models });
72
- const brainProofQ = useQuery({
73
- queryKey: ["memoryBrainProof", lastRecallQuery],
74
- queryFn: () => latticeApi.memoryBrainProof(lastRecallQuery, 3),
75
- });
76
-
77
- const memoryFragments = React.useMemo(
78
- () => buildMemoryFragments(memoriesQ.data?.data, historyQ.data?.data),
79
- [memoriesQ.data, historyQ.data],
80
- );
81
- const graphModel = React.useMemo(() => parseKnowledgeGraph(graphQ.data?.data), [graphQ.data]);
82
- const knowledgeConcepts = React.useMemo(
83
- () => graphModel.nodes.slice(0, 10),
84
- [graphModel.nodes],
85
- );
86
- const brainReadiness = React.useMemo(
87
- () => buildBrainReadiness(memoriesQ.data?.data, memoryFragments.length, knowledgeConcepts.length),
88
- [knowledgeConcepts.length, memoriesQ.data, memoryFragments.length],
89
- );
90
- const relationshipThreads = React.useMemo(
91
- () => graphModel.edges.slice(0, 10),
92
- [graphModel.edges],
93
- );
94
- const modelName = React.useMemo(() => currentModelName(modelsQ.data?.data), [modelsQ.data]);
95
- const brainProof = React.useMemo(
96
- () => buildBrainProof(brainProofQ.data?.data, modelName),
97
- [brainProofQ.data, modelName],
98
- );
99
- const currentDepth = DEPTHS[explorationDepth - 1];
100
- const starterPrompts = React.useMemo(
101
- () => [
102
- t(language, "brain.prompt.remember"),
103
- t(language, "brain.prompt.know"),
104
- t(language, "brain.prompt.plan"),
105
- ],
106
- [language],
107
- );
108
-
109
- React.useEffect(() => {
110
- if (streaming) onBrainChange("thinking", 0.94);
111
- else if (draft.trim().length > 4) onBrainChange("listening", 0.76);
112
- else onBrainChange(currentDepth.state, explorationDepth === 1 ? 0.58 : 0.66 + explorationDepth * 0.06);
113
- }, [streaming, draft, currentDepth.state, explorationDepth, onBrainChange]);
114
-
115
- React.useEffect(() => {
116
- const stream = streamRef.current;
117
- if (stream) stream.scrollTop = stream.scrollHeight;
118
- }, [messages]);
119
-
120
- React.useEffect(() => {
121
- return () => {
122
- if (recallTimerRef.current !== null) window.clearTimeout(recallTimerRef.current);
123
- for (const timers of Object.values(stageTimersRef.current)) {
124
- for (const timer of timers) window.clearTimeout(timer);
125
- }
126
- };
127
- }, []);
128
-
129
- const clearStageTimers = React.useCallback((sourceType: IngestionSourceType) => {
130
- for (const timer of stageTimersRef.current[sourceType]) window.clearTimeout(timer);
131
- stageTimersRef.current[sourceType] = [];
132
- }, []);
133
-
134
- const setStage = React.useCallback((sourceType: IngestionSourceType, stage: IngestionPipelineStage) => {
135
- setIngestionStates((prev) => {
136
- const current = prev[sourceType];
137
- if (!current) return prev;
138
- return {
139
- ...prev,
140
- [sourceType]: {
141
- ...current,
142
- stage,
143
- completedAt: stage === "complete" || stage === "error" ? Date.now() : current.completedAt,
144
- },
145
- };
146
- });
147
- }, []);
148
-
149
- const beginIngestion = React.useCallback(
150
- (sourceType: IngestionSourceType, label: string) => {
151
- clearStageTimers(sourceType);
152
- pendingBaselineRef.current[sourceType] = {
153
- memories: memoryFragments.length,
154
- entities: knowledgeConcepts.length,
155
- label,
156
- };
157
- setIngestionStates((prev) => ({
158
- ...prev,
159
- [sourceType]: {
160
- sourceType,
161
- label,
162
- stage: "preparing",
163
- startedAt: Date.now(),
164
- completedAt: null,
165
- newMemories: 0,
166
- newEntities: 0,
167
- },
168
- }));
169
- // Progressive disclosure of the in-flight pipeline while the request runs.
170
- const interim: IngestionPipelineStage[] = ["parsing", "embedding", "indexing"];
171
- interim.forEach((stage, index) => {
172
- const timer = window.setTimeout(() => setStage(sourceType, stage), 420 * (index + 1));
173
- stageTimersRef.current[sourceType].push(timer);
174
- });
175
- },
176
- [clearStageTimers, knowledgeConcepts.length, memoryFragments.length, setStage],
177
- );
178
-
179
- const resolveEmergence = React.useCallback(
180
- (sourceType: IngestionSourceType, memoryCount: number, entityCount: number) => {
181
- clearStageTimers(sourceType);
182
- const baseline = pendingBaselineRef.current[sourceType];
183
- const label = baseline?.label ?? "";
184
- // Snapshot deltas after invalidation lands; cap at >=0 to avoid noise.
185
- const newMemories = Math.max(0, memoryCount - (baseline?.memories ?? memoryCount));
186
- const newEntities = Math.max(0, entityCount - (baseline?.entities ?? entityCount));
187
- delete pendingBaselineRef.current[sourceType];
188
- setIngestionStates((prev) => {
189
- const current = prev[sourceType];
190
- if (!current) return prev;
191
- return {
192
- ...prev,
193
- [sourceType]: {
194
- ...current,
195
- stage: "complete",
196
- completedAt: Date.now(),
197
- newMemories,
198
- newEntities,
199
- },
200
- };
201
- });
202
- setEmergenceEvents((events) =>
203
- [
204
- {
205
- id: `${sourceType}-${Date.now()}`,
206
- sourceType,
207
- label,
208
- newMemories,
209
- newEntities,
210
- at: Date.now(),
211
- },
212
- ...events,
213
- ].slice(0, 10),
214
- );
215
- },
216
- [clearStageTimers],
217
- );
218
-
219
- // Once a request resolves we wait for the refetched counts to settle, then record the
220
- // real emergence delta. A fallback timer guarantees the panel never hangs on a stale count.
221
- const markAwaitingEmergence = React.useCallback(
222
- (sourceType: IngestionSourceType) => {
223
- awaitingEmergenceRef.current.add(sourceType);
224
- if (settleTimerRef.current !== null) window.clearTimeout(settleTimerRef.current);
225
- settleTimerRef.current = window.setTimeout(() => {
226
- for (const pending of Array.from(awaitingEmergenceRef.current)) {
227
- resolveEmergence(pending, memoryFragments.length, knowledgeConcepts.length);
228
- awaitingEmergenceRef.current.delete(pending);
229
- }
230
- }, 1600);
231
- },
232
- [knowledgeConcepts.length, memoryFragments.length, resolveEmergence],
233
- );
234
-
235
- // Flush awaiting ingestions as soon as the underlying counts change post-invalidation.
236
- React.useEffect(() => {
237
- if (awaitingEmergenceRef.current.size === 0) return;
238
- for (const pending of Array.from(awaitingEmergenceRef.current)) {
239
- const baseline = pendingBaselineRef.current[pending];
240
- if (!baseline) {
241
- awaitingEmergenceRef.current.delete(pending);
242
- continue;
243
- }
244
- if (memoryFragments.length !== baseline.memories || knowledgeConcepts.length !== baseline.entities) {
245
- resolveEmergence(pending, memoryFragments.length, knowledgeConcepts.length);
246
- awaitingEmergenceRef.current.delete(pending);
247
- }
248
- }
249
- }, [memoryFragments.length, knowledgeConcepts.length, resolveEmergence]);
250
-
251
- React.useEffect(() => {
252
- return () => {
253
- if (settleTimerRef.current !== null) window.clearTimeout(settleTimerRef.current);
254
- };
255
- }, []);
256
-
257
- const failIngestion = React.useCallback(
258
- (sourceType: IngestionSourceType, reason: string) => {
259
- clearStageTimers(sourceType);
260
- delete pendingBaselineRef.current[sourceType];
261
- setIngestionStates((prev) => {
262
- const current = prev[sourceType];
263
- if (!current) return prev;
264
- return {
265
- ...prev,
266
- [sourceType]: { ...current, stage: "error", completedAt: Date.now(), error: reason },
267
- };
268
- });
269
- },
270
- [clearStageTimers],
271
- );
272
-
273
- async function send() {
274
- const text = draft.trim();
275
- if (!text || streaming) return;
276
- const activeConversationId = conversationId || `brain-${Date.now()}`;
277
- if (!conversationId) setConversationId(activeConversationId);
278
-
279
- setMessages((items) => [...items, { role: "user", content: text }, { role: "assistant", content: "" }]);
280
- setDraft("");
281
- setImageData(null);
282
- setStreaming(true);
283
- setMemoryFeedback(null);
284
- onBrainChange("thinking", 0.96);
285
-
286
- try {
287
- const result = await latticeApi.streamChat(
288
- { message: text, conversation_id: activeConversationId, image_data: imageData || undefined },
289
- {
290
- onChunk: (_delta, fullText) => {
291
- setMessages((items) => {
292
- const next = [...items];
293
- next[next.length - 1] = { role: "assistant", content: fullText };
294
- return next;
295
- });
296
- },
297
- onTrace: (trace) => {
298
- if (!trace) return;
299
- onBrainChange("recalling", 0.9);
300
- triggerBrainRecall();
301
- if (recallTimerRef.current !== null) window.clearTimeout(recallTimerRef.current);
302
- recallTimerRef.current = window.setTimeout(() => onBrainChange("thinking", 0.9), 900);
303
- },
304
- },
305
- );
306
- if (result.error) {
307
- setMessages((items) => {
308
- const next = [...items];
309
- next[next.length - 1] = { role: "assistant", content: `${t(language, "brain.unavailable")}: ${result.error}` };
310
- return next;
311
- });
312
- } else {
313
- setMemoryFeedback(t(language, "brain.saved", { topics: knowledgeConcepts.length, memories: memoryFragments.length }));
314
- setLastRecallQuery(text);
315
- void attachAnswerProof(text);
316
- }
317
- } finally {
318
- setStreaming(false);
319
- void qc.invalidateQueries({ queryKey: ["chatHistory"] });
320
- void qc.invalidateQueries({ queryKey: ["memoryManager"] });
321
- void qc.invalidateQueries({ queryKey: ["graph"] });
322
- void qc.invalidateQueries({ queryKey: ["memoryBrainProof"] });
323
- }
324
- }
325
-
326
- async function attachAnswerProof(query: string) {
327
- const proofResult = await latticeApi.memoryBrainProof(query, 4);
328
- const proof = buildBrainProof(proofResult.data, modelName);
329
- const messageProof = toMessageProof(proof, query, modelName);
330
- setMessages((items) => {
331
- const next = [...items];
332
- for (let index = next.length - 1; index >= 0; index -= 1) {
333
- if (next[index].role === "assistant") {
334
- next[index] = { ...next[index], proof: messageProof };
335
- break;
336
- }
337
- }
338
- return next;
339
- });
340
- }
341
-
342
- async function uploadDocument(file: File) {
343
- if (uploadingDocument) return;
344
-
345
- setUploadingDocument(true);
346
- setMemoryFeedback(t(language, "brain.upload.pending", { name: file.name }));
347
- onBrainChange("recalling", 0.86);
348
- beginIngestion("file", file.name);
349
-
350
- try {
351
- const result = await latticeApi.uploadDocument(file);
352
- if (result.error) {
353
- setMemoryFeedback(t(language, "brain.upload.failed", { reason: result.error }));
354
- failIngestion("file", result.error);
355
- return;
356
- }
357
-
358
- setMemoryFeedback(t(language, "brain.upload.saved", { name: file.name }));
359
- setLastRecallQuery(file.name);
360
- triggerBrainRecall();
361
- void qc.invalidateQueries({ queryKey: ["memoryManager"] });
362
- void qc.invalidateQueries({ queryKey: ["graph"] });
363
- void qc.invalidateQueries({ queryKey: ["memoryBrainProof"] });
364
- markAwaitingEmergence("file");
365
- } finally {
366
- setUploadingDocument(false);
367
- }
368
- }
369
-
370
- async function connectFolder(path: string) {
371
- const target = path.trim();
372
- if (!target) return;
373
- setMemoryFeedback(t(language, "brain.ingest.folder.pending", { path: target }));
374
- onBrainChange("recalling", 0.84);
375
- beginIngestion("folder", target);
376
- const result = await latticeApi.connectFolder(target);
377
- if (result.error || !result.ok) {
378
- setMemoryFeedback(t(language, "brain.ingest.folder.failed", { reason: result.error || "unavailable" }));
379
- failIngestion("folder", result.error || "unavailable");
380
- return;
381
- }
382
- setMemoryFeedback(t(language, "brain.ingest.folder.saved", { path: target }));
383
- setLastRecallQuery(target);
384
- triggerBrainRecall();
385
- void refreshBrainProof(target);
386
- markAwaitingEmergence("folder");
387
- }
388
-
389
- async function ingestNote(note: string) {
390
- const content = note.trim();
391
- if (!content) return;
392
- setMemoryFeedback(t(language, "brain.ingest.note.pending"));
393
- onBrainChange("recalling", 0.84);
394
- beginIngestion("note", content.slice(0, 80));
395
- const result = await latticeApi.ingestNote(content, content.slice(0, 80));
396
- if (result.error || !result.ok) {
397
- setMemoryFeedback(t(language, "brain.ingest.note.failed", { reason: result.error || "unavailable" }));
398
- failIngestion("note", result.error || "unavailable");
399
- return;
400
- }
401
- setMemoryFeedback(t(language, "brain.ingest.note.saved"));
402
- setLastRecallQuery(content.slice(0, 120));
403
- triggerBrainRecall();
404
- void refreshBrainProof(content.slice(0, 120));
405
- markAwaitingEmergence("note");
406
- }
407
-
408
- async function ingestWeb(url: string) {
409
- const target = url.trim();
410
- if (!target) return;
411
- setMemoryFeedback(t(language, "brain.ingest.web.pending", { url: target }));
412
- onBrainChange("recalling", 0.84);
413
- beginIngestion("web", target);
414
- const result = await latticeApi.browserReadUrl(target);
415
- if (result.error || !result.ok) {
416
- setMemoryFeedback(t(language, "brain.ingest.web.failed", { reason: result.error || "unavailable" }));
417
- failIngestion("web", result.error || "unavailable");
418
- return;
419
- }
420
- setMemoryFeedback(t(language, "brain.ingest.web.saved", { url: target }));
421
- setLastRecallQuery(target);
422
- triggerBrainRecall();
423
- void refreshBrainProof(target);
424
- markAwaitingEmergence("web");
425
- }
426
-
427
- async function refreshBrainProof(query = lastRecallQuery) {
428
- await Promise.all([
429
- qc.invalidateQueries({ queryKey: ["memoryManager"] }),
430
- qc.invalidateQueries({ queryKey: ["graph"] }),
431
- qc.invalidateQueries({ queryKey: ["memoryBrainProof"] }),
432
- ]);
433
- if (query.trim()) {
434
- await attachAnswerProof(query);
435
- }
436
- }
437
-
438
- async function verifyModelContinuity() {
439
- const lastUserMessage = [...messages].reverse().find((message: Message) => message.role === "user");
440
- const query = lastRecallQuery || lastUserMessage?.content || "";
441
- if (!query.trim()) {
442
- setMemoryFeedback(t(language, "brain.modelDemo.needQuestion"));
443
- return;
444
- }
445
- setMemoryFeedback(t(language, "brain.modelDemo.checking", { model: modelName }));
446
- await attachAnswerProof(query);
447
- setLastRecallQuery(query);
448
- setMemoryFeedback(t(language, "brain.modelDemo.done", { model: modelName }));
449
- }
450
-
451
- function deepen() {
452
- setExplorationDepth((depth) => {
453
- const next = Math.min(5, depth + 1) as BrainDepth;
454
- const nextDepth = DEPTHS[next - 1];
455
- onBrainChange(nextDepth.state, 0.66 + next * 0.06);
456
- if (next >= 2) triggerBrainRecall();
457
- return next;
458
- });
459
- }
460
-
461
- function jumpToDepth(next: BrainDepth) {
462
- setExplorationDepth(next);
463
- const nextDepth = DEPTHS[next - 1];
464
- onBrainChange(nextDepth.state, next === 1 ? 0.58 : 0.66 + next * 0.06);
465
- if (next >= 2) triggerBrainRecall();
466
- }
467
-
468
- function surface() {
469
- setExplorationDepth(1);
470
- setSelectedGraphId(null);
471
- setGraphSearch("");
472
- onBrainChange("idle", 0.58);
473
- }
474
-
475
- function recallMemory(fragment: MemoryFragment) {
476
- triggerBrainRecall();
477
- setExplorationDepth((depth) => Math.max(depth, 2) as BrainDepth);
478
- setMessages((items) => [
479
- ...items,
480
- { role: "assistant", content: t(language, "brain.recalled", { title: fragment.title }) },
481
- ]);
482
- }
483
-
484
- return (
485
- <main className="brain-home" aria-label={t(language, "brain.aria.home")}>
486
- <section className="brain-presence" aria-label={t(language, "brain.aria.exploration")}>
487
- <div className="brain-exploration" data-depth={explorationDepth}>
488
- <LivingBrain
489
- state={brainState}
490
- intensity={intensity + explorationDepth * 0.035}
491
- size="large"
492
- depth={explorationDepth}
493
- showLabel={false}
494
- onInteract={deepen}
495
- />
496
-
497
- {explorationDepth > 1 ? (
498
- <>
499
- <BrainDepthRings explorationDepth={explorationDepth} onOpenDepth={jumpToDepth} />
500
-
501
- <div className="brain-depth-badge" aria-live="polite">
502
- <span>{t(language, "brain.level")} {explorationDepth}</span>
503
- <strong>{t(language, `brain.depth.${explorationDepth}`)}</strong>
504
- </div>
505
-
506
- <div className="brain-depth-actions" aria-label={t(language, "brain.aria.quickViews")}>
507
- <button type="button" className={explorationDepth === 2 ? "is-active" : ""} onClick={() => jumpToDepth(2)}>{t(language, "brain.view.memories")}</button>
508
- <button type="button" className={explorationDepth === 3 ? "is-active" : ""} onClick={() => jumpToDepth(3)}>{t(language, "brain.view.topics")}</button>
509
- <button type="button" className={explorationDepth === 4 ? "is-active" : ""} onClick={() => jumpToDepth(4)}>{t(language, "brain.view.relationships")}</button>
510
- <button type="button" className={explorationDepth === 5 ? "is-active" : ""} onClick={() => jumpToDepth(5)}>{t(language, "brain.view.graph")}</button>
511
- </div>
512
-
513
- <div className="brain-depth-rail" aria-label={t(language, "brain.depthRail.aria")}>
514
- {DEPTHS.map((depth) => (
515
- <button
516
- key={depth.level}
517
- type="button"
518
- className={depth.level <= explorationDepth ? "is-revealed" : ""}
519
- aria-current={depth.level === explorationDepth ? "step" : undefined}
520
- onClick={() => jumpToDepth(depth.level)}
521
- >
522
- <span>{depth.level}</span>
523
- <strong>{t(language, `brain.depth.${depth.level}`)}</strong>
524
- </button>
525
- ))}
526
- </div>
527
- </>
528
- ) : null}
529
-
530
- <div className="brain-field-layer" aria-hidden={explorationDepth < 2}>
531
- <DepthEmergence
532
- depth={explorationDepth}
533
- memories={memoryFragments}
534
- concepts={knowledgeConcepts}
535
- relationships={relationshipThreads}
536
- graphModel={graphModel}
537
- graphSearch={graphSearch}
538
- selectedGraphId={selectedGraphId}
539
- onGraphSearch={setGraphSearch}
540
- onSelectGraphNode={setSelectedGraphId}
541
- onRecallMemory={recallMemory}
542
- />
543
- </div>
544
-
545
- {explorationDepth > 1 ? (
546
- <button className="brain-surface-control" type="button" onClick={surface}>
547
- {t(language, "brain.surface")}
548
- </button>
549
- ) : null}
550
- </div>
551
- </section>
552
-
553
- <BrainConversation
554
- language={language}
555
- explorationDepth={explorationDepth}
556
- modelName={modelName}
557
- messages={messages}
558
- starterPrompts={starterPrompts}
559
- memoryFeedback={memoryFeedback}
560
- ingestionStates={ingestionStates}
561
- emergenceEvents={emergenceEvents}
562
- draft={draft}
563
- streaming={streaming}
564
- imageData={imageData}
565
- streamRef={streamRef}
566
- memories={memoryFragments}
567
- concepts={knowledgeConcepts}
568
- readiness={brainReadiness}
569
- proof={brainProof}
570
- uploadingDocument={uploadingDocument}
571
- onOpenDepth={jumpToDepth}
572
- onDraftChange={setDraft}
573
- onImageDataChange={setImageData}
574
- onUploadDocument={(file) => void uploadDocument(file)}
575
- onConnectFolder={(path) => void connectFolder(path)}
576
- onIngestNote={(note) => void ingestNote(note)}
577
- onIngestWeb={(url) => void ingestWeb(url)}
578
- onVerifyModelContinuity={() => void verifyModelContinuity()}
579
- onSend={() => void send()}
580
- />
581
- </main>
582
- );
583
- }
584
-
585
- function BrainDepthRings({
586
- explorationDepth,
587
- onOpenDepth,
588
- }: {
589
- explorationDepth: BrainDepth;
590
- onOpenDepth: (depth: BrainDepth) => void;
591
- }) {
592
- const language = useAppStore((state) => state.language);
593
- return (
594
- <div className="brain-memory-rings" aria-label={t(language, "brain.rings.aria")}>
595
- {DEPTHS.map((depth, index) => (
596
- <button
597
- key={depth.level}
598
- type="button"
599
- className={`brain-memory-ring ring-${depth.level} ${depth.level <= explorationDepth ? "is-revealed" : ""}`}
600
- aria-current={depth.level === explorationDepth ? "step" : undefined}
601
- onClick={() => onOpenDepth(depth.level)}
602
- style={{ "--ring-delay": `${index * 70}ms` } as React.CSSProperties}
603
- >
604
- <span>{depth.level}</span>
605
- <strong>{t(language, `brain.rings.${depth.level}`)}</strong>
606
- </button>
607
- ))}
608
- </div>
609
- );
610
- }
611
-
612
- function toMessageProof(proof: BrainProof, query: string, fallbackModelName: string): MessageProof {
613
- return {
614
- query,
615
- model: proof.modelContinuity.activeModel || fallbackModelName,
616
- provenAcrossModels: proof.modelContinuity.proven && proof.claims.keepsContextAcrossModels,
617
- citations: proof.recall.items.slice(0, 4).map((item) => ({
618
- id: item.id,
619
- source: item.source,
620
- title: item.title,
621
- snippet: item.snippet,
622
- })),
623
- };
624
- }
@@ -1,45 +0,0 @@
1
- import type { BrainDepth, MemoryFragment } from "./types";
2
- import { t } from "@/i18n";
3
- import { useAppStore } from "@/store/appStore";
4
- import { layerStyle, polarPoint } from "./graphLayout";
5
-
6
- export function BrainMemoryLayer({
7
- memories,
8
- depth,
9
- onRecallMemory,
10
- }: {
11
- memories: MemoryFragment[];
12
- depth: BrainDepth;
13
- onRecallMemory: (fragment: MemoryFragment) => void;
14
- }) {
15
- const language = useAppStore((state) => state.language);
16
- const visible = memories.slice(0, depth >= 3 ? 8 : 6);
17
- if (!visible.length) {
18
- return (
19
- <div className="memory-fragment is-empty">
20
- <span>{t(language, "brain.memory.empty.kicker")}</span>
21
- <strong>{t(language, "brain.memory.empty")}</strong>
22
- </div>
23
- );
24
- }
25
-
26
- return (
27
- <>
28
- {visible.map((memory, index) => {
29
- const point = polarPoint(index, visible.length, depth >= 3 ? 39 : 31, depth >= 3 ? 24 : 18, -112);
30
- return (
31
- <button
32
- key={memory.id}
33
- type="button"
34
- className="memory-fragment"
35
- style={layerStyle({ "--x": `${point.x}%`, "--y": `${point.y}%`, "--delay": `${index * 55}ms` })}
36
- onClick={() => onRecallMemory(memory)}
37
- >
38
- <span>{memory.kind}</span>
39
- <strong>{memory.title}</strong>
40
- </button>
41
- );
42
- })}
43
- </>
44
- );
45
- }