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,365 +0,0 @@
1
- import * as React from "react";
2
- import { Filter, RotateCcw, Search, X } from "lucide-react";
3
- import { t } from "@/i18n";
4
- import { useAppStore } from "@/store/appStore";
5
- import type { KnowledgeConcept, KnowledgeGraphModel } from "./types";
6
- import { clamp, computeGraphNeighbors, layerStyle, layoutGraphNodes, polarPoint } from "./graphLayout";
7
-
8
- export function BrainKnowledgeLayer({ concepts, depth }: { concepts: KnowledgeConcept[]; depth: number }) {
9
- const language = useAppStore((state) => state.language);
10
- const visible = concepts.slice(0, depth >= 4 ? 10 : 7);
11
- if (!visible.length) return <div className="concept-signal is-empty">{t(language, "brain.knowledge.empty")}</div>;
12
-
13
- return (
14
- <>
15
- {visible.map((concept, index) => {
16
- const point = polarPoint(index, visible.length, 24, 15, -70);
17
- return (
18
- <button
19
- key={concept.id}
20
- type="button"
21
- className="concept-signal"
22
- style={layerStyle({ "--x": `${point.x}%`, "--y": `${point.y}%`, "--delay": `${index * 45}ms` })}
23
- title={concept.summary || concept.type}
24
- >
25
- <span>{concept.type}</span>
26
- {concept.label}
27
- </button>
28
- );
29
- })}
30
- </>
31
- );
32
- }
33
-
34
- const DAY_MS = 24 * 60 * 60 * 1000;
35
- // Time-exploration presets (days back). null == all time.
36
- const TIME_WINDOWS: Array<{ days: number | null }> = [{ days: 7 }, { days: 30 }, { days: 90 }, { days: null }];
37
- // A node added within this window of "now" is treated as recent for highlighting.
38
- const RECENT_WINDOW_MS = 7 * DAY_MS;
39
-
40
- // Highlight occurrences of `query` inside `label` by wrapping matches in <mark>.
41
- function highlightMatch(label: string, query: string): React.ReactNode {
42
- if (!query) return label;
43
- const lower = label.toLowerCase();
44
- const target = query.toLowerCase();
45
- const parts: React.ReactNode[] = [];
46
- let cursor = 0;
47
- let matchIndex = lower.indexOf(target, cursor);
48
- let key = 0;
49
- while (matchIndex !== -1 && target.length > 0) {
50
- if (matchIndex > cursor) parts.push(label.slice(cursor, matchIndex));
51
- parts.push(<mark key={`m-${key++}`}>{label.slice(matchIndex, matchIndex + target.length)}</mark>);
52
- cursor = matchIndex + target.length;
53
- matchIndex = lower.indexOf(target, cursor);
54
- }
55
- if (cursor < label.length) parts.push(label.slice(cursor));
56
- return parts.length ? parts : label;
57
- }
58
-
59
- export function BrainGraphLayer({
60
- model,
61
- search,
62
- selectedId,
63
- onSearch,
64
- onSelect,
65
- }: {
66
- model: KnowledgeGraphModel;
67
- search: string;
68
- selectedId: string | null;
69
- onSearch: (value: string) => void;
70
- onSelect: (id: string | null) => void;
71
- }) {
72
- const language = useAppStore((state) => state.language);
73
- const query = search.trim().toLowerCase();
74
-
75
- // Control state local to the graph view so existing lifted search/selection
76
- // behavior (and its reset in surface()) is preserved untouched.
77
- const [activeTypes, setActiveTypes] = React.useState<Set<string>>(() => new Set());
78
- const [timeDays, setTimeDays] = React.useState<number | null>(null);
79
- const [typeaheadOpen, setTypeaheadOpen] = React.useState(false);
80
-
81
- // Distinct entity types available in the graph (for the type filter chips).
82
- const availableTypes = React.useMemo(() => {
83
- const seen = new Set<string>();
84
- for (const node of model.nodes) seen.add(node.type);
85
- return Array.from(seen).sort((a, b) => a.localeCompare(b));
86
- }, [model.nodes]);
87
-
88
- // Drop type selections that no longer exist after a graph refresh.
89
- React.useEffect(() => {
90
- setActiveTypes((prev) => {
91
- if (prev.size === 0) return prev;
92
- const next = new Set<string>();
93
- for (const type of prev) if (availableTypes.includes(type)) next.add(type);
94
- return next.size === prev.size ? prev : next;
95
- });
96
- }, [availableTypes]);
97
-
98
- const hasTimestamps = React.useMemo(() => model.nodes.some((node) => typeof node.createdAt === "number"), [model.nodes]);
99
- const timeCutoff = timeDays === null ? null : Date.now() - timeDays * DAY_MS;
100
-
101
- const filtersActive = activeTypes.size > 0 || timeDays !== null;
102
-
103
- const visibleNodes = React.useMemo(() => {
104
- const filtered = model.nodes.filter((node) => {
105
- if (activeTypes.size > 0 && !activeTypes.has(node.type)) return false;
106
- if (timeCutoff !== null && typeof node.createdAt === "number" && node.createdAt < timeCutoff) return false;
107
- if (!query) return true;
108
- return `${node.label} ${node.type} ${node.summary}`.toLowerCase().includes(query);
109
- });
110
- return filtered.slice(0, 18);
111
- }, [model.nodes, query, activeTypes, timeCutoff]);
112
-
113
- // Typeahead suggestions: label-prefix/substring matches ranked by importance.
114
- const suggestions = React.useMemo(() => {
115
- if (!query) return [];
116
- return model.nodes
117
- .filter((node) => node.label.toLowerCase().includes(query) || node.type.toLowerCase().includes(query))
118
- .slice(0, 6);
119
- }, [model.nodes, query]);
120
-
121
- const layout = React.useMemo(() => layoutGraphNodes(visibleNodes, 38, 24), [visibleNodes]);
122
- const positionById = React.useMemo(() => new Map(layout.map((item) => [item.node.id, item])), [layout]);
123
- const visibleEdges = React.useMemo(
124
- () => model.edges.filter((edge) => positionById.has(edge.source) && positionById.has(edge.target)).slice(0, 36),
125
- [model.edges, positionById],
126
- );
127
- const selected = visibleNodes.find((node) => node.id === selectedId) || visibleNodes[0] || null;
128
-
129
- // 1-hop neighbor set for the focused node, drives focus/dim + edge highlight.
130
- const neighborIds = React.useMemo(
131
- () => (selectedId ? computeGraphNeighbors(selectedId, model.edges) : new Set<string>()),
132
- [selectedId, model.edges],
133
- );
134
- const focusActive = Boolean(selectedId) && neighborIds.size > 0;
135
-
136
- const matchedIds = React.useMemo(() => {
137
- if (!query) return new Set<string>();
138
- return new Set(visibleNodes.filter((node) => node.label.toLowerCase().includes(query)).map((node) => node.id));
139
- }, [visibleNodes, query]);
140
-
141
- function focusState(nodeId: string): "true" | "false" | undefined {
142
- if (!focusActive) return undefined;
143
- if (nodeId === selectedId || neighborIds.has(nodeId)) return "true";
144
- return "false";
145
- }
146
-
147
- return (
148
- <section className="mind-core-graph" data-testid="emergent-knowledge-graph" aria-label={t(language, "brain.aria.graph")}>
149
- <div className="brain-graph-head">
150
- <div>
151
- <span>{t(language, "brain.level")} 5</span>
152
- <strong>{t(language, "brain.depth.5")}</strong>
153
- </div>
154
- <div className="brain-graph-search-wrap">
155
- <label className="brain-graph-search">
156
- <Search className="h-3.5 w-3.5" />
157
- <input
158
- value={search}
159
- onChange={(event) => {
160
- onSearch(event.target.value);
161
- setTypeaheadOpen(true);
162
- }}
163
- onFocus={() => setTypeaheadOpen(true)}
164
- onBlur={() => window.setTimeout(() => setTypeaheadOpen(false), 120)}
165
- placeholder={t(language, "brain.graph.search")}
166
- aria-label={t(language, "brain.graph.searchAria")}
167
- role="combobox"
168
- aria-expanded={typeaheadOpen && Boolean(query)}
169
- aria-autocomplete="list"
170
- aria-controls="brain-graph-typeahead"
171
- />
172
- {search ? (
173
- <button
174
- type="button"
175
- className="brain-graph-search-clear"
176
- onClick={() => onSearch("")}
177
- aria-label={t(language, "brain.graph.focus.clear")}
178
- >
179
- <X className="h-3 w-3" />
180
- </button>
181
- ) : null}
182
- </label>
183
- {typeaheadOpen && query ? (
184
- <ul
185
- id="brain-graph-typeahead"
186
- className="brain-graph-typeahead"
187
- role="listbox"
188
- aria-label={t(language, "brain.graph.search.typeaheadAria")}
189
- >
190
- {suggestions.length ? (
191
- suggestions.map((node) => (
192
- <li key={node.id} role="option" aria-selected={node.id === selectedId}>
193
- <button
194
- type="button"
195
- onMouseDown={(event) => {
196
- event.preventDefault();
197
- onSearch(node.label);
198
- onSelect(node.id);
199
- setTypeaheadOpen(false);
200
- }}
201
- >
202
- <span>{node.type}</span>
203
- <strong>{highlightMatch(node.label, query)}</strong>
204
- </button>
205
- </li>
206
- ))
207
- ) : (
208
- <li className="is-empty" aria-disabled>
209
- {t(language, "brain.graph.search.noMatches")}
210
- </li>
211
- )}
212
- </ul>
213
- ) : null}
214
- </div>
215
- </div>
216
-
217
- <div className="brain-graph-control-panel" role="group" aria-label={t(language, "brain.graph.control.panel")}>
218
- <div className="brain-graph-filter-row">
219
- <span className="brain-graph-control-label">
220
- <Filter className="h-3 w-3" aria-hidden />
221
- {t(language, "brain.graph.control.types")}
222
- </span>
223
- <div className="brain-graph-type-chips" role="group" aria-label={t(language, "brain.graph.control.types")}>
224
- <button
225
- type="button"
226
- className={`brain-graph-chip ${activeTypes.size === 0 ? "is-active" : ""}`}
227
- aria-pressed={activeTypes.size === 0}
228
- onClick={() => setActiveTypes(new Set())}
229
- >
230
- {t(language, "brain.graph.control.allTypes")}
231
- </button>
232
- {availableTypes.map((type) => {
233
- const active = activeTypes.has(type);
234
- return (
235
- <button
236
- key={type}
237
- type="button"
238
- className={`brain-graph-chip ${active ? "is-active" : ""}`}
239
- aria-pressed={active}
240
- aria-label={t(language, "brain.graph.control.toggleType", { type })}
241
- onClick={() =>
242
- setActiveTypes((prev) => {
243
- const next = new Set(prev);
244
- if (next.has(type)) next.delete(type);
245
- else next.add(type);
246
- return next;
247
- })
248
- }
249
- >
250
- {type}
251
- </button>
252
- );
253
- })}
254
- </div>
255
- </div>
256
-
257
- <div className="brain-graph-filter-row">
258
- <span className="brain-graph-control-label">{t(language, "brain.graph.control.time")}</span>
259
- <div className="brain-graph-time-chips" role="group" aria-label={t(language, "brain.graph.control.dateRange")}>
260
- {TIME_WINDOWS.map((window) => {
261
- const active = timeDays === window.days;
262
- const label =
263
- window.days === null
264
- ? t(language, "brain.graph.control.allTime")
265
- : t(language, "brain.graph.control.recentWindow", { days: window.days });
266
- return (
267
- <button
268
- key={window.days ?? "all"}
269
- type="button"
270
- className={`brain-graph-chip ${active ? "is-active" : ""}`}
271
- aria-pressed={active}
272
- disabled={window.days !== null && !hasTimestamps}
273
- onClick={() => setTimeDays(window.days)}
274
- >
275
- {label}
276
- </button>
277
- );
278
- })}
279
- </div>
280
- {filtersActive ? (
281
- <button
282
- type="button"
283
- className="brain-graph-reset"
284
- onClick={() => {
285
- setActiveTypes(new Set());
286
- setTimeDays(null);
287
- }}
288
- >
289
- <RotateCcw className="h-3 w-3" aria-hidden />
290
- {t(language, "brain.graph.control.resetFilters")}
291
- </button>
292
- ) : null}
293
- </div>
294
- </div>
295
-
296
- {visibleNodes.length ? (
297
- <div className="brain-graph-canvas" data-focus-active={focusActive ? "true" : "false"}>
298
- <svg className="brain-graph-edges" viewBox="0 0 100 100" aria-hidden>
299
- {visibleEdges.map((edge, index) => {
300
- const source = positionById.get(edge.source);
301
- const target = positionById.get(edge.target);
302
- if (!source || !target) return null;
303
- const touchesFocus =
304
- focusActive && (edge.source === selectedId || edge.target === selectedId);
305
- const touchesMatch = matchedIds.has(edge.source) || matchedIds.has(edge.target);
306
- const highlight = touchesFocus || (query ? touchesMatch : false);
307
- return (
308
- <line
309
- key={`${edge.id}-${index}`}
310
- x1={source.x}
311
- y1={source.y}
312
- x2={target.x}
313
- y2={target.y}
314
- data-highlight={
315
- focusActive || query ? (highlight ? "true" : "false") : undefined
316
- }
317
- style={{ "--weight": String(clamp(edge.weight, 0.4, 2.8)) } as React.CSSProperties}
318
- />
319
- );
320
- })}
321
- </svg>
322
- {layout.map(({ node, x, y }, index) => {
323
- const isRecent =
324
- typeof node.createdAt === "number" && Date.now() - node.createdAt <= RECENT_WINDOW_MS;
325
- return (
326
- <button
327
- key={node.id}
328
- type="button"
329
- className={`graph-node ${selected?.id === node.id ? "is-selected" : ""} ${matchedIds.has(node.id) ? "is-match" : ""}`}
330
- data-focus={focusState(node.id)}
331
- data-recent={isRecent ? "true" : undefined}
332
- style={layerStyle({ "--x": `${x}%`, "--y": `${y}%`, "--delay": `${index * 35}ms` })}
333
- onClick={() => onSelect(selectedId === node.id ? null : node.id)}
334
- >
335
- <span>{node.type}</span>
336
- {highlightMatch(node.label, query)}
337
- </button>
338
- );
339
- })}
340
- </div>
341
- ) : (
342
- <div className="brain-graph-empty">{t(language, "brain.graph.empty")}</div>
343
- )}
344
-
345
- <div className="brain-graph-focus">
346
- {selected ? (
347
- <>
348
- <span>{selected.type}</span>
349
- <strong>{selected.label}</strong>
350
- <p>{selected.summary || t(language, "brain.graph.summaryFallback")}</p>
351
- {focusActive ? (
352
- <p className="brain-graph-focus-meta" role="status">
353
- {t(language, "brain.graph.focus.neighbors", { count: neighborIds.size })}
354
- </p>
355
- ) : (
356
- <p>{t(language, "brain.graph.focused")}</p>
357
- )}
358
- </>
359
- ) : (
360
- <p>{t(language, "brain.graph.emptyFocus")}</p>
361
- )}
362
- </div>
363
- </section>
364
- );
365
- }