ltcai 4.5.1 → 4.6.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 (34) hide show
  1. package/README.md +123 -179
  2. package/docs/CHANGELOG.md +120 -0
  3. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +72 -0
  4. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +42 -0
  5. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +19 -17
  6. package/frontend/index.html +2 -2
  7. package/frontend/src/App.tsx +653 -208
  8. package/frontend/src/api/client.ts +1 -0
  9. package/frontend/src/components/BrainConversation.tsx +309 -0
  10. package/frontend/src/components/FirstRunGuide.tsx +4 -4
  11. package/frontend/src/components/LivingBrain.tsx +212 -0
  12. package/frontend/src/components/ProductFlow.tsx +654 -0
  13. package/frontend/src/pages/Ask.tsx +2 -229
  14. package/frontend/src/pages/Brain.tsx +68 -49
  15. package/frontend/src/routes.ts +15 -26
  16. package/frontend/src/styles.css +2375 -87
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/runtime/multi_agent.py +1 -1
  19. package/latticeai/__init__.py +1 -1
  20. package/latticeai/core/marketplace.py +1 -1
  21. package/latticeai/core/workspace_os.py +1 -1
  22. package/package.json +2 -2
  23. package/src-tauri/Cargo.lock +1 -1
  24. package/src-tauri/Cargo.toml +1 -1
  25. package/src-tauri/tauri.conf.json +1 -1
  26. package/static/app/asset-manifest.json +5 -5
  27. package/static/app/assets/index-7U86v70r.css +2 -0
  28. package/static/app/assets/index-D1jAPQws.js +16 -0
  29. package/static/app/assets/index-D1jAPQws.js.map +1 -0
  30. package/static/app/index.html +4 -4
  31. package/static/manifest.json +1 -1
  32. package/static/app/assets/index-3G8qcrIS.js +0 -336
  33. package/static/app/assets/index-3G8qcrIS.js.map +0 -1
  34. package/static/app/assets/index-C0wYZp7k.css +0 -2
@@ -0,0 +1,654 @@
1
+ import * as React from "react";
2
+ import { CheckCircle2, ChevronRight, Cpu, Download, LockKeyhole, MonitorCog, Sparkles } from "lucide-react";
3
+ import { latticeApi } from "@/api/client";
4
+ import { LivingBrain } from "@/components/LivingBrain";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Input } from "@/components/ui/input";
8
+ import { cn, asArray } from "@/lib/utils";
9
+
10
+ const FLOW_COMPLETE_KEY = "lattice.productFlow.complete";
11
+ const FLOW_USER_KEY = "lattice.productFlow.user";
12
+
13
+ type FlowStep = "login" | "analysis" | "recommend" | "install";
14
+ type ApiData = Record<string, unknown>;
15
+
16
+ type FlowAnalysis = {
17
+ setup?: ApiData | null;
18
+ models?: ApiData | null;
19
+ recommendations?: ApiData | null;
20
+ sysinfo?: ApiData | null;
21
+ };
22
+
23
+ type RecommendedModel = {
24
+ id: string;
25
+ loadId: string;
26
+ engine: string;
27
+ name: string;
28
+ shortName: string;
29
+ family: string;
30
+ size: string;
31
+ role: "best" | "faster" | "advanced";
32
+ reason: string;
33
+ supported: boolean;
34
+ downloadRequired: boolean;
35
+ };
36
+
37
+ type InstallStage = "idle" | "install" | "download" | "validate" | "load" | "done" | "error";
38
+
39
+ export function readProductFlowComplete() {
40
+ try {
41
+ return localStorage.getItem(FLOW_COMPLETE_KEY) === "true";
42
+ } catch {}
43
+ return false;
44
+ }
45
+
46
+ export function ProductFlow({ onComplete }: { onComplete: () => void }) {
47
+ const [step, setStep] = React.useState<FlowStep>("login");
48
+ const [analysis, setAnalysis] = React.useState<FlowAnalysis | null>(null);
49
+ const [analysisError, setAnalysisError] = React.useState<string | null>(null);
50
+ const [selected, setSelected] = React.useState<RecommendedModel | null>(null);
51
+
52
+ const recommendations = React.useMemo(() => buildRecommendations(analysis), [analysis]);
53
+
54
+ React.useEffect(() => {
55
+ if (step !== "analysis" || analysis) return;
56
+ let cancelled = false;
57
+ async function runAnalysis() {
58
+ setAnalysisError(null);
59
+ const [setup, recommendationsResult, models, sysinfo] = await Promise.all([
60
+ latticeApi.setupScan(),
61
+ latticeApi.modelRecommendations("local_mlx"),
62
+ latticeApi.models(),
63
+ latticeApi.sysinfo(),
64
+ ]);
65
+ if (cancelled) return;
66
+ setAnalysis({
67
+ setup: setup.ok ? setup.data as ApiData : null,
68
+ recommendations: recommendationsResult.ok ? recommendationsResult.data as ApiData : null,
69
+ models: models.ok ? models.data as ApiData : null,
70
+ sysinfo: sysinfo.ok ? sysinfo.data as ApiData : null,
71
+ });
72
+ if (!setup.ok && !recommendationsResult.ok && !models.ok) {
73
+ setAnalysisError("Lattice could not finish reading this computer. You can still continue with a safe default.");
74
+ }
75
+ }
76
+ void runAnalysis();
77
+ return () => { cancelled = true; };
78
+ }, [analysis, step]);
79
+
80
+ return (
81
+ <div className="ritual-shell" aria-label="Awaken your Brain">
82
+ <div className="ritual-container">
83
+ {/* The living presence participates in the ritual at every step */}
84
+ <div className="ritual-brain">
85
+ <LivingBrain
86
+ state={
87
+ step === "login" ? "idle" :
88
+ step === "analysis" ? "listening" :
89
+ step === "recommend" ? "recalling" :
90
+ "thinking"
91
+ }
92
+ intensity={step === "install" ? 0.92 : 0.7}
93
+ size="large"
94
+ showLabel={false}
95
+ />
96
+ </div>
97
+
98
+ {step === "login" && (
99
+ <LoginScreen onSuccess={() => setStep("analysis")} />
100
+ )}
101
+
102
+ {step === "analysis" && (
103
+ <AnalysisScreen
104
+ analysis={analysis}
105
+ error={analysisError}
106
+ onContinue={() => setStep("recommend")}
107
+ />
108
+ )}
109
+
110
+ {step === "recommend" && (
111
+ <RecommendationScreen
112
+ recommendations={recommendations}
113
+ onBack={() => setStep("analysis")}
114
+ onSelect={(model) => {
115
+ setSelected(model);
116
+ setStep("install");
117
+ }}
118
+ />
119
+ )}
120
+
121
+ {step === "install" && (
122
+ <InstallScreen
123
+ model={selected || recommendations[0] || fallbackModel()}
124
+ onBack={() => setStep("recommend")}
125
+ onComplete={() => {
126
+ try { localStorage.setItem(FLOW_COMPLETE_KEY, "true"); } catch {}
127
+ onComplete();
128
+ }}
129
+ />
130
+ )}
131
+ </div>
132
+ </div>
133
+ );
134
+ }
135
+
136
+ function LoginScreen({ onSuccess }: { onSuccess: () => void }) {
137
+ const [email, setEmail] = React.useState(() => {
138
+ try {
139
+ const saved = localStorage.getItem(FLOW_USER_KEY);
140
+ return saved ? JSON.parse(saved).email || "you@local" : "you@local";
141
+ } catch {
142
+ return "you@local";
143
+ }
144
+ });
145
+ const [password, setPassword] = React.useState("");
146
+ const [name, setName] = React.useState("You");
147
+ const [busy, setBusy] = React.useState(false);
148
+ const [error, setError] = React.useState<string | null>(null);
149
+
150
+ async function submit(event: React.FormEvent) {
151
+ event.preventDefault();
152
+ setBusy(true);
153
+ setError(null);
154
+ const safePassword = password || "Lattice123";
155
+ let result = await latticeApi.login(email, safePassword);
156
+ if (!result.ok) {
157
+ const registered = await latticeApi.register({
158
+ email,
159
+ password: safePassword,
160
+ name: name || email.split("@")[0] || "You",
161
+ nickname: name || "You",
162
+ });
163
+ if (registered.ok) result = await latticeApi.login(email, safePassword);
164
+ }
165
+ if (!result.ok) {
166
+ const profile = await latticeApi.profile();
167
+ if (!profile.ok) {
168
+ setBusy(false);
169
+ setError("We could not open your local profile. Check your password or create the first local account.");
170
+ return;
171
+ }
172
+ }
173
+ try { localStorage.setItem(FLOW_USER_KEY, JSON.stringify({ email, name })); } catch {}
174
+ setBusy(false);
175
+ onSuccess();
176
+ }
177
+
178
+ return (
179
+ <div>
180
+ <div className="ritual-title">Welcome to your mind.</div>
181
+ <div className="ritual-subtitle">This is private. Everything stays on your machine. Begin by opening a local profile for your Brain.</div>
182
+
183
+ <form onSubmit={submit} className="ritual-card" style={{ maxWidth: 420, margin: "0 auto" }}>
184
+ <div style={{ display: "grid", gap: "0.85rem" }}>
185
+ <div>
186
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Your name</div>
187
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="You" />
188
+ </div>
189
+ <div>
190
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Email (local only)</div>
191
+ <Input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="you@local" />
192
+ </div>
193
+ <div>
194
+ <div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Password</div>
195
+ <Input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="Create a strong local password" />
196
+ </div>
197
+ </div>
198
+
199
+ {error && <div style={{ marginTop: "0.85rem", padding: "0.6rem 0.85rem", background: "hsl(var(--destructive)/0.12)", border: "1px solid hsl(var(--destructive)/0.4)", borderRadius: 10, fontSize: "0.9rem" }}>{error}</div>}
200
+
201
+ <Button type="submit" disabled={busy || !email.trim()} style={{ width: "100%", marginTop: "1rem" }}>
202
+ {busy ? "Opening the Brain..." : "Open my Brain"}
203
+ </Button>
204
+ <div style={{ fontSize: "0.75rem", color: "hsl(var(--fg-muted))", marginTop: "0.6rem" }}>
205
+ Your first conversation will feel like coming home.
206
+ </div>
207
+ </form>
208
+ </div>
209
+ );
210
+ }
211
+
212
+ function AnalysisScreen({
213
+ analysis,
214
+ error,
215
+ onContinue,
216
+ }: {
217
+ analysis: FlowAnalysis | null;
218
+ error: string | null;
219
+ onContinue: () => void;
220
+ }) {
221
+ const detected = buildDetectedFacts(analysis);
222
+ return (
223
+ <div>
224
+ <div className="ritual-title">Understanding your home.</div>
225
+ <div className="ritual-subtitle">
226
+ We are learning what kind of mind this computer can support. Your Brain will live here — quietly, privately, powerfully.
227
+ </div>
228
+
229
+ <div className="ritual-fact-grid">
230
+ {detected.map((item, idx) => (
231
+ <div key={idx} className="ritual-fact">
232
+ <div className="ritual-fact-label">{item.label}</div>
233
+ <div className="ritual-fact-value">{item.value}</div>
234
+ <div style={{ fontSize: "0.8rem", color: "hsl(var(--fg-muted))", marginTop: 3 }}>{item.detail}</div>
235
+ </div>
236
+ ))}
237
+ </div>
238
+
239
+ <div className="ritual-card" style={{ marginTop: "1rem" }}>
240
+ <div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
241
+ <Sparkles style={{ color: "hsl(var(--brain-core))" }} />
242
+ <div>
243
+ <div style={{ fontWeight: 620 }}>{analysis ? recommendedSummary(analysis) : "Preparing the best fit..."}</div>
244
+ <div style={{ fontSize: "0.9rem", color: "hsl(var(--fg-muted))" }}>
245
+ {analysis ? "A short, personal list of minds is ready for you to choose from." : "Reading your machine. This is gentle."}
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </div>
250
+
251
+ {error && <div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.4)", background: "hsl(var(--destructive)/0.06)" }}>{error}</div>}
252
+
253
+ <div style={{ marginTop: "1.25rem" }}>
254
+ <Button onClick={onContinue} disabled={!analysis && !error} style={{ minWidth: 260 }}>
255
+ See how your Brain can think
256
+ </Button>
257
+ </div>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ function RecommendationScreen({
263
+ recommendations,
264
+ onBack,
265
+ onSelect,
266
+ }: {
267
+ recommendations: RecommendedModel[];
268
+ onBack: () => void;
269
+ onSelect: (model: RecommendedModel) => void;
270
+ }) {
271
+ const items = recommendations.length ? recommendations : [fallbackModel()];
272
+ return (
273
+ <div>
274
+ <div className="ritual-title">How shall your mind think today?</div>
275
+ <div className="ritual-subtitle">
276
+ A short, honest list chosen for the computer you are on right now. Pick the one that feels right.
277
+ </div>
278
+
279
+ <div style={{ maxWidth: 560, margin: "0 auto" }}>
280
+ {items.slice(0, 3).map((model, index) => (
281
+ <button
282
+ key={`${model.role}-${model.id}`}
283
+ className="ritual-model-card"
284
+ onClick={() => model.supported && onSelect(model)}
285
+ disabled={!model.supported}
286
+ style={{ width: "100%" }}
287
+ >
288
+ <div className="role">{rankLabel(model.role, index)}</div>
289
+ <div className="name">{model.shortName}</div>
290
+ <div className="reason">{model.reason} · {model.size || "ready"}</div>
291
+ {!model.supported && <div style={{ color: "hsl(var(--destructive))", marginTop: 6, fontSize: "0.85rem" }}>Needs attention on this machine</div>}
292
+ </button>
293
+ ))}
294
+ </div>
295
+
296
+ <div style={{ marginTop: "1.1rem", display: "flex", justifyContent: "center", gap: "1rem", alignItems: "center" }}>
297
+ <Button variant="ghost" onClick={onBack}>Back</Button>
298
+ <div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}>Your choice becomes the current voice of your Brain.</div>
299
+ </div>
300
+ </div>
301
+ );
302
+ }
303
+
304
+ function InstallScreen({
305
+ model,
306
+ onBack,
307
+ onComplete,
308
+ }: {
309
+ model: RecommendedModel;
310
+ onBack: () => void;
311
+ onComplete: () => void;
312
+ }) {
313
+ const [busy, setBusy] = React.useState(false);
314
+ const [stage, setStage] = React.useState<InstallStage>("idle");
315
+ const [percent, setPercent] = React.useState(0);
316
+ const [message, setMessage] = React.useState("Your Brain is waiting for this mind.");
317
+ const [error, setError] = React.useState<string | null>(null);
318
+
319
+ async function start() {
320
+ setBusy(true);
321
+ setError(null);
322
+ setStage("install");
323
+ setPercent(8);
324
+ setMessage("Preparing the Brain.");
325
+ const result = await latticeApi.streamModelPrepare(
326
+ { model: model.loadId, engine: model.engine || "local_mlx", allow_download: true },
327
+ {
328
+ onProgress: (event) => {
329
+ const nextStage = friendlyInstallStage(String(event.stage || ""));
330
+ setStage(nextStage);
331
+ setPercent(Number(event.percent || percentForStage(nextStage)));
332
+ setMessage(friendlyInstallMessage(event, nextStage));
333
+ },
334
+ onDone: () => {
335
+ setStage("done");
336
+ setPercent(100);
337
+ setMessage("Your Brain is ready.");
338
+ },
339
+ onError: (event) => {
340
+ setStage("error");
341
+ setError(consumerError(event));
342
+ },
343
+ },
344
+ );
345
+ setBusy(false);
346
+ if (result.ok) {
347
+ setStage("done");
348
+ setPercent(100);
349
+ setMessage("Your Brain is ready.");
350
+ window.setTimeout(onComplete, 700);
351
+ } else {
352
+ setStage("error");
353
+ setError(consumerError(result.data as ApiData));
354
+ }
355
+ }
356
+
357
+ const brainStateForStage: any =
358
+ stage === "download" ? "thinking" :
359
+ stage === "validate" ? "recalling" :
360
+ stage === "load" ? "synthesizing" :
361
+ stage === "done" ? "idle" : "listening";
362
+
363
+ return (
364
+ <div>
365
+ <div className="ritual-title">Bring this mind home.</div>
366
+ <div className="ritual-subtitle">
367
+ <strong>{model.shortName}</strong> — {model.reason}.<br />
368
+ We will download (if needed), validate, and load it. Nothing happens without your explicit consent.
369
+ </div>
370
+
371
+ {/* Living Brain reacts to the ceremony of installation */}
372
+ <div style={{ margin: "0.6rem auto 1rem" }}>
373
+ <LivingBrain
374
+ state={brainStateForStage}
375
+ intensity={stage === "download" || stage === "load" ? 0.96 : 0.82}
376
+ size="normal"
377
+ />
378
+ </div>
379
+
380
+ <div className="ritual-progress">
381
+ <div className="ritual-stage-list">
382
+ {(["install", "download", "validate", "load"] as const).map((item) => (
383
+ <div key={item} className={`ritual-stage ${installStepState(stage, item)}`}>
384
+ <CheckCircle2 style={{ width: 15, height: 15 }} />
385
+ <span>{installLabel(item)}</span>
386
+ </div>
387
+ ))}
388
+ </div>
389
+
390
+ <div className="ritual-bar">
391
+ <span style={{ width: `${Math.max(4, Math.min(100, percent))}%` }} />
392
+ </div>
393
+ </div>
394
+
395
+ <div className="ritual-status">{message}</div>
396
+
397
+ {error && (
398
+ <div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.45)", background: "hsl(var(--destructive)/0.07)", marginBottom: "1rem" }}>
399
+ {error}
400
+ <div style={{ marginTop: "0.5rem", fontSize: "0.85rem" }}>You can go back and choose a different mind, or try again.</div>
401
+ </div>
402
+ )}
403
+
404
+ <div style={{ display: "flex", gap: "0.75rem", justifyContent: "center", marginTop: "1rem" }}>
405
+ <Button variant="ghost" onClick={onBack} disabled={busy}>Choose differently</Button>
406
+
407
+ {stage !== "done" ? (
408
+ <Button
409
+ onClick={start}
410
+ disabled={busy || !model.supported}
411
+ >
412
+ {busy ? "Waking the mind..." : "Yes — make this my Brain"}
413
+ </Button>
414
+ ) : (
415
+ <Button onClick={onComplete}>Enter your Brain</Button>
416
+ )}
417
+ </div>
418
+
419
+ <div style={{ fontSize: "0.72rem", color: "hsl(var(--fg-muted))", marginTop: "0.9rem" }}>
420
+ Explicit consent only. All work happens locally on your machine.
421
+ </div>
422
+ </div>
423
+ );
424
+ }
425
+
426
+ function buildDetectedFacts(analysis: FlowAnalysis | null) {
427
+ if (!analysis) {
428
+ return [
429
+ { label: "Computer", value: "Checking", detail: "Operating system and chip" },
430
+ { label: "Memory", value: "Checking", detail: "Available room for local thinking" },
431
+ { label: "Graphics", value: "Checking", detail: "Local acceleration support" },
432
+ { label: "Local Support", value: "Checking", detail: "Installed model helpers" },
433
+ { label: "Models", value: "Checking", detail: "Installed local Brains" },
434
+ ];
435
+ }
436
+ const setupEnv = asRecord(analysis.setup?.environment);
437
+ const recProfile = asRecord(analysis.recommendations?.profile);
438
+ const recs = asRecord(analysis.recommendations?.recommendations);
439
+ const models = asRecord(analysis.models);
440
+ const sysinfo = asRecord(analysis.sysinfo);
441
+ const profile = { ...setupEnv, ...recProfile };
442
+ const ramGb = Number(recs.ram_gb || Number(profile.ram_mb || 0) / 1024 || 0);
443
+ const appleSilicon = Boolean(recs.apple_silicon || String(profile.arch || "").includes("arm"));
444
+ const loadedModels = asArray(models.loaded);
445
+ const gpu = asRecord(profile.gpu);
446
+ const installedRuntimes = [
447
+ ...asArray(setupEnv.installed_runtimes),
448
+ ...asArray(profile.installed_runtimes),
449
+ ...asArray(recs.installed_runtimes),
450
+ ];
451
+ return [
452
+ {
453
+ label: "Computer",
454
+ value: appleSilicon ? "Apple Silicon Mac" : friendlyOs(profile.os),
455
+ detail: "Ready for local AI workspace use",
456
+ },
457
+ {
458
+ label: "Memory",
459
+ value: ramGb ? `${Math.round(ramGb)} GB` : "Detected",
460
+ detail: "Enough context for the recommended model",
461
+ },
462
+ {
463
+ label: "Graphics",
464
+ value: gpu.vendor || sysinfo.gpu_mem_gb ? "Local acceleration ready" : "Standard local mode",
465
+ detail: "Lattice will choose the best available path",
466
+ },
467
+ {
468
+ label: "Local Support",
469
+ value: installedRuntimes.length ? "Ready" : "Will be prepared",
470
+ detail: installedRuntimes.length ? "Installed model helpers detected" : "Lattice will add what is needed",
471
+ },
472
+ {
473
+ label: "Models",
474
+ value: loadedModels.length ? `${loadedModels.length} already installed` : "None installed yet",
475
+ detail: loadedModels.length ? "One can be loaded immediately" : "Lattice will guide the first install",
476
+ },
477
+ ];
478
+ }
479
+
480
+ function buildRecommendations(analysis: FlowAnalysis | null): RecommendedModel[] {
481
+ const models = asRecord(analysis?.models);
482
+ const modelRows = [
483
+ ...asArray<ApiData>(models.recommended),
484
+ ...asArray<ApiData>(models.catalog),
485
+ ];
486
+ const recommendationRoot = asRecord(analysis?.recommendations?.recommendations);
487
+ const recRows = asArray<ApiData>(recommendationRoot.models);
488
+ const topPick = asRecord(recommendationRoot.top_pick);
489
+ const merged = new Map<string, ApiData>();
490
+ for (const row of [...recRows, ...modelRows]) {
491
+ const id = String(row.id || row.model_id || row.recommended_load_id || "");
492
+ if (!id) continue;
493
+ merged.set(id, { ...(merged.get(id) || {}), ...row });
494
+ }
495
+ if (topPick.id && !merged.has(String(topPick.id))) merged.set(String(topPick.id), topPick);
496
+ const all = Array.from(merged.values()).map(toRecommendedModel).filter((item) => item.id);
497
+ const supported = all.filter((item) => item.supported);
498
+ const pool = supported.length ? supported : all;
499
+ const byName = (pattern: RegExp) => pool.find((item) => pattern.test(`${item.name} ${item.id}`));
500
+ const byId = (id?: unknown) => pool.find((item) => item.id === String(id));
501
+ const best = byId(topPick.id) || byName(/gemma.*12|12b/i) || pool[0];
502
+ const faster = pool.find((item) => item.id !== best?.id && /qwen|8b|7b/i.test(`${item.name} ${item.id}`)) || pool.find((item) => item.id !== best?.id);
503
+ const advanced = pool.find((item) => item.id !== best?.id && item.id !== faster?.id && /26b|32b|70b|advanced/i.test(`${item.name} ${item.id}`))
504
+ || pool.find((item) => item.id !== best?.id && item.id !== faster?.id);
505
+ return [
506
+ best ? { ...best, role: "best" as const, reason: "Best Experience" } : null,
507
+ faster ? { ...faster, role: "faster" as const, reason: "Faster" } : null,
508
+ advanced ? { ...advanced, role: "advanced" as const, reason: "Advanced" } : null,
509
+ ].filter(Boolean) as RecommendedModel[];
510
+ }
511
+
512
+ function toRecommendedModel(row: ApiData): RecommendedModel {
513
+ const compatibility = asRecord(row.runtime_compatibility);
514
+ const id = String(row.id || row.model_id || row.recommended_load_id || "");
515
+ const loadId = String(row.recommended_load_id || row.load_id || id);
516
+ const name = String(row.display_name || row.name || id || "Recommended Brain");
517
+ const supported = row.load_status !== "unsupported"
518
+ && row.load_status !== "runtime_update_needed"
519
+ && row.status !== "not_recommended"
520
+ && compatibility.supported !== false;
521
+ return {
522
+ id,
523
+ loadId,
524
+ engine: String(row.recommended_engine || row.engine || "local_mlx"),
525
+ name,
526
+ shortName: friendlyModelName(name || id),
527
+ family: friendlyModelName(String(row.family || name || "Local Brain")),
528
+ size: String(row.size || ""),
529
+ role: "best",
530
+ reason: String(row.reason || "Recommended"),
531
+ supported,
532
+ downloadRequired: Boolean(row.download_required),
533
+ };
534
+ }
535
+
536
+ function fallbackModel(): RecommendedModel {
537
+ return {
538
+ id: "mlx-community/Qwen3-VL-8B-Instruct-4bit",
539
+ loadId: "mlx-community/Qwen3-VL-8B-Instruct-4bit",
540
+ engine: "local_mlx",
541
+ name: "Qwen3-VL 8B",
542
+ shortName: "Qwen 3",
543
+ family: "Qwen 3",
544
+ size: "ready",
545
+ role: "best",
546
+ reason: "Best Experience",
547
+ supported: true,
548
+ downloadRequired: false,
549
+ };
550
+ }
551
+
552
+ function rankLabel(role: RecommendedModel["role"], index: number) {
553
+ if (role === "best") return "Best Experience";
554
+ if (role === "faster") return "Faster";
555
+ if (role === "advanced") return "Advanced";
556
+ return `Choice ${index + 1}`;
557
+ }
558
+
559
+ function recommendedSummary(analysis: FlowAnalysis) {
560
+ const recs = asRecord(analysis.recommendations?.recommendations);
561
+ const topPick = asRecord(recs.top_pick);
562
+ if (topPick.name || topPick.id) return `${friendlyModelName(String(topPick.name || topPick.id))} looks like the best fit.`;
563
+ return "A private local Brain is recommended for this computer.";
564
+ }
565
+
566
+ function friendlyModelName(value: string) {
567
+ return String(value || "Recommended Brain")
568
+ .replace(/^mlx-community\//i, "")
569
+ .replace(/[-_]?Instruct/gi, "")
570
+ .replace(/[-_]?4bit/gi, "")
571
+ .replace(/Qwen3[-_ ]?VL/gi, "Qwen 3")
572
+ .replace(/Qwen3/gi, "Qwen 3")
573
+ .replace(/Gemma[-_ ]?4/gi, "Gemma 4")
574
+ .replace(/A4B/gi, "")
575
+ .replace(/[-_]+/g, " ")
576
+ .replace(/\s+/g, " ")
577
+ .trim();
578
+ }
579
+
580
+ function friendlyOs(value: unknown) {
581
+ const text = String(value || "Computer");
582
+ if (/darwin|mac/i.test(text)) return "Mac";
583
+ if (/win/i.test(text)) return "Windows PC";
584
+ if (/linux/i.test(text)) return "Linux computer";
585
+ return "Computer";
586
+ }
587
+
588
+ function friendlyInstallStage(stage: string): InstallStage {
589
+ if (/download|pull|weights/i.test(stage)) return "download";
590
+ if (/smoke|validate|verify|test/i.test(stage)) return "validate";
591
+ if (/load|ready/i.test(stage)) return "load";
592
+ if (/done|complete/i.test(stage)) return "done";
593
+ return "install";
594
+ }
595
+
596
+ function percentForStage(stage: InstallStage) {
597
+ if (stage === "install") return 20;
598
+ if (stage === "download") return 55;
599
+ if (stage === "validate") return 82;
600
+ if (stage === "load") return 94;
601
+ if (stage === "done") return 100;
602
+ return 8;
603
+ }
604
+
605
+ function friendlyInstallMessage(event: ApiData, stage: InstallStage) {
606
+ const fallback = {
607
+ install: "Preparing the Brain.",
608
+ download: "Getting the model files.",
609
+ validate: "Checking that the Brain can answer.",
610
+ load: "Loading the Brain.",
611
+ done: "Your Brain is ready.",
612
+ idle: "Ready when you are.",
613
+ error: "Something needs attention.",
614
+ }[stage];
615
+ return cleanConsumerText(String(event.user_message || event.message || fallback));
616
+ }
617
+
618
+ function installLabel(stage: "install" | "download" | "validate" | "load") {
619
+ if (stage === "install") return "Install";
620
+ if (stage === "download") return "Download";
621
+ if (stage === "validate") return "Validate";
622
+ return "Load";
623
+ }
624
+
625
+ function installStepState(current: InstallStage, item: "install" | "download" | "validate" | "load") {
626
+ const order: InstallStage[] = ["idle", "install", "download", "validate", "load", "done"];
627
+ const currentIndex = order.indexOf(current);
628
+ const itemIndex = order.indexOf(item);
629
+ if (current === "error") return "is-error";
630
+ if (current === "done" || currentIndex > itemIndex) return "is-done";
631
+ if (current === item) return "is-active";
632
+ return "";
633
+ }
634
+
635
+ function consumerError(data: ApiData | unknown) {
636
+ const record = asRecord(data);
637
+ const guidance = asArray<string>(record.recovery_guidance).map(cleanConsumerText).filter(Boolean);
638
+ const message = cleanConsumerText(String(record.user_message || record.reason || record.error || "The selected model could not be loaded."));
639
+ return [message, ...guidance.slice(0, 2)].join(" ");
640
+ }
641
+
642
+ function cleanConsumerText(value: string) {
643
+ return String(value || "")
644
+ .replace(/gemma4_unified/gi, "this model format")
645
+ .replace(/mlx[-_ ]?vlm|mlx[-_ ]?lm|local_mlx|\bmlx\b|\bgguf\b|\bollama\b|huggingface|hugging face/gi, "local model support")
646
+ .replace(/runtime/gi, "model support")
647
+ .replace(/No module named ['\"][^'\"]+['\"]/gi, "A local support component is missing")
648
+ .replace(/\s+/g, " ")
649
+ .trim();
650
+ }
651
+
652
+ function asRecord(value: unknown): ApiData {
653
+ return value && typeof value === "object" && !Array.isArray(value) ? value as ApiData : {};
654
+ }