ltcai 6.1.0 → 6.3.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 (70) hide show
  1. package/README.md +36 -38
  2. package/docs/CHANGELOG.md +75 -1
  3. package/frontend/src/App.tsx +3 -1286
  4. package/frontend/src/components/LanguageSwitcher.tsx +23 -0
  5. package/frontend/src/components/ProductFlow.tsx +32 -669
  6. package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
  7. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
  8. package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
  9. package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
  10. package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
  11. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -0
  12. package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
  13. package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
  14. package/frontend/src/features/admin/AdminConsole.tsx +294 -0
  15. package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
  16. package/frontend/src/features/brain/BrainComposer.tsx +66 -0
  17. package/frontend/src/features/brain/BrainConversation.tsx +131 -0
  18. package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
  19. package/frontend/src/features/brain/BrainHome.tsx +232 -0
  20. package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
  21. package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
  22. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
  23. package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
  24. package/frontend/src/features/brain/brainData.ts +98 -0
  25. package/frontend/src/features/brain/graphLayout.ts +26 -0
  26. package/frontend/src/features/brain/types.ts +44 -0
  27. package/frontend/src/features/review/ReviewCard.tsx +3 -1
  28. package/frontend/src/features/review/ReviewInbox.tsx +11 -1
  29. package/frontend/src/i18n.ts +290 -0
  30. package/frontend/src/pages/Brain.tsx +63 -5
  31. package/frontend/src/pages/Capture.tsx +102 -13
  32. package/frontend/src/pages/Library.tsx +72 -6
  33. package/frontend/src/styles.css +220 -0
  34. package/lattice_brain/__init__.py +1 -1
  35. package/lattice_brain/runtime/multi_agent.py +1 -1
  36. package/latticeai/__init__.py +1 -1
  37. package/latticeai/api/tools.py +50 -23
  38. package/latticeai/app_factory.py +59 -76
  39. package/latticeai/cli/entrypoint.py +283 -0
  40. package/latticeai/core/marketplace.py +1 -1
  41. package/latticeai/core/workspace_os.py +1 -1
  42. package/latticeai/integrations/__init__.py +0 -0
  43. package/latticeai/integrations/telegram_bot.py +1009 -0
  44. package/latticeai/runtime/chat_wiring.py +119 -0
  45. package/latticeai/runtime/lifespan_runtime.py +1 -1
  46. package/latticeai/runtime/model_wiring.py +40 -0
  47. package/latticeai/runtime/platform_runtime_wiring.py +86 -0
  48. package/latticeai/runtime/review_wiring.py +37 -0
  49. package/latticeai/runtime/router_registration.py +49 -30
  50. package/latticeai/runtime/tail_wiring.py +21 -0
  51. package/latticeai/services/p_reinforce.py +258 -0
  52. package/latticeai/services/router_context.py +52 -0
  53. package/ltcai_cli.py +7 -279
  54. package/p_reinforce.py +4 -255
  55. package/package.json +3 -2
  56. package/scripts/check_i18n_literals.mjs +52 -0
  57. package/scripts/release_smoke.py +133 -0
  58. package/scripts/wheel_smoke.py +4 -0
  59. package/src-tauri/Cargo.lock +1 -1
  60. package/src-tauri/Cargo.toml +1 -1
  61. package/src-tauri/tauri.conf.json +1 -1
  62. package/static/app/asset-manifest.json +5 -5
  63. package/static/app/assets/index-D76dWuQk.js +16 -0
  64. package/static/app/assets/index-D76dWuQk.js.map +1 -0
  65. package/static/app/assets/index-Div5vMlq.css +2 -0
  66. package/static/app/index.html +2 -2
  67. package/telegram_bot.py +9 -1002
  68. package/static/app/assets/index-B744yblP.css +0 -2
  69. package/static/app/assets/index-DYaUKNfl.js +0 -16
  70. package/static/app/assets/index-DYaUKNfl.js.map +0 -1
@@ -0,0 +1,43 @@
1
+ import type { KnowledgeConcept, RelationshipThread } from "./types";
2
+ import { layoutGraphNodes } from "./graphLayout";
3
+
4
+ export function BrainRelationshipLayer({
5
+ concepts,
6
+ relationships,
7
+ }: {
8
+ concepts: KnowledgeConcept[];
9
+ relationships: RelationshipThread[];
10
+ }) {
11
+ const visibleConcepts = concepts.slice(0, 10);
12
+ const layout = layoutGraphNodes(visibleConcepts, 30, 20);
13
+ const positionById = new Map(layout.map((item) => [item.node.id, item]));
14
+ const visibleRelationships = relationships
15
+ .map((relationship, index) => {
16
+ const source = positionById.get(relationship.source) || layout[index % Math.max(layout.length, 1)];
17
+ const target = positionById.get(relationship.target) || layout[(index + 3) % Math.max(layout.length, 1)];
18
+ return source && target && source.node.id !== target.node.id ? { relationship, source, target } : null;
19
+ })
20
+ .filter(Boolean)
21
+ .slice(0, 8) as Array<{
22
+ relationship: RelationshipThread;
23
+ source: ReturnType<typeof layoutGraphNodes>[number];
24
+ target: ReturnType<typeof layoutGraphNodes>[number];
25
+ }>;
26
+
27
+ if (!visibleRelationships.length) return null;
28
+
29
+ return (
30
+ <svg className="relationship-weave" viewBox="0 0 100 100" aria-hidden>
31
+ {visibleRelationships.map(({ relationship, source, target }, index) => (
32
+ <line
33
+ key={`${relationship.id}-${index}`}
34
+ x1={source.x}
35
+ y1={source.y}
36
+ x2={target.x}
37
+ y2={target.y}
38
+ style={{ animationDelay: `${index * 80}ms` }}
39
+ />
40
+ ))}
41
+ </svg>
42
+ );
43
+ }
@@ -0,0 +1,53 @@
1
+ import type { BrainDepth, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
2
+ import { BrainGraphLayer, BrainKnowledgeLayer } from "./BrainGraphLayer";
3
+ import { BrainMemoryLayer } from "./BrainMemoryLayer";
4
+ import { BrainRelationshipLayer } from "./BrainRelationshipLayer";
5
+
6
+ export function DepthEmergence({
7
+ depth,
8
+ memories,
9
+ concepts,
10
+ relationships,
11
+ graphModel,
12
+ graphSearch,
13
+ selectedGraphId,
14
+ onGraphSearch,
15
+ onSelectGraphNode,
16
+ onRecallMemory,
17
+ }: {
18
+ depth: BrainDepth;
19
+ memories: MemoryFragment[];
20
+ concepts: KnowledgeConcept[];
21
+ relationships: RelationshipThread[];
22
+ graphModel: KnowledgeGraphModel;
23
+ graphSearch: string;
24
+ selectedGraphId: string | null;
25
+ onGraphSearch: (value: string) => void;
26
+ onSelectGraphNode: (id: string | null) => void;
27
+ onRecallMemory: (fragment: MemoryFragment) => void;
28
+ }) {
29
+ if (depth === 1) return null;
30
+
31
+ return (
32
+ <>
33
+ {depth >= 2 ? (
34
+ <BrainMemoryLayer memories={memories} depth={depth} onRecallMemory={onRecallMemory} />
35
+ ) : null}
36
+ {depth >= 3 && depth < 5 ? (
37
+ <BrainKnowledgeLayer concepts={concepts} depth={depth} />
38
+ ) : null}
39
+ {depth >= 4 && depth < 5 ? (
40
+ <BrainRelationshipLayer concepts={concepts} relationships={relationships} />
41
+ ) : null}
42
+ {depth >= 5 ? (
43
+ <BrainGraphLayer
44
+ model={graphModel}
45
+ search={graphSearch}
46
+ selectedId={selectedGraphId}
47
+ onSearch={onGraphSearch}
48
+ onSelect={onSelectGraphNode}
49
+ />
50
+ ) : null}
51
+ </>
52
+ );
53
+ }
@@ -0,0 +1,98 @@
1
+ import { asArray } from "@/lib/utils";
2
+ import type { ApiRecord, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
3
+ import { clamp } from "./graphLayout";
4
+
5
+ export function buildMemoryFragments(memoryData: unknown, historyData: unknown): MemoryFragment[] {
6
+ const memory = isRecord(memoryData) ? memoryData : {};
7
+ const sourceRows = asArray<ApiRecord>(memory.sources).length
8
+ ? asArray<ApiRecord>(memory.sources)
9
+ : asArray<ApiRecord>(memory.tiers);
10
+ const sourceFragments = sourceRows.map((item, index) => ({
11
+ id: textValue(item, ["id", "source", "label"], `memory-${index}`),
12
+ title: textValue(item, ["title", "label", "source", "path", "name"], "Workspace memory"),
13
+ kind: titleValue(item, ["type", "source_type", "kind", "health"], "Memory"),
14
+ }));
15
+ const conversationFragments = asArray<ApiRecord>(historyData).map((item, index) => ({
16
+ id: textValue(item, ["id", "conversation_id"], `conversation-${index}`),
17
+ title: textValue(item, ["title", "summary", "id"], "Conversation"),
18
+ kind: "Conversation",
19
+ }));
20
+
21
+ return uniqueById([...sourceFragments, ...conversationFragments]).slice(0, 10);
22
+ }
23
+
24
+ export function parseKnowledgeGraph(data: unknown): KnowledgeGraphModel {
25
+ const graph = isRecord(data) ? data : {};
26
+ const rawNodes = asArray<ApiRecord>(graph.nodes);
27
+ const rawEdges = asArray<ApiRecord>(graph.edges);
28
+ const nodes = rawNodes.flatMap((node): KnowledgeConcept[] => {
29
+ const id = textValue(node, ["id", "node_id", "title", "label"]);
30
+ if (!id) return [];
31
+ const metadata = isRecord(node.metadata) ? node.metadata : {};
32
+ const type = titleValue(node, ["type", "kind", "category"], "Concept");
33
+ const label = textValue(node, ["title", "label", "name"], id.replace(/^[^:]+:/, ""));
34
+ const summary = textValue(node, ["summary", "description", "snippet"]) || textValue(metadata, ["summary", "description", "relative_path", "filename"]);
35
+ const importance = clamp(numberValue(node, ["importance_norm", "importance", "score"]) || 0.5, 0.08, 1);
36
+ return [{ id, label, type, summary, importance }];
37
+ }).sort((left, right) => right.importance - left.importance);
38
+ const ids = new Set(nodes.map((node) => node.id));
39
+ const edges = rawEdges.flatMap((edge, index): RelationshipThread[] => {
40
+ const source = textValue(edge, ["from", "source", "source_id"]);
41
+ const target = textValue(edge, ["to", "target", "target_id"]);
42
+ if (!source || !target || !ids.has(source) || !ids.has(target)) return [];
43
+ return [{
44
+ id: textValue(edge, ["id"], `edge-${index}`),
45
+ source,
46
+ target,
47
+ label: titleValue(edge, ["type", "label", "relationship"], "Relates"),
48
+ weight: numberValue(edge, ["weight", "score", "confidence"]) || 1,
49
+ }];
50
+ });
51
+ return { nodes, edges };
52
+ }
53
+
54
+ export function currentModelName(data: unknown) {
55
+ const record = isRecord(data) ? data : {};
56
+ const current = textValue(record, ["current", "current_model", "local_model"]);
57
+ if (current) return current;
58
+ const loaded = asArray<ApiRecord>(record.loaded || record.loaded_models);
59
+ const firstLoaded = loaded.find((item) => item.id || item.name || item.model_id);
60
+ return firstLoaded ? textValue(firstLoaded, ["name", "id", "model_id"], "local mind") : "local mind";
61
+ }
62
+
63
+ function uniqueById<T extends { id: string }>(items: T[]) {
64
+ const seen = new Set<string>();
65
+ return items.filter((item) => {
66
+ if (seen.has(item.id)) return false;
67
+ seen.add(item.id);
68
+ return true;
69
+ });
70
+ }
71
+
72
+ export function isRecord(value: unknown): value is ApiRecord {
73
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
74
+ }
75
+
76
+ export function textValue(record: ApiRecord, keys: string[], fallback = "") {
77
+ for (const key of keys) {
78
+ const value = record[key];
79
+ if (typeof value === "string" && value.trim()) return value;
80
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
81
+ }
82
+ return fallback;
83
+ }
84
+
85
+ function titleValue(record: ApiRecord, keys: string[], fallback = "") {
86
+ const value = textValue(record, keys, fallback);
87
+ return value
88
+ .replace(/[_-]+/g, " ")
89
+ .replace(/\b\w/g, (character) => character.toUpperCase());
90
+ }
91
+
92
+ function numberValue(record: ApiRecord, keys: string[]) {
93
+ for (const key of keys) {
94
+ const value = Number(record[key]);
95
+ if (Number.isFinite(value)) return value;
96
+ }
97
+ return 0;
98
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import type { KnowledgeConcept } from "./types";
3
+
4
+ export function layoutGraphNodes(nodes: KnowledgeConcept[], radiusX: number, radiusY: number) {
5
+ return nodes.map((node, index) => {
6
+ const point = polarPoint(index, nodes.length, radiusX, radiusY, -88);
7
+ return { node, x: point.x, y: point.y };
8
+ });
9
+ }
10
+
11
+ export function polarPoint(index: number, total: number, radiusX: number, radiusY: number, offsetDegrees = -90) {
12
+ const count = Math.max(total, 1);
13
+ const angle = ((360 / count) * index + offsetDegrees) * Math.PI / 180;
14
+ return {
15
+ x: 50 + Math.cos(angle) * radiusX,
16
+ y: 50 + Math.sin(angle) * radiusY,
17
+ };
18
+ }
19
+
20
+ export function layerStyle(values: Record<string, string>) {
21
+ return values as React.CSSProperties;
22
+ }
23
+
24
+ export function clamp(value: number, min: number, max: number) {
25
+ return Math.max(min, Math.min(max, value));
26
+ }
@@ -0,0 +1,44 @@
1
+ import type { BrainState } from "@/components/LivingBrain";
2
+
3
+ export type ApiRecord = Record<string, unknown>;
4
+ export type BrainDepth = 1 | 2 | 3 | 4 | 5;
5
+
6
+ export type Message = {
7
+ role: "user" | "assistant";
8
+ content: string;
9
+ };
10
+
11
+ export type MemoryFragment = {
12
+ id: string;
13
+ title: string;
14
+ kind: string;
15
+ };
16
+
17
+ export type KnowledgeConcept = {
18
+ id: string;
19
+ label: string;
20
+ type: string;
21
+ summary: string;
22
+ importance: number;
23
+ };
24
+
25
+ export type RelationshipThread = {
26
+ id: string;
27
+ source: string;
28
+ target: string;
29
+ label: string;
30
+ weight: number;
31
+ };
32
+
33
+ export type KnowledgeGraphModel = {
34
+ nodes: KnowledgeConcept[];
35
+ edges: RelationshipThread[];
36
+ };
37
+
38
+ export const DEPTHS: Array<{ level: BrainDepth; labelKey: string; state: BrainState }> = [
39
+ { level: 1, labelKey: "brain.depthLabel.1", state: "idle" },
40
+ { level: 2, labelKey: "brain.depthLabel.2", state: "recalling" },
41
+ { level: 3, labelKey: "brain.depthLabel.3", state: "synthesizing" },
42
+ { level: 4, labelKey: "brain.depthLabel.4", state: "planning" },
43
+ { level: 5, labelKey: "brain.depthLabel.5", state: "synthesizing" },
44
+ ];
@@ -89,7 +89,9 @@ export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
89
89
  </div>
90
90
  ) : null}
91
91
  {feedback ? (
92
- <p className="mt-2 text-xs text-emerald-300">{feedback} - item stays open until you approve or dismiss.</p>
92
+ <p className={`mt-2 text-xs ${/fail|error|unavailable/i.test(feedback) ? "text-amber-300" : "text-emerald-300"}`}>
93
+ {feedback} - item stays open until you approve or dismiss.
94
+ </p>
93
95
  ) : null}
94
96
  </div>
95
97
  );
@@ -38,11 +38,21 @@ export function ReviewInbox() {
38
38
  action === "unsnooze" ? () => latticeApi.unsnoozeReviewItem(item.id) :
39
39
  () => latticeApi.runNowReviewItem(item.id);
40
40
  const result = await call();
41
+ if (!result.ok) {
42
+ setRunFeedback((prev) => ({
43
+ ...prev,
44
+ [item.id]: result.error || `${action} failed`,
45
+ }));
46
+ return result;
47
+ }
41
48
  if (result.ok) {
42
49
  if (action === "run_now") {
50
+ const payload = result.data.payload || {};
51
+ const provenance = result.data.provenance || {};
52
+ const runId = String(payload.last_run_id || provenance.run_id || "");
43
53
  setRunFeedback((prev) => ({
44
54
  ...prev,
45
- [item.id]: hadRunBefore ? "Regenerated" : "Executed",
55
+ [item.id]: runId ? `${hadRunBefore ? "Regenerated" : "Executed"} · ${runId}` : hadRunBefore ? "Regenerated" : "Executed",
46
56
  }));
47
57
  } else {
48
58
  setRunFeedback((prev) => {