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,70 +0,0 @@
1
- import * as React from "react";
2
- import { ShieldCheck, Lock } from "lucide-react";
3
- import { t, type Language } from "@/i18n";
4
- import { useAppStore, type WorkspaceMode } from "@/store/appStore";
5
-
6
- const MODES: WorkspaceMode[] = ["basic", "advanced", "admin"];
7
-
8
- /**
9
- * Role-aware admin entry point for the Brain shell.
10
- *
11
- * Mode is the access proxy used across the app (SystemPage gates the admin
12
- * tab and AdminPanel on `mode`). When the user is in advanced/admin mode we
13
- * surface a discoverable Admin Console button plus a compact mode switcher.
14
- * In basic mode the console is hidden behind an explained affordance that
15
- * promotes the user to admin mode rather than failing silently.
16
- */
17
- export function AdminAccessGate({ language }: { language: Language }) {
18
- const mode = useAppStore((state) => state.mode);
19
- const setMode = useAppStore((state) => state.setMode);
20
- const canSeeAdmin = mode === "advanced" || mode === "admin";
21
-
22
- return (
23
- <div className="admin-access-gate">
24
- {canSeeAdmin ? (
25
- <button
26
- type="button"
27
- className="admin-access-button"
28
- title={t(language, "shell.admin.tooltip")}
29
- aria-label={t(language, "shell.admin.open")}
30
- onClick={() => {
31
- window.location.hash = "/admin";
32
- }}
33
- >
34
- <ShieldCheck className="h-3.5 w-3.5" aria-hidden="true" />
35
- <span>{t(language, "shell.admin.label")}</span>
36
- </button>
37
- ) : (
38
- <button
39
- type="button"
40
- className="admin-access-button is-locked"
41
- title={t(language, "shell.admin.needsMode")}
42
- aria-label={t(language, "shell.admin.needsMode")}
43
- onClick={() => setMode("admin")}
44
- >
45
- <Lock className="h-3.5 w-3.5" aria-hidden="true" />
46
- <span>{t(language, "shell.admin.enable")}</span>
47
- </button>
48
- )}
49
-
50
- <div
51
- className="mode-switcher-mini"
52
- role="group"
53
- aria-label={t(language, "shell.mode.label")}
54
- title={t(language, "shell.mode.info")}
55
- >
56
- {MODES.map((item) => (
57
- <button
58
- key={item}
59
- type="button"
60
- className={mode === item ? "is-active" : ""}
61
- aria-pressed={mode === item}
62
- onClick={() => setMode(item)}
63
- >
64
- {t(language, `shell.mode.${item}`)}
65
- </button>
66
- ))}
67
- </div>
68
- </div>
69
- );
70
- }
@@ -1,45 +0,0 @@
1
- import * as React from "react";
2
- import { AlertTriangle, Inbox, RotateCcw } from "lucide-react";
3
- import { t, type Language } from "@/i18n";
4
-
5
- export type FeedbackTone = "empty" | "error";
6
-
7
- /**
8
- * Shared empty / error feedback surface so every view tells the user what just
9
- * happened and what to do next, instead of a blank or silent failure.
10
- */
11
- export function FeedbackState({
12
- tone,
13
- language,
14
- title,
15
- body,
16
- actionLabel,
17
- onAction,
18
- }: {
19
- tone: FeedbackTone;
20
- language: Language;
21
- title: string;
22
- body?: string;
23
- actionLabel?: string;
24
- onAction?: () => void;
25
- }) {
26
- const isError = tone === "error";
27
- const resolvedActionLabel = actionLabel || (isError ? t(language, "feedback.retry") : undefined);
28
- return (
29
- <div className={`feedback-state is-${tone}`} role={isError ? "alert" : "status"}>
30
- <span className="feedback-state-icon" aria-hidden="true">
31
- {isError ? <AlertTriangle className="h-4 w-4" /> : <Inbox className="h-4 w-4" />}
32
- </span>
33
- <div className="feedback-state-body">
34
- <strong>{title}</strong>
35
- {body ? <span>{body}</span> : null}
36
- </div>
37
- {resolvedActionLabel && onAction ? (
38
- <button type="button" className="feedback-state-action" onClick={onAction}>
39
- {isError ? <RotateCcw className="h-3.5 w-3.5" /> : null}
40
- {resolvedActionLabel}
41
- </button>
42
- ) : null}
43
- </div>
44
- );
45
- }
@@ -1,23 +0,0 @@
1
- import { LANGUAGE_LABELS, t, type Language } from "@/i18n";
2
- import { useAppStore } from "@/store/appStore";
3
-
4
- export function LanguageSwitcher({ compact = false }: { compact?: boolean }) {
5
- const language = useAppStore((state) => state.language);
6
- const setLanguage = useAppStore((state) => state.setLanguage);
7
-
8
- return (
9
- <div className={compact ? "language-switcher compact" : "language-switcher"} aria-label={t(language, "language.label")}>
10
- {(["ko", "en"] as Language[]).map((item) => (
11
- <button
12
- key={item}
13
- type="button"
14
- className={language === item ? "is-active" : ""}
15
- onClick={() => setLanguage(item)}
16
- aria-pressed={language === item}
17
- >
18
- {LANGUAGE_LABELS[item]}
19
- </button>
20
- ))}
21
- </div>
22
- );
23
- }
@@ -1,220 +0,0 @@
1
- import * as React from "react";
2
- import { cn } from "@/lib/utils";
3
-
4
- export type BrainState = "idle" | "listening" | "thinking" | "recalling" | "synthesizing" | "planning" | "acting" | "resting";
5
-
6
- export interface LivingBrainProps {
7
- state?: BrainState;
8
- intensity?: number; // 0-1 how "alive" it feels right now
9
- onPulse?: () => void; // allow parent to trigger a memory pulse
10
- size?: "normal" | "large" | "trace";
11
- label?: string;
12
- className?: string;
13
- showLabel?: boolean;
14
- depth?: number; // 0-5 progressive exploration depth; higher = more "open" / revealing
15
- onInteract?: () => void; // called on click to advance exploration (travel deeper)
16
- }
17
-
18
- /**
19
- * The Living Brain — the primary visual and emotional object in the product.
20
- * It is not decoration. It is the other participant.
21
- * It breathes. It reacts. It remembers.
22
- */
23
- export function LivingBrain({
24
- state = "idle",
25
- intensity = 0.6,
26
- onPulse,
27
- size = "large",
28
- label,
29
- className,
30
- showLabel = true,
31
- depth = 0,
32
- onInteract,
33
- }: LivingBrainProps) {
34
- const [isPulsing, setIsPulsing] = React.useState(false);
35
- const organismRef = React.useRef<HTMLButtonElement>(null);
36
-
37
- // External trigger for memory / important recall moments
38
- React.useEffect(() => {
39
- if (onPulse) {
40
- const handler = () => firePulse();
41
- // allow global window event too for simplicity across components
42
- window.addEventListener("brain:recall", handler as EventListener);
43
- return () => window.removeEventListener("brain:recall", handler as EventListener);
44
- }
45
- }, [onPulse]);
46
-
47
- function firePulse() {
48
- setIsPulsing(true);
49
- if (organismRef.current) {
50
- organismRef.current.classList.add("pulse");
51
- // clean after animation
52
- window.setTimeout(() => {
53
- if (organismRef.current) organismRef.current.classList.remove("pulse");
54
- setIsPulsing(false);
55
- }, 1350);
56
- }
57
- onPulse?.();
58
- }
59
-
60
- // Auto gentle pulse when recalling or high intensity
61
- React.useEffect(() => {
62
- if ((state === "recalling" || intensity > 0.82) && !isPulsing) {
63
- const t = window.setTimeout(() => firePulse(), 180);
64
- return () => clearTimeout(t);
65
- }
66
- }, [state, intensity]);
67
-
68
- const dataState = state;
69
- const isLarge = size === "large";
70
- const isTrace = size === "trace";
71
-
72
- const dynamicIntensity = Math.max(0.35, Math.min(1, intensity));
73
- const effectiveDepth = Math.max(0, Math.min(5, depth || 0));
74
- const canTravel = state !== "thinking";
75
-
76
- const handleClick = () => {
77
- if (!canTravel) return;
78
- firePulse();
79
- onInteract?.();
80
- };
81
-
82
- return (
83
- <div
84
- className={cn(
85
- "brain-presence select-none",
86
- isLarge && "large",
87
- isTrace && "trace",
88
- className,
89
- effectiveDepth > 0 && "is-exploring"
90
- )}
91
- aria-label="Your living Brain"
92
- role="group"
93
- data-depth={effectiveDepth}
94
- >
95
- <button
96
- type="button"
97
- ref={organismRef}
98
- className={cn("brain-organism", `size-${size}`, `depth-${effectiveDepth}`)}
99
- data-state={dataState}
100
- aria-label={effectiveDepth < 5 ? "Travel deeper into your Brain" : "Rest inside the Knowledge Graph"}
101
- aria-disabled={!canTravel}
102
- style={{
103
- transform: `scale(${0.96 + (dynamicIntensity - 0.5) * 0.09 + effectiveDepth * 0.015})`,
104
- }}
105
- onClick={handleClick}
106
- title={effectiveDepth < 5 ? "Travel deeper into your Brain" : "The core of your knowledge"}
107
- >
108
- {/* Living anatomical presence. The glow opens with depth; the folds make it unmistakably a Brain. */}
109
- <div className="brain-core" style={{ transform: `scale(${1 + effectiveDepth * 0.045})` }}>
110
- <svg className="brain-anatomy" viewBox="0 0 220 174" aria-hidden>
111
- <path
112
- className="brain-lobe brain-lobe-left"
113
- d="M102 30c-13-20-44-19-55 1-18 1-29 16-28 33-13 8-18 25-11 39-9 16-1 36 17 42 5 19 27 26 43 15 13 10 33 8 43-5 5-7 8-16 8-27V52c0-8-6-17-17-22Z"
114
- />
115
- <path
116
- className="brain-lobe brain-lobe-right"
117
- d="M118 30c13-20 44-19 55 1 18 1 29 16 28 33 13 8 18 25 11 39 9 16 1 36-17 42-5 19-27 26-43 15-13 10-33 8-43-5-5-7-8-16-8-27V52c0-8 6-17 17-22Z"
118
- />
119
- <path className="brain-bridge" d="M103 48c9-8 24-8 33 0 7 6 9 16 5 25-5 11-16 15-31 12-15 3-26-1-31-12-4-9-2-19 5-25 5-4 12-6 19 0Z" />
120
- <path className="brain-stem" d="M92 137c10 9 26 9 36 0 1 14 7 25 20 33H76c12-8 17-19 16-33Z" />
121
- <path className="brain-fold fold-a" d="M48 50c18-11 38-8 47 8" />
122
- <path className="brain-fold fold-b" d="M34 82c22-8 45-5 58 8" />
123
- <path className="brain-fold fold-c" d="M43 119c18 5 35 2 49-11" />
124
- <path className="brain-fold fold-d" d="M172 50c-18-11-38-8-47 8" />
125
- <path className="brain-fold fold-e" d="M186 82c-22-8-45-5-58 8" />
126
- <path className="brain-fold fold-f" d="M177 119c-18 5-35 2-49-11" />
127
- <path className="brain-fold fold-mid" d="M110 38c-5 30-5 70 0 112" />
128
- </svg>
129
- </div>
130
-
131
- {/* Breathing field expands as we go deeper. */}
132
- <div
133
- className="brain-aura"
134
- style={{
135
- animationDuration: state === "thinking" ? "1.65s" : state === "recalling" ? "2.4s" : "6.8s",
136
- transform: `scale(${1 + effectiveDepth * 0.12})`,
137
- opacity: 0.65 + effectiveDepth * 0.05,
138
- boxShadow: effectiveDepth > 2 ? "0 0 60px hsl(var(--brain-core) / 0.25)" : "none"
139
- }}
140
- />
141
-
142
- {/* Thought activity — increases and starts to "resolve" into structure at higher depths */}
143
- <div className="thought-activity" aria-hidden>
144
- {Array.from({ length: Math.min(12, 5 + effectiveDepth * 2) }).map((_, i) => (
145
- <div
146
- key={i}
147
- className={cn("thought-particle", effectiveDepth >= 3 && "resolving")}
148
- style={{
149
- left: `${18 + ((i * 13 + effectiveDepth * 4) % 64)}%`,
150
- top: `${22 + (i % 5) * 14}%`,
151
- animationDelay: `-${i * 0.55 + (intensity * 1.1) - effectiveDepth * 0.2}s`,
152
- animationDuration: `${2.8 + (1 - dynamicIntensity) * 1.6 - effectiveDepth * 0.15}s`,
153
- }}
154
- />
155
- ))}
156
- </div>
157
-
158
- {/* Memory ripples — more and stronger as depth increases (echoes surfacing) */}
159
- {Array.from({ length: 1 + Math.floor(effectiveDepth / 1.5) }).map((_, i) => (
160
- <div
161
- key={i}
162
- className="memory-ripple"
163
- aria-hidden
164
- style={{
165
- inset: `${18 + i * 6}%`,
166
- animationDelay: `${180 + i * 220}ms`,
167
- opacity: 0.55 + effectiveDepth * 0.06
168
- }}
169
- />
170
- ))}
171
-
172
- {/* Relationship structure appears only near the deepest layers. */}
173
- {effectiveDepth >= 4 && (
174
- <svg className="brain-inner-structure" viewBox="0 0 100 100" aria-hidden>
175
- <g stroke="hsl(var(--brain-core) / 0.35)" strokeWidth="0.6" fill="none">
176
- <circle cx="50" cy="50" r="18" />
177
- <circle cx="50" cy="50" r="28" />
178
- <path d="M32 50 Q50 32 68 50" />
179
- <path d="M32 50 Q50 68 68 50" />
180
- {/* lattice connections for deeper feel */}
181
- <circle cx="25" cy="35" r="2" fill="hsl(var(--brain-core) / 0.4)" />
182
- <circle cx="75" cy="35" r="2" fill="hsl(var(--brain-core) / 0.4)" />
183
- <circle cx="25" cy="65" r="2" fill="hsl(var(--brain-core) / 0.4)" />
184
- <circle cx="75" cy="65" r="2" fill="hsl(var(--brain-core) / 0.4)" />
185
- <path d="M25 35 L50 50 L75 35" stroke="hsl(var(--knowledge)/0.3)" strokeWidth="0.4" />
186
- <path d="M25 65 L50 50 L75 65" stroke="hsl(var(--knowledge)/0.3)" strokeWidth="0.4" />
187
- </g>
188
- </svg>
189
- )}
190
- </button>
191
-
192
- {showLabel && !isTrace && (
193
- <div className="brain-presence-label" data-state={state}>
194
- <span className="dot" />
195
- {label || (effectiveDepth > 0 ? `Depth ${effectiveDepth}` : humanState(state))}
196
- </div>
197
- )}
198
- </div>
199
- );
200
- }
201
-
202
- function humanState(s: BrainState) {
203
- switch (s) {
204
- case "listening": return "Listening";
205
- case "thinking": return "Thinking with you";
206
- case "recalling": return "Remembering";
207
- case "synthesizing": return "Making sense";
208
- case "planning": return "Planning";
209
- case "acting": return "Acting";
210
- case "resting": return "With you";
211
- default: return "Here";
212
- }
213
- }
214
-
215
- // Helper to broadcast recall pulses from anywhere (conversation, memory surface, etc)
216
- export function triggerBrainRecall() {
217
- if (typeof window !== "undefined") {
218
- window.dispatchEvent(new CustomEvent("brain:recall"));
219
- }
220
- }
@@ -1,171 +0,0 @@
1
- import * as React from "react";
2
- import { latticeApi } from "@/api/client";
3
- import { type BrainState, LivingBrain } from "@/components/LivingBrain";
4
- import { t } from "@/i18n";
5
- import { useAppStore } from "@/store/appStore";
6
- import { ArrowRight, Cpu, Shield, Zap } from "lucide-react";
7
- import {
8
- AnalysisScreen,
9
- InstallScreen,
10
- LanguageChooser,
11
- LoginScreen,
12
- RecommendationScreen,
13
- buildRecommendations,
14
- fallbackModel,
15
- type FlowAnalysis,
16
- type FlowStep,
17
- type RecommendedModel,
18
- } from "@/components/onboarding/ProductFlowScreens";
19
-
20
- const FLOW_COMPLETE_KEY = "lattice.productFlow.complete";
21
-
22
- export function readProductFlowComplete() {
23
- try {
24
- return localStorage.getItem(FLOW_COMPLETE_KEY) === "true";
25
- } catch {}
26
- return false;
27
- }
28
-
29
- export function ProductFlow({ onComplete }: { onComplete: () => void }) {
30
- const language = useAppStore((state) => state.language);
31
- const [step, setStep] = React.useState<FlowStep>("wake");
32
- const [analysis, setAnalysis] = React.useState<FlowAnalysis | null>(null);
33
- const [analysisError, setAnalysisError] = React.useState<string | null>(null);
34
- const [selected, setSelected] = React.useState<RecommendedModel | null>(null);
35
-
36
- const recommendations = React.useMemo(() => buildRecommendations(analysis), [analysis]);
37
-
38
- React.useEffect(() => {
39
- if (step !== "analysis" || analysis) return;
40
- let cancelled = false;
41
- async function runAnalysis() {
42
- setAnalysisError(null);
43
- const [setup, recommendationsResult, models, sysinfo] = await Promise.all([
44
- latticeApi.setupScan(),
45
- latticeApi.modelRecommendations("local_mlx"),
46
- latticeApi.models(),
47
- latticeApi.sysinfo(),
48
- ]);
49
- if (cancelled) return;
50
- setAnalysis({
51
- setup: setup.ok ? setup.data as Record<string, unknown> : null,
52
- recommendations: recommendationsResult.ok ? recommendationsResult.data as Record<string, unknown> : null,
53
- models: models.ok ? models.data as Record<string, unknown> : null,
54
- sysinfo: sysinfo.ok ? sysinfo.data as Record<string, unknown> : null,
55
- });
56
- if (!setup.ok && !recommendationsResult.ok && !models.ok) {
57
- setAnalysisError(t(language, "flow.analysis.error"));
58
- }
59
- }
60
- void runAnalysis();
61
- return () => { cancelled = true; };
62
- }, [analysis, language, step]);
63
-
64
- return (
65
- <div className="ritual-shell" aria-label={t(language, "flow.shell")}>
66
- <div className="ritual-container">
67
- <LanguageChooser />
68
- <div className="ritual-brain">
69
- <LivingBrain state={brainStateForStep(step)} intensity={step === "install" ? 0.92 : 0.7} size="large" showLabel={false} />
70
- <div className="ritual-edition" title={t(language, "brain.edition.tip")}>
71
- {t(language, "brain.edition")}
72
- </div>
73
- </div>
74
-
75
- {step === "wake" && <WakeBrainScreen onWake={() => setStep("login")} onUseExisting={() => completeFlow(onComplete)} />}
76
-
77
- {step === "login" && <LoginScreen onSuccess={() => setStep("analysis")} />}
78
-
79
- {step === "analysis" && (
80
- <AnalysisScreen analysis={analysis} error={analysisError} onContinue={() => setStep("recommend")} />
81
- )}
82
-
83
- {step === "recommend" && (
84
- <RecommendationScreen
85
- recommendations={recommendations}
86
- onBack={() => setStep("analysis")}
87
- onSkipModel={() => completeFlow(onComplete)}
88
- onSelect={(model) => {
89
- setSelected(model);
90
- setStep("install");
91
- }}
92
- />
93
- )}
94
-
95
- {step === "install" && (
96
- <InstallScreen
97
- model={selected || recommendations[0] || fallbackModel()}
98
- onBack={() => setStep("recommend")}
99
- onComplete={() => completeFlow(onComplete)}
100
- onLater={() => completeFlow(onComplete)}
101
- />
102
- )}
103
- </div>
104
- </div>
105
- );
106
- }
107
-
108
- function WakeBrainScreen({ onWake, onUseExisting }: { onWake: () => void; onUseExisting: () => void }) {
109
- const language = useAppStore((state) => state.language);
110
- const valueCards = [
111
- { icon: Shield, label: t(language, "flow.wake.value.local.k"), body: t(language, "flow.wake.value.local.v") },
112
- { icon: Zap, label: t(language, "flow.wake.value.instant.k"), body: t(language, "flow.wake.value.instant.v") },
113
- { icon: Cpu, label: t(language, "flow.wake.value.brain.k"), body: t(language, "flow.wake.value.brain.v") },
114
- ];
115
- return (
116
- <section className="ritual-wake" aria-label={t(language, "flow.wake.aria")}>
117
- <div className="ritual-title">{t(language, "flow.wake.title")}</div>
118
- <div className="ritual-subtitle">{t(language, "flow.wake.body")}</div>
119
-
120
- <div className="ritual-value-grid" aria-label={t(language, "flow.wake.value.aria")}>
121
- {valueCards.map((card) => {
122
- const Icon = card.icon;
123
- return (
124
- <div className="ritual-value-card" key={card.label}>
125
- <div className="ritual-value-label"><Icon size={14} /> {card.label}</div>
126
- <div className="ritual-value-body">{card.body}</div>
127
- </div>
128
- );
129
- })}
130
- </div>
131
-
132
- <div className="ritual-wake-plan" aria-label={t(language, "flow.wake.plan.aria")}>
133
- <div>
134
- <span>1</span>
135
- <strong>{t(language, "flow.wake.step.identity")}</strong>
136
- </div>
137
- <div>
138
- <span>2</span>
139
- <strong>{t(language, "flow.wake.step.check")}</strong>
140
- </div>
141
- <div>
142
- <span>3</span>
143
- <strong>{t(language, "flow.wake.step.voice")}</strong>
144
- </div>
145
- </div>
146
-
147
- <div className="ritual-button-row ritual-button-row-primary">
148
- <button type="button" className="ritual-full-button ritual-full-button-primary" onClick={onWake}>
149
- {t(language, "flow.wake.primary")} <ArrowRight size={18} />
150
- </button>
151
- <button type="button" className="ritual-secondary-button" onClick={onUseExisting}>
152
- {t(language, "flow.wake.existing")}
153
- </button>
154
- </div>
155
- <div className="ritual-start-note">{t(language, "flow.wake.startNote")}</div>
156
- </section>
157
- );
158
- }
159
-
160
- function completeFlow(onComplete: () => void) {
161
- try { localStorage.setItem(FLOW_COMPLETE_KEY, "true"); } catch {}
162
- onComplete();
163
- }
164
-
165
- function brainStateForStep(step: FlowStep): BrainState {
166
- if (step === "wake") return "idle";
167
- if (step === "analysis") return "listening";
168
- if (step === "recommend") return "recalling";
169
- if (step === "install") return "thinking";
170
- return "idle";
171
- }