ltcai 8.0.0 → 8.2.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 (183) hide show
  1. package/README.md +33 -72
  2. package/docs/CHANGELOG.md +52 -874
  3. package/docs/DEVELOPMENT.md +26 -3
  4. package/docs/LEGACY_COMPATIBILITY.md +3 -1
  5. package/docs/TRUST_MODEL.md +26 -26
  6. package/docs/WHY_LATTICE.md +28 -25
  7. package/docs/kg-schema.md +6 -4
  8. package/lattice_brain/__init__.py +1 -1
  9. package/lattice_brain/embeddings.py +9 -1
  10. package/lattice_brain/graph/schema.py +6 -0
  11. package/lattice_brain/runtime/multi_agent.py +1 -1
  12. package/latticeai/__init__.py +1 -1
  13. package/latticeai/api/computer_use.py +2 -0
  14. package/latticeai/api/memory.py +13 -0
  15. package/latticeai/app_factory.py +43 -162
  16. package/latticeai/brain/__init__.py +1 -1
  17. package/latticeai/core/config.py +10 -0
  18. package/latticeai/core/local_embeddings.py +8 -0
  19. package/latticeai/core/marketplace.py +1 -1
  20. package/latticeai/core/mcp_registry.py +140 -1
  21. package/latticeai/core/tool_registry.py +10 -0
  22. package/latticeai/core/workspace_graph_trace.py +132 -0
  23. package/latticeai/core/workspace_memory.py +75 -0
  24. package/latticeai/core/workspace_os.py +135 -1283
  25. package/latticeai/core/workspace_os_utils.py +132 -0
  26. package/latticeai/core/workspace_permissions.py +99 -0
  27. package/latticeai/core/workspace_plugins.py +97 -0
  28. package/latticeai/core/workspace_runs.py +612 -0
  29. package/latticeai/core/workspace_skills.py +114 -0
  30. package/latticeai/core/workspace_snapshots.py +195 -0
  31. package/latticeai/core/workspace_timeline.py +107 -0
  32. package/latticeai/runtime/audit_runtime.py +64 -0
  33. package/latticeai/runtime/config_runtime.py +4 -0
  34. package/latticeai/runtime/sso_runtime.py +52 -0
  35. package/latticeai/services/architecture_readiness.py +6 -5
  36. package/latticeai/services/memory_service.py +215 -7
  37. package/latticeai/services/model_capability_registry.py +68 -0
  38. package/latticeai/services/model_engines.py +603 -0
  39. package/latticeai/services/model_loading.py +482 -0
  40. package/latticeai/services/model_recommendation.py +5 -0
  41. package/latticeai/services/model_runtime.py +146 -837
  42. package/latticeai/services/product_readiness.py +19 -18
  43. package/package.json +2 -2
  44. package/scripts/build_frontend_assets.mjs +11 -2
  45. package/src-tauri/Cargo.lock +1 -1
  46. package/src-tauri/Cargo.toml +1 -1
  47. package/src-tauri/tauri.conf.json +1 -1
  48. package/static/app/asset-manifest.json +11 -102
  49. package/static/app/assets/Act-D9jIknFd.js +1 -0
  50. package/static/app/assets/Brain-CFOtWbPN.js +321 -0
  51. package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-Q4WYzwr5.js} +1 -2
  52. package/static/app/assets/Library-C5Q2yWee.js +1 -0
  53. package/static/app/assets/System-BLbjdr1_.js +1 -0
  54. package/static/app/assets/core-CwxXejkd.js +1 -2
  55. package/static/app/assets/index-BqammyNu.js +16 -0
  56. package/static/app/assets/index-ty1iGgZu.css +2 -0
  57. package/static/app/assets/primitives-Br8uSfZ4.js +1 -0
  58. package/static/app/assets/{textarea-BZk6ybp5.js → textarea-BnhNs1_X.js} +1 -2
  59. package/static/app/index.html +3 -10
  60. package/static/app/theme-boot.js +8 -0
  61. package/static/sw.js +1 -1
  62. package/tools/__init__.py +2 -1
  63. package/tools/computer.py +21 -0
  64. package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
  65. package/docs/HANDOVER_v3.6.0.md +0 -46
  66. package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
  67. package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
  68. package/docs/V2_ARCHITECTURE.md +0 -561
  69. package/docs/V3_2_AUDIT.md +0 -82
  70. package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
  71. package/docs/V3_FRONTEND.md +0 -140
  72. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
  73. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
  74. package/docs/V4_1_VALIDATION_REPORT.md +0 -47
  75. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
  76. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
  77. package/docs/V4_2_VALIDATION_REPORT.md +0 -89
  78. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
  79. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
  80. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
  81. package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
  82. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
  83. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
  84. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
  85. package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
  86. package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
  87. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
  88. package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
  89. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
  90. package/docs/V4_3_VALIDATION_REPORT.md +0 -58
  91. package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
  92. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
  93. package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
  94. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
  95. package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
  96. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
  97. package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
  98. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
  99. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
  100. package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
  101. package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
  102. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
  103. package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
  104. package/docs/V4_5_1_UX_REPORT.md +0 -45
  105. package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
  106. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
  107. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
  108. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
  109. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
  110. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
  111. package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
  112. package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
  113. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
  114. package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
  115. package/frontend/index.html +0 -24
  116. package/frontend/openapi.json +0 -15425
  117. package/frontend/src/App.tsx +0 -243
  118. package/frontend/src/api/client.ts +0 -580
  119. package/frontend/src/api/openapi.ts +0 -17892
  120. package/frontend/src/components/AdminAccessGate.tsx +0 -70
  121. package/frontend/src/components/FeedbackState.tsx +0 -45
  122. package/frontend/src/components/LanguageSwitcher.tsx +0 -23
  123. package/frontend/src/components/LivingBrain.tsx +0 -220
  124. package/frontend/src/components/ProductFlow.tsx +0 -171
  125. package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
  126. package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
  127. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
  128. package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
  129. package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
  130. package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
  131. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
  132. package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
  133. package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
  134. package/frontend/src/components/primitives.tsx +0 -392
  135. package/frontend/src/components/ui/badge.tsx +0 -27
  136. package/frontend/src/components/ui/button.tsx +0 -37
  137. package/frontend/src/components/ui/card.tsx +0 -22
  138. package/frontend/src/components/ui/input.tsx +0 -16
  139. package/frontend/src/components/ui/textarea.tsx +0 -16
  140. package/frontend/src/features/admin/AdminConsole.tsx +0 -334
  141. package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
  142. package/frontend/src/features/brain/BrainComposer.tsx +0 -85
  143. package/frontend/src/features/brain/BrainConversation.tsx +0 -688
  144. package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
  145. package/frontend/src/features/brain/BrainHome.tsx +0 -624
  146. package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
  147. package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
  148. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
  149. package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
  150. package/frontend/src/features/brain/brainData.ts +0 -246
  151. package/frontend/src/features/brain/graphLayout.ts +0 -37
  152. package/frontend/src/features/brain/types.ts +0 -150
  153. package/frontend/src/features/review/ReviewCard.tsx +0 -100
  154. package/frontend/src/features/review/ReviewInbox.tsx +0 -127
  155. package/frontend/src/features/review/reviewHelpers.ts +0 -69
  156. package/frontend/src/i18n.ts +0 -1303
  157. package/frontend/src/lib/utils.ts +0 -33
  158. package/frontend/src/main.tsx +0 -23
  159. package/frontend/src/pages/Act.tsx +0 -458
  160. package/frontend/src/pages/Ask.tsx +0 -14
  161. package/frontend/src/pages/Brain.tsx +0 -914
  162. package/frontend/src/pages/Capture.tsx +0 -258
  163. package/frontend/src/pages/Library.tsx +0 -486
  164. package/frontend/src/pages/System.tsx +0 -621
  165. package/frontend/src/routes.ts +0 -92
  166. package/frontend/src/store/appStore.ts +0 -94
  167. package/frontend/src/styles.css +0 -6579
  168. package/static/app/assets/Act-DOvf59ru.js +0 -2
  169. package/static/app/assets/Act-DOvf59ru.js.map +0 -1
  170. package/static/app/assets/Brain-C7_0mEiI.js +0 -322
  171. package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
  172. package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
  173. package/static/app/assets/Library-BJPEEm5O.js +0 -2
  174. package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
  175. package/static/app/assets/System-D6t9jo9V.js +0 -2
  176. package/static/app/assets/System-D6t9jo9V.js.map +0 -1
  177. package/static/app/assets/core-CwxXejkd.js.map +0 -1
  178. package/static/app/assets/index-C7g26IF6.css +0 -2
  179. package/static/app/assets/index-DbcEYJQ2.js +0 -17
  180. package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
  181. package/static/app/assets/primitives-CD38lt4n.js +0 -2
  182. package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
  183. package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
@@ -1,176 +0,0 @@
1
- import * as React from "react";
2
- import { useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { Check, ChevronDown, UserCircle, Building2, ArrowRightLeft } from "lucide-react";
4
- import { latticeApi } from "@/api/client";
5
- import { t, type Language } from "@/i18n";
6
- import { useAppStore } from "@/store/appStore";
7
- import { asArray } from "@/lib/utils";
8
-
9
- type WorkspaceRow = Record<string, unknown>;
10
-
11
- function workspaceId(row: WorkspaceRow): string {
12
- return String(row.workspace_id || row.id || "");
13
- }
14
-
15
- function workspaceName(language: Language, row: WorkspaceRow): string {
16
- const name = row.name;
17
- if (typeof name === "string" && name.trim()) return name;
18
- const id = workspaceId(row);
19
- return id || t(language, "shell.workspace.personal");
20
- }
21
-
22
- function ownerEmail(profile: unknown): string | null {
23
- if (!profile || typeof profile !== "object") return null;
24
- const record = profile as Record<string, unknown>;
25
- const candidate = record.email || record.owner_email || record.username || record.name;
26
- return typeof candidate === "string" && candidate.trim() ? candidate : null;
27
- }
28
-
29
- /**
30
- * Discoverable workspace + profile switcher for the Brain shell.
31
- * Surfaces the active workspace and signed-in owner, and lets the user
32
- * switch workspace or jump to account/workspace settings without hunting
33
- * through the Settings tabs.
34
- */
35
- export function WorkspaceProfileSwitcher({ language }: { language: Language }) {
36
- const qc = useQueryClient();
37
- const workspaceIdState = useAppStore((state) => state.workspaceId);
38
- const setWorkspaceId = useAppStore((state) => state.setWorkspaceId);
39
- const [open, setOpen] = React.useState(false);
40
- const containerRef = React.useRef<HTMLDivElement | null>(null);
41
-
42
- const profile = useQuery({ queryKey: ["profile"], queryFn: latticeApi.profile });
43
- const registry = useQuery({ queryKey: ["workspaceRegistry"], queryFn: latticeApi.workspaceRegistry });
44
-
45
- const workspaces = asArray<WorkspaceRow>(
46
- (registry.data?.data as Record<string, unknown> | undefined)?.workspaces,
47
- );
48
- const owner = ownerEmail(profile.data?.data);
49
-
50
- const activeWorkspace = workspaces.find((row) => workspaceId(row) === workspaceIdState);
51
- const activeLabel = activeWorkspace
52
- ? workspaceName(language, activeWorkspace)
53
- : workspaceIdState || t(language, "shell.workspace.personal");
54
-
55
- React.useEffect(() => {
56
- if (!open) return;
57
- const onPointer = (event: MouseEvent) => {
58
- if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
59
- setOpen(false);
60
- }
61
- };
62
- const onKey = (event: KeyboardEvent) => {
63
- if (event.key === "Escape") setOpen(false);
64
- };
65
- window.addEventListener("mousedown", onPointer);
66
- window.addEventListener("keydown", onKey);
67
- return () => {
68
- window.removeEventListener("mousedown", onPointer);
69
- window.removeEventListener("keydown", onKey);
70
- };
71
- }, [open]);
72
-
73
- const handleSwitch = (id: string) => {
74
- if (id && id !== workspaceIdState) {
75
- setWorkspaceId(id);
76
- // Workspace scoping changes server responses; drop cached views.
77
- qc.invalidateQueries();
78
- }
79
- setOpen(false);
80
- };
81
-
82
- return (
83
- <div className="workspace-profile-switcher" ref={containerRef}>
84
- <button
85
- type="button"
86
- className="workspace-profile-trigger"
87
- aria-haspopup="dialog"
88
- aria-expanded={open}
89
- aria-label={t(language, "shell.workspace.label")}
90
- onClick={() => setOpen((value) => !value)}
91
- >
92
- <Building2 className="h-3.5 w-3.5" aria-hidden="true" />
93
- <span className="workspace-profile-trigger-text">
94
- <span className="workspace-profile-trigger-name">{activeLabel}</span>
95
- <span className="workspace-profile-trigger-owner">{owner || t(language, "shell.profile.signedOut")}</span>
96
- </span>
97
- <ChevronDown className="h-3.5 w-3.5" aria-hidden="true" />
98
- </button>
99
-
100
- {open ? (
101
- <div className="workspace-profile-popover" role="dialog" aria-label={t(language, "shell.workspace.label")}>
102
- <section className="workspace-profile-section" aria-label={t(language, "shell.profile.label")}>
103
- <div className="workspace-profile-section-head">
104
- <UserCircle className="h-3.5 w-3.5" aria-hidden="true" />
105
- <span>{t(language, "shell.profile.label")}</span>
106
- </div>
107
- <div className="workspace-profile-owner-row">
108
- <span className="workspace-profile-owner-label">{t(language, "shell.profile.owner")}</span>
109
- <span className="workspace-profile-owner-value">{owner || t(language, "shell.profile.signedOut")}</span>
110
- </div>
111
- <button
112
- type="button"
113
- className="workspace-profile-link"
114
- onClick={() => {
115
- setOpen(false);
116
- window.location.hash = "/account";
117
- }}
118
- >
119
- {t(language, "shell.profile.manage")}
120
- </button>
121
- </section>
122
-
123
- <section className="workspace-profile-section" aria-label={t(language, "shell.workspace.current")}>
124
- <div className="workspace-profile-section-head">
125
- <Building2 className="h-3.5 w-3.5" aria-hidden="true" />
126
- <span>{t(language, "shell.workspace.current")}</span>
127
- </div>
128
- {workspaces.length === 0 ? (
129
- <p className="workspace-profile-empty">{t(language, "shell.workspace.empty")}</p>
130
- ) : (
131
- <ul className="workspace-profile-list">
132
- {workspaces.map((row) => {
133
- const id = workspaceId(row);
134
- const isActive = id === workspaceIdState;
135
- return (
136
- <li key={id}>
137
- <button
138
- type="button"
139
- className={`workspace-profile-item ${isActive ? "is-active" : ""}`}
140
- aria-current={isActive ? "true" : undefined}
141
- onClick={() => handleSwitch(id)}
142
- >
143
- <span className="workspace-profile-item-name">{workspaceName(language, row)}</span>
144
- {isActive ? (
145
- <span className="workspace-profile-item-flag">
146
- <Check className="h-3 w-3" aria-hidden="true" />
147
- {t(language, "shell.workspace.active")}
148
- </span>
149
- ) : (
150
- <span className="workspace-profile-item-switch">
151
- <ArrowRightLeft className="h-3 w-3" aria-hidden="true" />
152
- {t(language, "shell.workspace.switch")}
153
- </span>
154
- )}
155
- </button>
156
- </li>
157
- );
158
- })}
159
- </ul>
160
- )}
161
- <button
162
- type="button"
163
- className="workspace-profile-link"
164
- onClick={() => {
165
- setOpen(false);
166
- window.location.hash = "/workspace-admin";
167
- }}
168
- >
169
- {t(language, "shell.workspace.manageSpaces")}
170
- </button>
171
- </section>
172
- </div>
173
- ) : null}
174
- </div>
175
- );
176
- }
@@ -1,135 +0,0 @@
1
- import { Sparkles } from "lucide-react";
2
- import { Button } from "@/components/ui/button";
3
- import { asArray } from "@/lib/utils";
4
- import { t, type Language } from "@/i18n";
5
- import { useAppStore } from "@/store/appStore";
6
- import { asRecord, friendlyModelName, type FlowAnalysis } from "./recommendationModel";
7
-
8
- export function AnalysisScreen({
9
- analysis,
10
- error,
11
- onContinue,
12
- }: {
13
- analysis: FlowAnalysis | null;
14
- error: string | null;
15
- onContinue: () => void;
16
- }) {
17
- const language = useAppStore((state) => state.language);
18
- const detected = buildDetectedFacts(analysis, language);
19
- return (
20
- <div>
21
- <div className="ritual-title">{t(language, "flow.analysis.title")}</div>
22
- <div className="ritual-subtitle">{t(language, "flow.analysis.body")}</div>
23
-
24
- <div className="ritual-fact-grid">
25
- {detected.map((item, idx) => (
26
- <div key={idx} className="ritual-fact ritual-fact--visual">
27
- <div className="ritual-fact-head">
28
- <span className="ritual-hardware-icon" aria-hidden="true">{item.icon}</span>
29
- <div className="ritual-fact-label">{item.label}</div>
30
- </div>
31
- <div className="ritual-fact-value">{item.value}</div>
32
- <div className="ritual-fact-detail">{item.detail}</div>
33
- </div>
34
- ))}
35
- </div>
36
-
37
- <div className="ritual-card ritual-analysis-card">
38
- <div className="ritual-inline-row">
39
- <Sparkles className="ritual-core-icon" />
40
- <div>
41
- <div className="ritual-strong-text">{analysis ? recommendedSummary(analysis, language) : t(language, "flow.analysis.finding")}</div>
42
- <div className="ritual-muted-text">
43
- {analysis ? t(language, "flow.analysis.ready") : t(language, "flow.analysis.wait")}
44
- </div>
45
- </div>
46
- </div>
47
- </div>
48
-
49
- {error && <div className="ritual-card ritual-error-card" role="alert">{error}</div>}
50
-
51
- <div className="ritual-centered-actions">
52
- <Button onClick={onContinue} disabled={!analysis && !error} className="ritual-wide-button">
53
- {t(language, "flow.analysis.continue")}
54
- </Button>
55
- </div>
56
- </div>
57
- );
58
- }
59
-
60
- function buildDetectedFacts(analysis: FlowAnalysis | null, language: Language) {
61
- if (!analysis) {
62
- return [
63
- { icon: t(language, "flow.analysis.hardware.icon.chip"), label: t(language, "flow.analysis.hardware.label.chip"), value: t(language, "flow.analysis.checking"), detail: t(language, "flow.analysis.fact.computerDetail") },
64
- { icon: t(language, "flow.analysis.hardware.icon.ram"), label: t(language, "flow.analysis.hardware.label.ram"), value: t(language, "flow.analysis.checking"), detail: t(language, "flow.analysis.fact.memoryDetail") },
65
- { icon: t(language, "flow.analysis.hardware.icon.gpu"), label: t(language, "flow.analysis.hardware.label.gpu"), value: t(language, "flow.analysis.checking"), detail: t(language, "flow.analysis.fact.graphicsDetail") },
66
- { icon: t(language, "flow.analysis.hardware.icon.support"), label: t(language, "flow.analysis.hardware.label.support"), value: t(language, "flow.analysis.checking"), detail: t(language, "flow.analysis.fact.supportDetail") },
67
- { icon: t(language, "flow.analysis.hardware.icon.models"), label: t(language, "flow.analysis.hardware.label.models"), value: t(language, "flow.analysis.checking"), detail: t(language, "flow.analysis.fact.modelsDetail") },
68
- ];
69
- }
70
- const setupEnv = asRecord(analysis.setup?.environment);
71
- const recProfile = asRecord(analysis.recommendations?.profile);
72
- const recs = asRecord(analysis.recommendations?.recommendations);
73
- const models = asRecord(analysis.models);
74
- const sysinfo = asRecord(analysis.sysinfo);
75
- const profile = { ...setupEnv, ...recProfile };
76
- const ramGb = Number(recs.ram_gb || Number(profile.ram_mb || 0) / 1024 || 0);
77
- const appleSilicon = Boolean(recs.apple_silicon || String(profile.arch || "").includes("arm"));
78
- const loadedModels = asArray(models.loaded);
79
- const gpu = asRecord(profile.gpu);
80
- const installedRuntimes = [
81
- ...asArray(setupEnv.installed_runtimes),
82
- ...asArray(profile.installed_runtimes),
83
- ...asArray(recs.installed_runtimes),
84
- ];
85
- return [
86
- {
87
- icon: t(language, "flow.analysis.hardware.icon.chip"),
88
- label: t(language, "flow.analysis.hardware.label.chip"),
89
- value: appleSilicon ? t(language, "flow.analysis.apple") : friendlyOs(profile.os, language),
90
- detail: t(language, "flow.analysis.readyDetail"),
91
- },
92
- {
93
- icon: t(language, "flow.analysis.hardware.icon.ram"),
94
- label: t(language, "flow.analysis.hardware.label.ram"),
95
- value: ramGb ? `${Math.round(ramGb)} GB` : t(language, "flow.analysis.detected"),
96
- detail: t(language, "flow.analysis.memoryReadyDetail"),
97
- },
98
- {
99
- icon: t(language, "flow.analysis.hardware.icon.gpu"),
100
- label: t(language, "flow.analysis.hardware.label.gpu"),
101
- value: gpu.vendor || sysinfo.gpu_mem_gb ? t(language, "flow.analysis.localReady") : t(language, "flow.analysis.standardLocal"),
102
- detail: t(language, "flow.analysis.graphicsReadyDetail"),
103
- },
104
- {
105
- icon: t(language, "flow.analysis.hardware.icon.support"),
106
- label: t(language, "flow.analysis.hardware.label.support"),
107
- value: installedRuntimes.length ? t(language, "flow.analysis.supportReady") : t(language, "flow.analysis.supportInstall"),
108
- detail: installedRuntimes.length ? t(language, "flow.analysis.supportReadyDetail") : t(language, "flow.analysis.supportInstallDetail"),
109
- },
110
- {
111
- icon: t(language, "flow.analysis.hardware.icon.models"),
112
- label: t(language, "flow.analysis.hardware.label.models"),
113
- value: loadedModels.length ? t(language, "flow.analysis.modelsInstalled", { count: loadedModels.length }) : t(language, "flow.analysis.noModels"),
114
- detail: loadedModels.length ? t(language, "flow.analysis.modelsReadyDetail") : t(language, "flow.analysis.modelsInstallDetail"),
115
- },
116
- ];
117
- }
118
-
119
- function recommendedSummary(analysis: FlowAnalysis, language: Language) {
120
- const recs = asRecord(analysis.recommendations?.recommendations);
121
- const topPick = asRecord(recs.top_pick);
122
- if (topPick.name || topPick.id) {
123
- const model = friendlyModelName(String(topPick.name || topPick.id));
124
- return t(language, "flow.analysis.bestFit", { model });
125
- }
126
- return t(language, "flow.analysis.privateRecommended");
127
- }
128
-
129
- function friendlyOs(value: unknown, language: Language) {
130
- const text = String(value || t(language, "flow.analysis.fact.computer"));
131
- if (/darwin|mac/i.test(text)) return "Mac";
132
- if (/win/i.test(text)) return t(language, "flow.analysis.os.windows");
133
- if (/linux/i.test(text)) return t(language, "flow.analysis.os.linux");
134
- return t(language, "flow.analysis.fact.computer");
135
- }
@@ -1,29 +0,0 @@
1
- import { t } from "@/i18n";
2
- import { useAppStore } from "@/store/appStore";
3
- import { type RecommendedModel } from "./recommendationModel";
4
-
5
- export function DownloadConsentPanel({ model }: { model: RecommendedModel }) {
6
- const language = useAppStore((state) => state.language);
7
- const items = [
8
- { label: t(language, "flow.consent.size"), value: model.downloadSize || t(language, "flow.consent.sizeUnknown") },
9
- { label: t(language, "flow.consent.location"), value: model.storageLocation },
10
- { label: t(language, "flow.consent.external"), value: model.externalHost || t(language, "flow.consent.externalNone") },
11
- ];
12
-
13
- return (
14
- <section className="ritual-consent-panel" aria-label={t(language, "flow.consent.title")}>
15
- <div>
16
- <strong>{t(language, "flow.consent.title")}</strong>
17
- <p>{model.downloadRequired ? t(language, "flow.consent.body") : t(language, "flow.consent.ready")}</p>
18
- </div>
19
- <dl>
20
- {items.map((item) => (
21
- <div key={item.label}>
22
- <dt>{item.label}</dt>
23
- <dd>{item.value}</dd>
24
- </div>
25
- ))}
26
- </dl>
27
- </section>
28
- );
29
- }
@@ -1,263 +0,0 @@
1
- import * as React from "react";
2
- import { CheckCircle2 } from "lucide-react";
3
- import { latticeApi } from "@/api/client";
4
- import { type BrainState, LivingBrain } from "@/components/LivingBrain";
5
- import { Button } from "@/components/ui/button";
6
- import { asArray } from "@/lib/utils";
7
- import { t, type Language } from "@/i18n";
8
- import { useAppStore } from "@/store/appStore";
9
- import { DownloadConsentPanel } from "./DownloadConsentPanel";
10
- import { asRecord, type ApiData, type RecommendedModel } from "./recommendationModel";
11
-
12
- type InstallStage = "idle" | "install" | "download" | "validate" | "load" | "done" | "error";
13
-
14
- export function InstallScreen({
15
- model,
16
- onBack,
17
- onComplete,
18
- onLater,
19
- }: {
20
- model: RecommendedModel;
21
- onBack: () => void;
22
- onComplete: () => void;
23
- onLater: () => void;
24
- }) {
25
- const language = useAppStore((state) => state.language);
26
- const [busy, setBusy] = React.useState(false);
27
- const [stage, setStage] = React.useState<InstallStage>("idle");
28
- const [percent, setPercent] = React.useState(0);
29
- const [message, setMessage] = React.useState(t(language, "flow.install.wait"));
30
- const [error, setError] = React.useState<string | null>(null);
31
-
32
- async function start() {
33
- setBusy(true);
34
- setError(null);
35
- setStage("install");
36
- setPercent(8);
37
- setMessage(t(language, "flow.install.prepare"));
38
- const result = await latticeApi.streamModelPrepare(
39
- { model: model.loadId, engine: model.engine || "local_mlx", allow_download: true },
40
- {
41
- onProgress: (event) => {
42
- const nextStage = friendlyInstallStage(String(event.stage || ""));
43
- setStage(nextStage);
44
- setPercent(Number(event.percent || percentForStage(nextStage)));
45
- setMessage(friendlyInstallMessage(event, nextStage, language));
46
- },
47
- onDone: () => {
48
- setStage("done");
49
- setPercent(100);
50
- setMessage(t(language, "flow.install.done"));
51
- },
52
- onError: (event) => {
53
- setStage("error");
54
- setError(consumerError(event));
55
- },
56
- },
57
- );
58
- setBusy(false);
59
- if (result.ok) {
60
- setStage("done");
61
- setPercent(100);
62
- setMessage(t(language, "flow.install.done"));
63
- window.setTimeout(onComplete, 700);
64
- } else {
65
- setStage("error");
66
- setError(consumerError(result.data as ApiData));
67
- }
68
- }
69
-
70
- const brainStateForStage: BrainState =
71
- stage === "download" ? "thinking" :
72
- stage === "validate" ? "recalling" :
73
- stage === "load" ? "synthesizing" :
74
- stage === "done" ? "idle" : "listening";
75
-
76
- return (
77
- <div>
78
- <div className="ritual-title">{t(language, "flow.install.title")}</div>
79
- <div className="ritual-subtitle">
80
- <strong>{model.shortName}</strong> — {model.reason}.<br />
81
- {t(language, "flow.install.body")}
82
- </div>
83
-
84
- <section className="ritual-card ritual-expected-card" aria-label={t(language, "flow.install.expectedTitle")}>
85
- <div className="ritual-fact-label">{t(language, "flow.install.expectedTitle")}</div>
86
- <div className="ritual-time-estimate ritual-expected-line">{expectedLine(model, language)}</div>
87
- <ol className="ritual-timeline">
88
- {timelineSteps(model, language).map((step) => (
89
- <li key={step.key} className="ritual-timeline-step">
90
- <span className="ritual-timeline-name">{step.name}</span>
91
- <span className="ritual-timeline-time">{step.time}</span>
92
- </li>
93
- ))}
94
- </ol>
95
- <div className="ritual-muted-hint">{t(language, "flow.install.expectedCompletion")}</div>
96
- </section>
97
-
98
- <div className="ritual-install-brain">
99
- <LivingBrain
100
- state={brainStateForStage}
101
- intensity={stage === "download" || stage === "load" ? 0.96 : 0.82}
102
- size="normal"
103
- />
104
- </div>
105
-
106
- <DownloadConsentPanel model={model} />
107
-
108
- <div className="ritual-progress">
109
- <div className="ritual-stage-list">
110
- {(["install", "download", "validate", "load"] as const).map((item) => (
111
- <div key={item} className={`ritual-stage ${installStepState(stage, item)}`}>
112
- <CheckCircle2 className="ritual-stage-icon" />
113
- <span>{installLabel(item, language)}</span>
114
- </div>
115
- ))}
116
- </div>
117
-
118
- <div className="ritual-bar">
119
- <span className={`ritual-bar-fill ${progressClass(percent)}`} />
120
- </div>
121
- </div>
122
-
123
- <div className="ritual-status">{message}</div>
124
- <div className="ritual-card ritual-status-card">
125
- {t(language, "flow.install.note")}
126
- </div>
127
-
128
- {error && (
129
- <div className="ritual-card ritual-error-card ritual-install-error" role="alert">
130
- {error}
131
- <div className="ritual-error-detail">{t(language, "flow.install.retry")}</div>
132
- </div>
133
- )}
134
-
135
- <div className="ritual-button-row">
136
- <Button variant="ghost" onClick={onBack} disabled={busy}>{t(language, "flow.install.back")}</Button>
137
- <Button variant="outline" onClick={onLater} disabled={busy}>{t(language, "flow.install.later")}</Button>
138
-
139
- {stage !== "done" ? (
140
- <Button
141
- onClick={start}
142
- disabled={busy || !model.supported}
143
- >
144
- {busy ? t(language, "flow.install.busy") : t(language, "flow.install.start")}
145
- </Button>
146
- ) : (
147
- <Button onClick={onComplete}>{t(language, "flow.install.enter")}</Button>
148
- )}
149
- </div>
150
-
151
- <div className="ritual-local-note">
152
- {t(language, "flow.install.local")}
153
- </div>
154
- </div>
155
- );
156
- }
157
-
158
- function expectedLine(model: RecommendedModel, language: Language) {
159
- const response = model.estimatedFirstResponseSeconds;
160
- if (!model.downloadRequired || model.estimatedDownloadMinutes === 0) {
161
- return t(language, "flow.install.expected.ready", { response });
162
- }
163
- if (model.estimatedDownloadMinutes === null) {
164
- return t(language, "flow.install.expected.unknown", { response });
165
- }
166
- return t(language, "flow.install.expected", {
167
- download: t(language, "flow.recommend.minutes", { count: model.estimatedDownloadMinutes }),
168
- response,
169
- });
170
- }
171
-
172
- function timelineSteps(model: RecommendedModel, language: Language) {
173
- const downloadTime = model.downloadRequired && model.estimatedDownloadMinutes
174
- ? t(language, "flow.install.timelineApprox", {
175
- time: t(language, "flow.recommend.minutes", { count: model.estimatedDownloadMinutes }),
176
- })
177
- : t(language, "flow.install.timelineQuick");
178
- return [
179
- {
180
- key: "download",
181
- name: t(language, "flow.install.timelineStep.download"),
182
- time: downloadTime,
183
- },
184
- {
185
- key: "validate",
186
- name: t(language, "flow.install.timelineStep.validate"),
187
- time: t(language, "flow.install.timelineQuick"),
188
- },
189
- {
190
- key: "load",
191
- name: t(language, "flow.install.timelineStep.load"),
192
- time: t(language, "flow.install.timelineApprox", {
193
- time: `${model.estimatedFirstResponseSeconds}s`,
194
- }),
195
- },
196
- ];
197
- }
198
-
199
- function friendlyInstallStage(stage: string): InstallStage {
200
- if (/download|pull|weights/i.test(stage)) return "download";
201
- if (/smoke|validate|verify|test/i.test(stage)) return "validate";
202
- if (/load|ready/i.test(stage)) return "load";
203
- if (/done|complete/i.test(stage)) return "done";
204
- return "install";
205
- }
206
-
207
- function percentForStage(stage: InstallStage) {
208
- if (stage === "install") return 20;
209
- if (stage === "download") return 55;
210
- if (stage === "validate") return 82;
211
- if (stage === "load") return 94;
212
- if (stage === "done") return 100;
213
- return 8;
214
- }
215
-
216
- function friendlyInstallMessage(event: ApiData, stage: InstallStage, language: Language) {
217
- const fallback = {
218
- install: t(language, "flow.install.prepare"),
219
- download: t(language, "flow.install.stage.download"),
220
- validate: t(language, "flow.install.stage.validate"),
221
- load: t(language, "flow.install.stage.load"),
222
- done: t(language, "flow.install.done"),
223
- idle: t(language, "flow.install.wait"),
224
- error: t(language, "flow.install.stage.error"),
225
- }[stage];
226
- return cleanConsumerText(String(event.user_message || event.message || fallback));
227
- }
228
-
229
- function installLabel(stage: "install" | "download" | "validate" | "load", language: Language) {
230
- return t(language, `flow.install.step.${stage}`);
231
- }
232
-
233
- function progressClass(percent: number) {
234
- const step = Math.max(0, Math.min(100, Math.round(percent / 10) * 10));
235
- return `progress-${step}`;
236
- }
237
-
238
- function installStepState(current: InstallStage, item: "install" | "download" | "validate" | "load") {
239
- const order: InstallStage[] = ["idle", "install", "download", "validate", "load", "done"];
240
- const currentIndex = order.indexOf(current);
241
- const itemIndex = order.indexOf(item);
242
- if (current === "error") return "is-error";
243
- if (current === "done" || currentIndex > itemIndex) return "is-done";
244
- if (current === item) return "is-active";
245
- return "";
246
- }
247
-
248
- function consumerError(data: ApiData | unknown) {
249
- const record = asRecord(data);
250
- const guidance = asArray<string>(record.recovery_guidance).map(cleanConsumerText).filter(Boolean);
251
- const message = cleanConsumerText(String(record.user_message || record.reason || record.error || "The selected model could not be loaded."));
252
- return [message, ...guidance.slice(0, 2)].join(" ");
253
- }
254
-
255
- function cleanConsumerText(value: string) {
256
- return String(value || "")
257
- .replace(/gemma4_unified/gi, "this model format")
258
- .replace(/mlx[-_ ]?vlm|mlx[-_ ]?lm|local_mlx|\bmlx\b|\bgguf\b|\bollama\b|huggingface|hugging face/gi, "local model support")
259
- .replace(/runtime/gi, "model support")
260
- .replace(/No module named ['\"][^'\"]+['\"]/gi, "A local support component is missing")
261
- .replace(/\s+/g, " ")
262
- .trim();
263
- }
@@ -1,23 +0,0 @@
1
- import { LANGUAGE_LABELS, t, type Language } from "@/i18n";
2
- import { useAppStore } from "@/store/appStore";
3
-
4
- export function LanguageChooser() {
5
- const language = useAppStore((state) => state.language);
6
- const setLanguage = useAppStore((state) => state.setLanguage);
7
-
8
- return (
9
- <div className="language-switcher ritual-language" 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
- }