ltcai 6.2.0 → 6.3.1

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 (51) hide show
  1. package/README.md +29 -17
  2. package/docs/CHANGELOG.md +66 -0
  3. package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
  4. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
  5. package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
  6. package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
  7. package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
  8. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -688
  9. package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
  10. package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
  11. package/frontend/src/features/admin/AdminConsole.tsx +1 -1
  12. package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
  13. package/frontend/src/features/brain/BrainComposer.tsx +66 -0
  14. package/frontend/src/features/brain/BrainConversation.tsx +131 -0
  15. package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
  16. package/frontend/src/features/brain/BrainHome.tsx +28 -795
  17. package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
  18. package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
  19. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
  20. package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
  21. package/frontend/src/features/brain/types.ts +6 -6
  22. package/frontend/src/features/review/ReviewCard.tsx +14 -10
  23. package/frontend/src/features/review/ReviewInbox.tsx +24 -9
  24. package/frontend/src/i18n.ts +208 -0
  25. package/frontend/src/pages/Brain.tsx +63 -5
  26. package/frontend/src/pages/Capture.tsx +139 -43
  27. package/frontend/src/pages/Library.tsx +72 -6
  28. package/lattice_brain/__init__.py +1 -1
  29. package/lattice_brain/runtime/multi_agent.py +1 -1
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/app_factory.py +48 -106
  32. package/latticeai/core/marketplace.py +1 -1
  33. package/latticeai/core/workspace_os.py +1 -1
  34. package/latticeai/runtime/access_runtime.py +97 -0
  35. package/latticeai/runtime/chat_wiring.py +119 -0
  36. package/latticeai/runtime/model_wiring.py +40 -0
  37. package/latticeai/runtime/platform_runtime_wiring.py +2 -3
  38. package/latticeai/runtime/review_wiring.py +37 -0
  39. package/latticeai/runtime/tail_wiring.py +21 -0
  40. package/package.json +2 -2
  41. package/scripts/check_i18n_literals.mjs +52 -0
  42. package/src-tauri/Cargo.lock +1 -1
  43. package/src-tauri/Cargo.toml +1 -1
  44. package/src-tauri/tauri.conf.json +1 -1
  45. package/static/app/asset-manifest.json +5 -5
  46. package/static/app/assets/index-Div5vMlq.css +2 -0
  47. package/static/app/assets/{index-D91Rz5--.js → index-t1jx1BR9.js} +2 -2
  48. package/static/app/assets/index-t1jx1BR9.js.map +1 -0
  49. package/static/app/index.html +2 -2
  50. package/static/app/assets/index-B2-1Gm0q.css +0 -2
  51. package/static/app/assets/index-D91Rz5--.js.map +0 -1
@@ -0,0 +1,38 @@
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) return <div className="memory-fragment is-empty">{t(language, "brain.memory.empty")}</div>;
18
+
19
+ return (
20
+ <>
21
+ {visible.map((memory, index) => {
22
+ const point = polarPoint(index, visible.length, depth >= 3 ? 39 : 31, depth >= 3 ? 24 : 18, -112);
23
+ return (
24
+ <button
25
+ key={memory.id}
26
+ type="button"
27
+ className="memory-fragment"
28
+ style={layerStyle({ "--x": `${point.x}%`, "--y": `${point.y}%`, "--delay": `${index * 55}ms` })}
29
+ onClick={() => onRecallMemory(memory)}
30
+ >
31
+ <span>{memory.kind}</span>
32
+ <strong>{memory.title}</strong>
33
+ </button>
34
+ );
35
+ })}
36
+ </>
37
+ );
38
+ }
@@ -0,0 +1,74 @@
1
+ import * as React from "react";
2
+ import { t } from "@/i18n";
3
+ import { useAppStore } from "@/store/appStore";
4
+ import type { BrainDepth, KnowledgeConcept, MemoryFragment } from "./types";
5
+
6
+ export function BrainOverviewPanel({
7
+ memories,
8
+ concepts,
9
+ onOpenDepth,
10
+ }: {
11
+ memories: MemoryFragment[];
12
+ concepts: KnowledgeConcept[];
13
+ onOpenDepth: (depth: BrainDepth) => void;
14
+ }) {
15
+ const language = useAppStore((state) => state.language);
16
+ const recent = memories.slice(0, 3);
17
+ const older = memories.slice(3, 6);
18
+ const topics = concepts.slice(0, 4);
19
+
20
+ return (
21
+ <section className="brain-overview-panel" aria-label={t(language, "brain.aria.overview")}>
22
+ <div className="brain-overview-head">
23
+ <div>
24
+ <span>{t(language, "brain.overview.kicker")}</span>
25
+ <strong>{t(language, "brain.overview.title")}</strong>
26
+ </div>
27
+ <button type="button" onClick={() => onOpenDepth(5)}>{t(language, "brain.overview.graph")}</button>
28
+ </div>
29
+ <div className="brain-overview-grid">
30
+ <BrainOverviewColumn
31
+ title={t(language, "brain.overview.recent")}
32
+ empty={t(language, "brain.overview.recentEmpty")}
33
+ items={recent.map((memory) => memory.title)}
34
+ onOpen={() => onOpenDepth(2)}
35
+ />
36
+ <BrainOverviewColumn
37
+ title={t(language, "brain.overview.older")}
38
+ empty={t(language, "brain.overview.olderEmpty")}
39
+ items={older.map((memory) => memory.title)}
40
+ onOpen={() => onOpenDepth(2)}
41
+ />
42
+ <BrainOverviewColumn
43
+ title={t(language, "brain.overview.topics")}
44
+ empty={t(language, "brain.overview.topicsEmpty")}
45
+ items={topics.map((concept) => concept.label)}
46
+ onOpen={() => onOpenDepth(3)}
47
+ />
48
+ </div>
49
+ </section>
50
+ );
51
+ }
52
+
53
+ function BrainOverviewColumn({
54
+ title,
55
+ empty,
56
+ items,
57
+ onOpen,
58
+ }: {
59
+ title: string;
60
+ empty: string;
61
+ items: string[];
62
+ onOpen: () => void;
63
+ }) {
64
+ return (
65
+ <button type="button" className="brain-overview-column" onClick={onOpen}>
66
+ <span>{title}</span>
67
+ {items.length ? (
68
+ items.slice(0, 3).map((item) => <strong key={item}>{item}</strong>)
69
+ ) : (
70
+ <em>{empty}</em>
71
+ )}
72
+ </button>
73
+ );
74
+ }
@@ -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
+ }
@@ -35,10 +35,10 @@ export type KnowledgeGraphModel = {
35
35
  edges: RelationshipThread[];
36
36
  };
37
37
 
38
- export const DEPTHS: Array<{ level: BrainDepth; label: string; state: BrainState }> = [
39
- { level: 1, label: "Living Brain", state: "idle" },
40
- { level: 2, label: "Memory Layer", state: "recalling" },
41
- { level: 3, label: "Knowledge Layer", state: "synthesizing" },
42
- { level: 4, label: "Relationship Layer", state: "planning" },
43
- { level: 5, label: "Knowledge Graph", state: "synthesizing" },
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
44
  ];
@@ -3,6 +3,7 @@ import type { ApiResult, ReviewItem } from "@/api/client";
3
3
  import { ActionButton, KeyValueList } from "@/components/primitives";
4
4
  import { Badge } from "@/components/ui/badge";
5
5
  import { Button } from "@/components/ui/button";
6
+ import { t } from "@/i18n";
6
7
  import { useAppStore } from "@/store/appStore";
7
8
  import {
8
9
  formatSnoozedUntil,
@@ -22,6 +23,7 @@ type ReviewCardProps = {
22
23
 
23
24
  export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
24
25
  const mode = useAppStore((state) => state.mode);
26
+ const language = useAppStore((state) => state.language);
25
27
  const provenance = item.provenance || {};
26
28
  const payload = item.payload || {};
27
29
  const hadRun = hasRunBefore(item);
@@ -45,10 +47,10 @@ export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
45
47
  <div className="mt-3 flex flex-wrap items-center justify-between gap-3 rounded-md border border-border bg-muted/24 p-3 text-sm">
46
48
  <div>
47
49
  <div className="font-medium">{formatSnoozedUntil(item.snoozed_until)}</div>
48
- <p className="mt-1 text-muted-foreground">This stays out of the pending queue until then. Unsnooze brings it back immediately.</p>
50
+ <p className="mt-1 text-muted-foreground">{t(language, "review.snoozed.detail")}</p>
49
51
  </div>
50
52
  <Button size="sm" variant="outline" onClick={() => onAction(item, "unsnooze")} disabled={!actionable}>
51
- <RotateCcw className="h-3.5 w-3.5" /> Unsnooze
53
+ <RotateCcw className="h-3.5 w-3.5" /> {t(language, "review.unsnooze")}
52
54
  </Button>
53
55
  </div>
54
56
  ) : null}
@@ -73,23 +75,25 @@ export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
73
75
  {actionable ? (
74
76
  <div className="mt-4 grid gap-2">
75
77
  <p className="text-xs leading-5 text-muted-foreground">
76
- Run now previews the action without approving it. Approve or dismiss when the result looks right.
78
+ {t(language, "review.runNow.detail")}
77
79
  </p>
78
- <div className="flex flex-wrap gap-2" aria-label="Review actions">
80
+ <div className="flex flex-wrap gap-2" aria-label={t(language, "review.actions.aria")}>
79
81
  <ActionButton
80
- label="Run now"
81
- successLabel={hadRun ? "Regenerated" : "Executed"}
82
+ label={t(language, "review.runNow")}
83
+ successLabel={hadRun ? t(language, "review.regenerated") : t(language, "review.executed")}
82
84
  action={() => onAction(item, "run_now", hadRun)}
83
85
  invalidate={[]}
84
86
  />
85
- <ActionButton label="Approve" action={() => onAction(item, "approve")} invalidate={[]} />
86
- {!snoozed ? <ActionButton label="Snooze 1 day" action={() => onAction(item, "snooze")} invalidate={[]} /> : null}
87
- <ActionButton label="Dismiss" action={() => onAction(item, "dismiss")} invalidate={[]} variant="destructive" />
87
+ <ActionButton label={t(language, "review.approve")} action={() => onAction(item, "approve")} invalidate={[]} />
88
+ {!snoozed ? <ActionButton label={t(language, "review.snoozeDay")} action={() => onAction(item, "snooze")} invalidate={[]} /> : null}
89
+ <ActionButton label={t(language, "review.dismiss")} action={() => onAction(item, "dismiss")} invalidate={[]} variant="destructive" />
88
90
  </div>
89
91
  </div>
90
92
  ) : null}
91
93
  {feedback ? (
92
- <p className="mt-2 text-xs text-emerald-300">{feedback} - item stays open until you approve or dismiss.</p>
94
+ <p className={`mt-2 text-xs ${/fail|error|unavailable/i.test(feedback) ? "text-amber-300" : "text-emerald-300"}`}>
95
+ {feedback} - {t(language, "review.feedback.open")}
96
+ </p>
93
97
  ) : null}
94
98
  </div>
95
99
  );
@@ -4,6 +4,8 @@ import { latticeApi, type ReviewItem, type ReviewSourceFilter, type ReviewStatus
4
4
  import { EmptyState, LoadingPanel, Tabs } from "@/components/primitives";
5
5
  import { Badge } from "@/components/ui/badge";
6
6
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
7
+ import { t } from "@/i18n";
8
+ import { useAppStore } from "@/store/appStore";
7
9
  import { ReviewCard } from "./ReviewCard";
8
10
  import {
9
11
  defaultSnoozeUntil,
@@ -13,6 +15,7 @@ import {
13
15
  } from "./reviewHelpers";
14
16
 
15
17
  export function ReviewInbox() {
18
+ const language = useAppStore((state) => state.language);
16
19
  const qc = useQueryClient();
17
20
  const [statusFilter, setStatusFilter] = React.useState<ReviewStatusFilter>("pending");
18
21
  const [sourceFilter, setSourceFilter] = React.useState<ReviewSourceFilter>("all");
@@ -38,11 +41,23 @@ export function ReviewInbox() {
38
41
  action === "unsnooze" ? () => latticeApi.unsnoozeReviewItem(item.id) :
39
42
  () => latticeApi.runNowReviewItem(item.id);
40
43
  const result = await call();
44
+ if (!result.ok) {
45
+ setRunFeedback((prev) => ({
46
+ ...prev,
47
+ [item.id]: result.error || t(language, "review.action.failed", { action }),
48
+ }));
49
+ return result;
50
+ }
41
51
  if (result.ok) {
42
52
  if (action === "run_now") {
53
+ const payload = result.data.payload || {};
54
+ const provenance = result.data.provenance || {};
55
+ const runId = String(payload.last_run_id || provenance.run_id || "");
43
56
  setRunFeedback((prev) => ({
44
57
  ...prev,
45
- [item.id]: hadRunBefore ? "Regenerated" : "Executed",
58
+ [item.id]: runId
59
+ ? `${hadRunBefore ? t(language, "review.regenerated") : t(language, "review.executed")} · ${runId}`
60
+ : hadRunBefore ? t(language, "review.regenerated") : t(language, "review.executed"),
46
61
  }));
47
62
  } else {
48
63
  setRunFeedback((prev) => {
@@ -56,18 +71,18 @@ export function ReviewInbox() {
56
71
  return result;
57
72
  };
58
73
 
59
- if (reviews.isLoading) return <LoadingPanel title="Review inbox" />;
74
+ if (reviews.isLoading) return <LoadingPanel title={t(language, "review.inbox.title")} />;
60
75
 
61
76
  return (
62
77
  <Card>
63
78
  <CardHeader className="gap-3">
64
79
  <div className="flex flex-wrap items-start justify-between gap-3">
65
80
  <div>
66
- <CardTitle>Review inbox</CardTitle>
67
- <CardDescription>Automation suggestions waiting for your decision. Run now executes without approving.</CardDescription>
81
+ <CardTitle>{t(language, "review.inbox.title")}</CardTitle>
82
+ <CardDescription>{t(language, "review.inbox.description")}</CardDescription>
68
83
  </div>
69
84
  {reviews.data ? (
70
- <Badge variant={reviews.data.ok ? "success" : "warning"}>{reviews.data.ok ? "connected" : "unavailable"}</Badge>
85
+ <Badge variant={reviews.data.ok ? "success" : "warning"}>{reviews.data.ok ? t(language, "review.status.connected") : t(language, "review.status.unavailable")}</Badge>
71
86
  ) : null}
72
87
  </div>
73
88
  <div className="grid gap-2">
@@ -86,13 +101,13 @@ export function ReviewInbox() {
86
101
  <CardContent>
87
102
  {reviews.isError || (reviews.data && !reviews.data.ok) ? (
88
103
  <EmptyState
89
- title="Could not load review inbox"
90
- detail={reviews.data?.error || "The review queue is not available right now."}
104
+ title={t(language, "review.inbox.loadError")}
105
+ detail={reviews.data?.error || t(language, "review.inbox.unavailable")}
91
106
  />
92
107
  ) : !items.length ? (
93
108
  <EmptyState
94
- title="Nothing to review"
95
- detail={statusFilter === "snoozed" ? "Snoozed items will appear here until they are unsnoozed or become pending again." : "When automations opt into the review queue, new suggestions will appear here."}
109
+ title={t(language, "review.inbox.empty")}
110
+ detail={statusFilter === "snoozed" ? t(language, "review.inbox.emptySnoozed") : t(language, "review.inbox.emptyPending")}
96
111
  />
97
112
  ) : (
98
113
  <div className="grid gap-3">