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,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
- }