ltcai 4.6.0 → 4.7.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.
- package/README.md +145 -194
- package/docs/CHANGELOG.md +139 -1
- package/docs/PRODUCT_DIRECTION_REVIEW.md +88 -0
- package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +33 -19
- package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +42 -0
- package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +42 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +20 -18
- package/frontend/src/App.tsx +1098 -171
- package/frontend/src/api/client.ts +2 -0
- package/frontend/src/components/BrainConversation.tsx +10 -2
- package/frontend/src/components/LivingBrain.tsx +197 -106
- package/frontend/src/components/ProductFlow.tsx +210 -129
- package/frontend/src/styles.css +1946 -36
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +86 -13
- package/lattice_brain/portability.py +82 -14
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +30 -4
- package/latticeai/api/chat.py +25 -11
- package/latticeai/app_factory.py +8 -2
- package/latticeai/core/audit.py +3 -2
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +1 -1
- package/scripts/launch-pts-grok.sh +56 -0
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/index-DFmuiJ6t.css +2 -0
- package/static/app/assets/index-DwX3rNfA.js +16 -0
- package/static/app/assets/index-DwX3rNfA.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-By-G-Kay.css +0 -2
- package/static/app/assets/index-CJx6WuQH.js +0 -336
- package/static/app/assets/index-CJx6WuQH.js.map +0 -1
|
@@ -78,39 +78,58 @@ export function ProductFlow({ onComplete }: { onComplete: () => void }) {
|
|
|
78
78
|
}, [analysis, step]);
|
|
79
79
|
|
|
80
80
|
return (
|
|
81
|
-
<
|
|
82
|
-
<div className="
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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>
|
|
114
133
|
);
|
|
115
134
|
}
|
|
116
135
|
|
|
@@ -157,34 +176,59 @@ function LoginScreen({ onSuccess }: { onSuccess: () => void }) {
|
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
return (
|
|
160
|
-
<
|
|
161
|
-
<div className="
|
|
162
|
-
<div className="
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
<div>
|
|
180
|
+
<div className="ritual-title">Welcome to your mind.</div>
|
|
181
|
+
<div className="ritual-subtitle">
|
|
182
|
+
Models will change. Your knowledge should not. Lattice keeps your documents, conversations, decisions, and context together as a private Brain you own.
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<ProductPromise />
|
|
186
|
+
|
|
187
|
+
<form onSubmit={submit} className="ritual-card" style={{ maxWidth: 420, margin: "0 auto" }}>
|
|
188
|
+
<div style={{ display: "grid", gap: "0.85rem" }}>
|
|
189
|
+
<div>
|
|
190
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Your name</div>
|
|
191
|
+
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="You" />
|
|
192
|
+
</div>
|
|
193
|
+
<div>
|
|
194
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Email (local only)</div>
|
|
195
|
+
<Input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="you@local" />
|
|
196
|
+
</div>
|
|
197
|
+
<div>
|
|
198
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>Password</div>
|
|
199
|
+
<Input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="Create a strong local password" />
|
|
200
|
+
</div>
|
|
167
201
|
</div>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
202
|
+
|
|
203
|
+
{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>}
|
|
204
|
+
|
|
205
|
+
<Button type="submit" disabled={busy || !email.trim()} style={{ width: "100%", marginTop: "1rem" }}>
|
|
206
|
+
{busy ? "Opening the Brain..." : "Open my Brain"}
|
|
207
|
+
</Button>
|
|
208
|
+
<div style={{ fontSize: "0.75rem", color: "hsl(var(--fg-muted))", marginTop: "0.6rem" }}>
|
|
209
|
+
Local profile first. Model choice comes later.
|
|
210
|
+
</div>
|
|
211
|
+
</form>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function ProductPromise() {
|
|
217
|
+
return (
|
|
218
|
+
<div className="ritual-promise" aria-label="Lattice AI product promise">
|
|
219
|
+
<div>
|
|
220
|
+
<span>Durable knowledge</span>
|
|
221
|
+
<strong>Your work becomes long-lived context.</strong>
|
|
222
|
+
</div>
|
|
223
|
+
<div>
|
|
224
|
+
<span>Replaceable models</span>
|
|
225
|
+
<strong>The model is a voice, not the asset.</strong>
|
|
226
|
+
</div>
|
|
227
|
+
<div>
|
|
228
|
+
<span>User ownership</span>
|
|
229
|
+
<strong>Back up, restore, and move your Brain.</strong>
|
|
186
230
|
</div>
|
|
187
|
-
</
|
|
231
|
+
</div>
|
|
188
232
|
);
|
|
189
233
|
}
|
|
190
234
|
|
|
@@ -199,36 +243,42 @@ function AnalysisScreen({
|
|
|
199
243
|
}) {
|
|
200
244
|
const detected = buildDetectedFacts(analysis);
|
|
201
245
|
return (
|
|
202
|
-
<
|
|
203
|
-
<div className="
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<h1>Learning what your computer can do.</h1>
|
|
207
|
-
<p>Lattice checks the essentials, then recommends the best local Brain for this machine.</p>
|
|
208
|
-
</div>
|
|
209
|
-
<Badge variant={analysis ? "success" : "muted"}>{analysis ? "complete" : "analyzing"}</Badge>
|
|
246
|
+
<div>
|
|
247
|
+
<div className="ritual-title">Understanding your home.</div>
|
|
248
|
+
<div className="ritual-subtitle">
|
|
249
|
+
Lattice is checking what this computer can support so your Brain can run locally instead of turning your memories into a cloud dependency.
|
|
210
250
|
</div>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
<
|
|
251
|
+
|
|
252
|
+
<div className="ritual-fact-grid">
|
|
253
|
+
{detected.map((item, idx) => (
|
|
254
|
+
<div key={idx} className="ritual-fact">
|
|
255
|
+
<div className="ritual-fact-label">{item.label}</div>
|
|
256
|
+
<div className="ritual-fact-value">{item.value}</div>
|
|
257
|
+
<div style={{ fontSize: "0.8rem", color: "hsl(var(--fg-muted))", marginTop: 3 }}>{item.detail}</div>
|
|
217
258
|
</div>
|
|
218
259
|
))}
|
|
219
260
|
</div>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<div>
|
|
223
|
-
<
|
|
224
|
-
<
|
|
261
|
+
|
|
262
|
+
<div className="ritual-card" style={{ marginTop: "1rem" }}>
|
|
263
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
|
|
264
|
+
<Sparkles style={{ color: "hsl(var(--brain-core))" }} />
|
|
265
|
+
<div>
|
|
266
|
+
<div style={{ fontWeight: 620 }}>{analysis ? recommendedSummary(analysis) : "Preparing the best fit..."}</div>
|
|
267
|
+
<div style={{ fontSize: "0.9rem", color: "hsl(var(--fg-muted))" }}>
|
|
268
|
+
{analysis ? "A short, personal list of minds is ready for you to choose from." : "Reading your machine. This is gentle."}
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
225
271
|
</div>
|
|
226
272
|
</div>
|
|
227
|
-
|
|
228
|
-
<div className="
|
|
229
|
-
|
|
273
|
+
|
|
274
|
+
{error && <div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.4)", background: "hsl(var(--destructive)/0.06)" }}>{error}</div>}
|
|
275
|
+
|
|
276
|
+
<div style={{ marginTop: "1.25rem" }}>
|
|
277
|
+
<Button onClick={onContinue} disabled={!analysis && !error} style={{ minWidth: 260 }}>
|
|
278
|
+
See how your Brain can think
|
|
279
|
+
</Button>
|
|
230
280
|
</div>
|
|
231
|
-
</
|
|
281
|
+
</div>
|
|
232
282
|
);
|
|
233
283
|
}
|
|
234
284
|
|
|
@@ -243,36 +293,34 @@ function RecommendationScreen({
|
|
|
243
293
|
}) {
|
|
244
294
|
const items = recommendations.length ? recommendations : [fallbackModel()];
|
|
245
295
|
return (
|
|
246
|
-
<
|
|
247
|
-
<div className="
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<h1>Recommended for your computer.</h1>
|
|
251
|
-
<p>A short list, ranked for this Mac. No catalog digging required.</p>
|
|
252
|
-
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<div className="ritual-title">How shall your mind think today?</div>
|
|
298
|
+
<div className="ritual-subtitle">
|
|
299
|
+
The model is the current voice of your Brain. You can replace it later; your knowledge stays.
|
|
253
300
|
</div>
|
|
254
|
-
|
|
301
|
+
|
|
302
|
+
<div style={{ maxWidth: 560, margin: "0 auto" }}>
|
|
255
303
|
{items.slice(0, 3).map((model, index) => (
|
|
256
304
|
<button
|
|
257
305
|
key={`${model.role}-${model.id}`}
|
|
258
|
-
className=
|
|
259
|
-
onClick={() => onSelect(model)}
|
|
306
|
+
className="ritual-model-card"
|
|
307
|
+
onClick={() => model.supported && onSelect(model)}
|
|
260
308
|
disabled={!model.supported}
|
|
309
|
+
style={{ width: "100%" }}
|
|
261
310
|
>
|
|
262
|
-
<
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
</span>
|
|
267
|
-
<Badge variant={model.supported ? "success" : "warning"}>{model.supported ? model.size || "ready" : "needs update"}</Badge>
|
|
311
|
+
<div className="role">{rankLabel(model.role, index)}</div>
|
|
312
|
+
<div className="name">{model.shortName}</div>
|
|
313
|
+
<div className="reason">{model.reason} · {model.size || "ready"}</div>
|
|
314
|
+
{!model.supported && <div style={{ color: "hsl(var(--destructive))", marginTop: 6, fontSize: "0.85rem" }}>Needs attention on this machine</div>}
|
|
268
315
|
</button>
|
|
269
316
|
))}
|
|
270
317
|
</div>
|
|
271
|
-
|
|
318
|
+
|
|
319
|
+
<div style={{ marginTop: "1.1rem", display: "flex", justifyContent: "center", gap: "1rem", alignItems: "center" }}>
|
|
272
320
|
<Button variant="ghost" onClick={onBack}>Back</Button>
|
|
273
|
-
<
|
|
321
|
+
<div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}>Your choice becomes the current voice of your Brain.</div>
|
|
274
322
|
</div>
|
|
275
|
-
</
|
|
323
|
+
</div>
|
|
276
324
|
);
|
|
277
325
|
}
|
|
278
326
|
|
|
@@ -288,7 +336,7 @@ function InstallScreen({
|
|
|
288
336
|
const [busy, setBusy] = React.useState(false);
|
|
289
337
|
const [stage, setStage] = React.useState<InstallStage>("idle");
|
|
290
338
|
const [percent, setPercent] = React.useState(0);
|
|
291
|
-
const [message, setMessage] = React.useState("
|
|
339
|
+
const [message, setMessage] = React.useState("Your Brain is waiting for this mind.");
|
|
292
340
|
const [error, setError] = React.useState<string | null>(null);
|
|
293
341
|
|
|
294
342
|
async function start() {
|
|
@@ -329,39 +377,72 @@ function InstallScreen({
|
|
|
329
377
|
}
|
|
330
378
|
}
|
|
331
379
|
|
|
380
|
+
const brainStateForStage: any =
|
|
381
|
+
stage === "download" ? "thinking" :
|
|
382
|
+
stage === "validate" ? "recalling" :
|
|
383
|
+
stage === "load" ? "synthesizing" :
|
|
384
|
+
stage === "done" ? "idle" : "listening";
|
|
385
|
+
|
|
332
386
|
return (
|
|
333
|
-
<
|
|
334
|
-
<div className="
|
|
335
|
-
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
<h1>{model.shortName}</h1>
|
|
339
|
-
<p>Lattice will install, download, validate, and load the selected Brain.</p>
|
|
340
|
-
</div>
|
|
387
|
+
<div>
|
|
388
|
+
<div className="ritual-title">Bring this mind home.</div>
|
|
389
|
+
<div className="ritual-subtitle">
|
|
390
|
+
<strong>{model.shortName}</strong> — {model.reason}.<br />
|
|
391
|
+
This gives your Brain a local voice. Download, validation, and loading happen only with your consent.
|
|
341
392
|
</div>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
393
|
+
|
|
394
|
+
{/* Living Brain reacts to the ceremony of installation */}
|
|
395
|
+
<div style={{ margin: "0.6rem auto 1rem" }}>
|
|
396
|
+
<LivingBrain
|
|
397
|
+
state={brainStateForStage}
|
|
398
|
+
intensity={stage === "download" || stage === "load" ? 0.96 : 0.82}
|
|
399
|
+
size="normal"
|
|
400
|
+
/>
|
|
349
401
|
</div>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
402
|
+
|
|
403
|
+
<div className="ritual-progress">
|
|
404
|
+
<div className="ritual-stage-list">
|
|
405
|
+
{(["install", "download", "validate", "load"] as const).map((item) => (
|
|
406
|
+
<div key={item} className={`ritual-stage ${installStepState(stage, item)}`}>
|
|
407
|
+
<CheckCircle2 style={{ width: 15, height: 15 }} />
|
|
408
|
+
<span>{installLabel(item)}</span>
|
|
409
|
+
</div>
|
|
410
|
+
))}
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div className="ritual-bar">
|
|
414
|
+
<span style={{ width: `${Math.max(4, Math.min(100, percent))}%` }} />
|
|
354
415
|
</div>
|
|
355
|
-
<div className="install-bar"><span style={{ width: `${Math.max(0, Math.min(100, percent))}%` }} /></div>
|
|
356
416
|
</div>
|
|
357
|
-
|
|
358
|
-
<div className="
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
417
|
+
|
|
418
|
+
<div className="ritual-status">{message}</div>
|
|
419
|
+
|
|
420
|
+
{error && (
|
|
421
|
+
<div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.45)", background: "hsl(var(--destructive)/0.07)", marginBottom: "1rem" }}>
|
|
422
|
+
{error}
|
|
423
|
+
<div style={{ marginTop: "0.5rem", fontSize: "0.85rem" }}>You can go back and choose a different mind, or try again.</div>
|
|
424
|
+
</div>
|
|
425
|
+
)}
|
|
426
|
+
|
|
427
|
+
<div style={{ display: "flex", gap: "0.75rem", justifyContent: "center", marginTop: "1rem" }}>
|
|
428
|
+
<Button variant="ghost" onClick={onBack} disabled={busy}>Choose differently</Button>
|
|
429
|
+
|
|
430
|
+
{stage !== "done" ? (
|
|
431
|
+
<Button
|
|
432
|
+
onClick={start}
|
|
433
|
+
disabled={busy || !model.supported}
|
|
434
|
+
>
|
|
435
|
+
{busy ? "Waking the mind..." : "Yes — make this my Brain"}
|
|
436
|
+
</Button>
|
|
437
|
+
) : (
|
|
438
|
+
<Button onClick={onComplete}>Enter your Brain</Button>
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
<div style={{ fontSize: "0.72rem", color: "hsl(var(--fg-muted))", marginTop: "0.9rem" }}>
|
|
443
|
+
Explicit consent only. All work happens locally on your machine.
|
|
363
444
|
</div>
|
|
364
|
-
</
|
|
445
|
+
</div>
|
|
365
446
|
);
|
|
366
447
|
}
|
|
367
448
|
|