ltcai 4.7.0 → 4.7.2
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 +49 -38
- package/docs/CHANGELOG.md +80 -0
- package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +49 -0
- package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +62 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +16 -14
- package/frontend/src/App.tsx +191 -15
- package/frontend/src/api/client.ts +9 -1
- package/frontend/src/components/ProductFlow.tsx +89 -57
- package/frontend/src/pages/System.tsx +1 -1
- package/frontend/src/styles.css +205 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +112 -3
- package/latticeai/api/chat.py +11 -3
- package/latticeai/app_factory.py +0 -2
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +2 -1
- 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-DdAB4yfa.js +16 -0
- package/static/app/assets/index-DdAB4yfa.js.map +1 -0
- package/static/app/assets/{index-DFmuiJ6t.css → index-KlQ04wVv.css} +1 -1
- package/static/app/index.html +2 -2
- package/scripts/launch-pts-grok.sh +0 -56
- package/static/app/assets/index-DwX3rNfA.js +0 -16
- package/static/app/assets/index-DwX3rNfA.js.map +0 -1
|
@@ -43,6 +43,14 @@ export function readProductFlowComplete() {
|
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function readSavedFlowUser(): { email?: string; name?: string } | null {
|
|
47
|
+
try {
|
|
48
|
+
const raw = localStorage.getItem(FLOW_USER_KEY);
|
|
49
|
+
return raw ? JSON.parse(raw) : null;
|
|
50
|
+
} catch {}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
export function ProductFlow({ onComplete }: { onComplete: () => void }) {
|
|
47
55
|
const [step, setStep] = React.useState<FlowStep>("login");
|
|
48
56
|
const [analysis, setAnalysis] = React.useState<FlowAnalysis | null>(null);
|
|
@@ -135,51 +143,67 @@ export function ProductFlow({ onComplete }: { onComplete: () => void }) {
|
|
|
135
143
|
|
|
136
144
|
function LoginScreen({ onSuccess }: { onSuccess: () => void }) {
|
|
137
145
|
const [email, setEmail] = React.useState(() => {
|
|
138
|
-
|
|
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
|
-
}
|
|
146
|
+
return readSavedFlowUser()?.email || "you@local";
|
|
144
147
|
});
|
|
145
148
|
const [password, setPassword] = React.useState("");
|
|
146
|
-
const [name, setName] = React.useState("You");
|
|
149
|
+
const [name, setName] = React.useState(() => readSavedFlowUser()?.name || "You");
|
|
147
150
|
const [busy, setBusy] = React.useState(false);
|
|
148
151
|
const [error, setError] = React.useState<string | null>(null);
|
|
149
152
|
|
|
150
153
|
async function submit(event: React.FormEvent) {
|
|
151
154
|
event.preventDefault();
|
|
155
|
+
const cleanEmail = email.trim();
|
|
156
|
+
const cleanPassword = password.trim();
|
|
157
|
+
const cleanName = name.trim() || cleanEmail.split("@")[0] || "You";
|
|
158
|
+
if (!cleanEmail || !cleanPassword) {
|
|
159
|
+
setError("이름과 이메일, 비밀번호를 입력하면 기존 Brain을 안전하게 확인합니다.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
152
162
|
setBusy(true);
|
|
153
163
|
setError(null);
|
|
154
|
-
const
|
|
155
|
-
let result = await latticeApi.login(
|
|
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
|
-
}
|
|
164
|
+
const savedUser = readSavedFlowUser();
|
|
165
|
+
let result = await latticeApi.login(cleanEmail, cleanPassword);
|
|
165
166
|
if (!result.ok) {
|
|
166
167
|
const profile = await latticeApi.profile();
|
|
167
|
-
if (
|
|
168
|
+
if (profile.ok && (!savedUser?.email || savedUser.email === cleanEmail)) {
|
|
169
|
+
try { localStorage.setItem(FLOW_USER_KEY, JSON.stringify({ email: cleanEmail, name: cleanName })); } catch {}
|
|
170
|
+
setBusy(false);
|
|
171
|
+
onSuccess();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (savedUser?.email && savedUser.email !== cleanEmail) {
|
|
175
|
+
setBusy(false);
|
|
176
|
+
setError("이 컴퓨터의 기존 Brain과 다른 이메일입니다. 오타인지 확인해 주세요.");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (savedUser?.email === cleanEmail) {
|
|
168
180
|
setBusy(false);
|
|
169
|
-
setError("
|
|
181
|
+
setError("기존 Brain 이메일은 맞지만 비밀번호가 다릅니다. 비밀번호를 다시 확인해 주세요.");
|
|
170
182
|
return;
|
|
171
183
|
}
|
|
184
|
+
const registered = await latticeApi.register({
|
|
185
|
+
email: cleanEmail,
|
|
186
|
+
password: cleanPassword,
|
|
187
|
+
name: cleanName,
|
|
188
|
+
nickname: cleanName,
|
|
189
|
+
});
|
|
190
|
+
if (registered.ok) result = await latticeApi.login(cleanEmail, cleanPassword);
|
|
191
|
+
}
|
|
192
|
+
if (!result.ok) {
|
|
193
|
+
setBusy(false);
|
|
194
|
+
setError("로컬 프로필을 열 수 없습니다. 이메일과 비밀번호를 확인해 주세요.");
|
|
195
|
+
return;
|
|
172
196
|
}
|
|
173
|
-
try { localStorage.setItem(FLOW_USER_KEY, JSON.stringify({ email, name })); } catch {}
|
|
197
|
+
try { localStorage.setItem(FLOW_USER_KEY, JSON.stringify({ email: cleanEmail, name: cleanName })); } catch {}
|
|
174
198
|
setBusy(false);
|
|
175
199
|
onSuccess();
|
|
176
200
|
}
|
|
177
201
|
|
|
178
202
|
return (
|
|
179
203
|
<div>
|
|
180
|
-
<div className="ritual-title"
|
|
204
|
+
<div className="ritual-title">내 Brain을 시작합니다.</div>
|
|
181
205
|
<div className="ritual-subtitle">
|
|
182
|
-
|
|
206
|
+
모델은 바뀔 수 있지만, 내 문서와 대화, 결정, 기억은 사라지면 안 됩니다. Lattice는 이 지식을 내가 소유하는 개인 Brain으로 모읍니다.
|
|
183
207
|
</div>
|
|
184
208
|
|
|
185
209
|
<ProductPromise />
|
|
@@ -187,26 +211,26 @@ function LoginScreen({ onSuccess }: { onSuccess: () => void }) {
|
|
|
187
211
|
<form onSubmit={submit} className="ritual-card" style={{ maxWidth: 420, margin: "0 auto" }}>
|
|
188
212
|
<div style={{ display: "grid", gap: "0.85rem" }}>
|
|
189
213
|
<div>
|
|
190
|
-
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}
|
|
214
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>이름</div>
|
|
191
215
|
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="You" />
|
|
192
216
|
</div>
|
|
193
217
|
<div>
|
|
194
|
-
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}
|
|
218
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>이메일</div>
|
|
195
219
|
<Input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="you@local" />
|
|
196
220
|
</div>
|
|
197
221
|
<div>
|
|
198
|
-
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}
|
|
199
|
-
<Input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="
|
|
222
|
+
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "1px", color: "hsl(var(--fg-muted))", marginBottom: 4 }}>비밀번호</div>
|
|
223
|
+
<Input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="로컬 Brain 비밀번호" />
|
|
200
224
|
</div>
|
|
201
225
|
</div>
|
|
202
226
|
|
|
203
227
|
{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
228
|
|
|
205
|
-
<Button type="submit" disabled={busy || !email.trim()} style={{ width: "100%", marginTop: "1rem" }}>
|
|
206
|
-
{busy ? "
|
|
229
|
+
<Button type="submit" disabled={busy || !email.trim() || !password.trim()} style={{ width: "100%", marginTop: "1rem" }}>
|
|
230
|
+
{busy ? "Brain 여는 중..." : "내 Brain 시작하기"}
|
|
207
231
|
</Button>
|
|
208
232
|
<div style={{ fontSize: "0.75rem", color: "hsl(var(--fg-muted))", marginTop: "0.6rem" }}>
|
|
209
|
-
|
|
233
|
+
기존 Brain과 다른 이메일이면 새로 만들지 않고 먼저 확인합니다.
|
|
210
234
|
</div>
|
|
211
235
|
</form>
|
|
212
236
|
</div>
|
|
@@ -217,16 +241,16 @@ function ProductPromise() {
|
|
|
217
241
|
return (
|
|
218
242
|
<div className="ritual-promise" aria-label="Lattice AI product promise">
|
|
219
243
|
<div>
|
|
220
|
-
<span
|
|
221
|
-
<strong
|
|
244
|
+
<span>오래 남는 지식</span>
|
|
245
|
+
<strong>내 일이 장기 기억이 됩니다.</strong>
|
|
222
246
|
</div>
|
|
223
247
|
<div>
|
|
224
|
-
<span
|
|
225
|
-
<strong
|
|
248
|
+
<span>교체 가능한 모델</span>
|
|
249
|
+
<strong>모델은 목소리이고, 자산은 Brain입니다.</strong>
|
|
226
250
|
</div>
|
|
227
251
|
<div>
|
|
228
|
-
<span
|
|
229
|
-
<strong
|
|
252
|
+
<span>사용자 소유</span>
|
|
253
|
+
<strong>백업, 복원, 이동이 가능합니다.</strong>
|
|
230
254
|
</div>
|
|
231
255
|
</div>
|
|
232
256
|
);
|
|
@@ -244,9 +268,9 @@ function AnalysisScreen({
|
|
|
244
268
|
const detected = buildDetectedFacts(analysis);
|
|
245
269
|
return (
|
|
246
270
|
<div>
|
|
247
|
-
<div className="ritual-title"
|
|
271
|
+
<div className="ritual-title">이 컴퓨터를 확인합니다.</div>
|
|
248
272
|
<div className="ritual-subtitle">
|
|
249
|
-
|
|
273
|
+
Brain이 클라우드에 의존하지 않고 이 컴퓨터에서 편하게 돌아갈 수 있는지 확인합니다.
|
|
250
274
|
</div>
|
|
251
275
|
|
|
252
276
|
<div className="ritual-fact-grid">
|
|
@@ -263,9 +287,9 @@ function AnalysisScreen({
|
|
|
263
287
|
<div style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
|
|
264
288
|
<Sparkles style={{ color: "hsl(var(--brain-core))" }} />
|
|
265
289
|
<div>
|
|
266
|
-
<div style={{ fontWeight: 620 }}>{analysis ? recommendedSummary(analysis) : "
|
|
290
|
+
<div style={{ fontWeight: 620 }}>{analysis ? recommendedSummary(analysis) : "가장 편한 설정을 찾는 중..."}</div>
|
|
267
291
|
<div style={{ fontSize: "0.9rem", color: "hsl(var(--fg-muted))" }}>
|
|
268
|
-
{analysis ? "
|
|
292
|
+
{analysis ? "추천 모델을 바로 시작할 수 있게 준비했습니다." : "잠시만 기다리면 자동으로 정리됩니다."}
|
|
269
293
|
</div>
|
|
270
294
|
</div>
|
|
271
295
|
</div>
|
|
@@ -275,7 +299,7 @@ function AnalysisScreen({
|
|
|
275
299
|
|
|
276
300
|
<div style={{ marginTop: "1.25rem" }}>
|
|
277
301
|
<Button onClick={onContinue} disabled={!analysis && !error} style={{ minWidth: 260 }}>
|
|
278
|
-
|
|
302
|
+
추천 모델 보기
|
|
279
303
|
</Button>
|
|
280
304
|
</div>
|
|
281
305
|
</div>
|
|
@@ -294,12 +318,17 @@ function RecommendationScreen({
|
|
|
294
318
|
const items = recommendations.length ? recommendations : [fallbackModel()];
|
|
295
319
|
return (
|
|
296
320
|
<div>
|
|
297
|
-
<div className="ritual-title"
|
|
321
|
+
<div className="ritual-title">추천 모델로 시작하세요.</div>
|
|
298
322
|
<div className="ritual-subtitle">
|
|
299
|
-
|
|
323
|
+
모델은 Brain의 현재 목소리입니다. 나중에 바꿔도 기억과 지식은 그대로 남습니다.
|
|
300
324
|
</div>
|
|
301
325
|
|
|
302
326
|
<div style={{ maxWidth: 560, margin: "0 auto" }}>
|
|
327
|
+
{items[0]?.supported ? (
|
|
328
|
+
<Button onClick={() => onSelect(items[0])} style={{ width: "100%", marginBottom: "0.85rem" }}>
|
|
329
|
+
추천대로 시작하기
|
|
330
|
+
</Button>
|
|
331
|
+
) : null}
|
|
303
332
|
{items.slice(0, 3).map((model, index) => (
|
|
304
333
|
<button
|
|
305
334
|
key={`${model.role}-${model.id}`}
|
|
@@ -311,14 +340,14 @@ function RecommendationScreen({
|
|
|
311
340
|
<div className="role">{rankLabel(model.role, index)}</div>
|
|
312
341
|
<div className="name">{model.shortName}</div>
|
|
313
342
|
<div className="reason">{model.reason} · {model.size || "ready"}</div>
|
|
314
|
-
{!model.supported && <div style={{ color: "hsl(var(--destructive))", marginTop: 6, fontSize: "0.85rem" }}
|
|
343
|
+
{!model.supported && <div style={{ color: "hsl(var(--destructive))", marginTop: 6, fontSize: "0.85rem" }}>이 컴퓨터에서 추가 확인이 필요합니다</div>}
|
|
315
344
|
</button>
|
|
316
345
|
))}
|
|
317
346
|
</div>
|
|
318
347
|
|
|
319
348
|
<div style={{ marginTop: "1.1rem", display: "flex", justifyContent: "center", gap: "1rem", alignItems: "center" }}>
|
|
320
|
-
<Button variant="ghost" onClick={onBack}
|
|
321
|
-
<div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}
|
|
349
|
+
<Button variant="ghost" onClick={onBack}>뒤로</Button>
|
|
350
|
+
<div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}>잘 모르겠다면 추천대로 시작하면 됩니다.</div>
|
|
322
351
|
</div>
|
|
323
352
|
</div>
|
|
324
353
|
);
|
|
@@ -336,7 +365,7 @@ function InstallScreen({
|
|
|
336
365
|
const [busy, setBusy] = React.useState(false);
|
|
337
366
|
const [stage, setStage] = React.useState<InstallStage>("idle");
|
|
338
367
|
const [percent, setPercent] = React.useState(0);
|
|
339
|
-
const [message, setMessage] = React.useState("
|
|
368
|
+
const [message, setMessage] = React.useState("Brain이 사용할 모델을 기다리고 있습니다.");
|
|
340
369
|
const [error, setError] = React.useState<string | null>(null);
|
|
341
370
|
|
|
342
371
|
async function start() {
|
|
@@ -344,7 +373,7 @@ function InstallScreen({
|
|
|
344
373
|
setError(null);
|
|
345
374
|
setStage("install");
|
|
346
375
|
setPercent(8);
|
|
347
|
-
setMessage("
|
|
376
|
+
setMessage("Brain 준비 중입니다.");
|
|
348
377
|
const result = await latticeApi.streamModelPrepare(
|
|
349
378
|
{ model: model.loadId, engine: model.engine || "local_mlx", allow_download: true },
|
|
350
379
|
{
|
|
@@ -357,7 +386,7 @@ function InstallScreen({
|
|
|
357
386
|
onDone: () => {
|
|
358
387
|
setStage("done");
|
|
359
388
|
setPercent(100);
|
|
360
|
-
setMessage("
|
|
389
|
+
setMessage("Brain이 준비되었습니다.");
|
|
361
390
|
},
|
|
362
391
|
onError: (event) => {
|
|
363
392
|
setStage("error");
|
|
@@ -369,7 +398,7 @@ function InstallScreen({
|
|
|
369
398
|
if (result.ok) {
|
|
370
399
|
setStage("done");
|
|
371
400
|
setPercent(100);
|
|
372
|
-
setMessage("
|
|
401
|
+
setMessage("Brain이 준비되었습니다.");
|
|
373
402
|
window.setTimeout(onComplete, 700);
|
|
374
403
|
} else {
|
|
375
404
|
setStage("error");
|
|
@@ -385,10 +414,10 @@ function InstallScreen({
|
|
|
385
414
|
|
|
386
415
|
return (
|
|
387
416
|
<div>
|
|
388
|
-
<div className="ritual-title"
|
|
417
|
+
<div className="ritual-title">모델을 설치하고 시작합니다.</div>
|
|
389
418
|
<div className="ritual-subtitle">
|
|
390
419
|
<strong>{model.shortName}</strong> — {model.reason}.<br />
|
|
391
|
-
|
|
420
|
+
이 모델이 Brain의 로컬 목소리가 됩니다. 다운로드, 확인, 로드는 사용자가 누른 뒤에만 진행됩니다.
|
|
392
421
|
</div>
|
|
393
422
|
|
|
394
423
|
{/* Living Brain reacts to the ceremony of installation */}
|
|
@@ -416,31 +445,34 @@ function InstallScreen({
|
|
|
416
445
|
</div>
|
|
417
446
|
|
|
418
447
|
<div className="ritual-status">{message}</div>
|
|
448
|
+
<div className="ritual-card" style={{ margin: "0.8rem auto 0", maxWidth: 540, fontSize: "0.86rem", color: "hsl(var(--fg-muted))" }}>
|
|
449
|
+
큰 모델은 다운로드에 몇 분 이상 걸릴 수 있습니다. 진행률은 모델 런타임이 알려주는 만큼 표시하고, 멈춘 것처럼 보여도 현재 단계가 유지됩니다.
|
|
450
|
+
</div>
|
|
419
451
|
|
|
420
452
|
{error && (
|
|
421
453
|
<div className="ritual-card" style={{ borderColor: "hsl(var(--destructive)/0.45)", background: "hsl(var(--destructive)/0.07)", marginBottom: "1rem" }}>
|
|
422
454
|
{error}
|
|
423
|
-
<div style={{ marginTop: "0.5rem", fontSize: "0.85rem" }}
|
|
455
|
+
<div style={{ marginTop: "0.5rem", fontSize: "0.85rem" }}>다른 모델을 고르거나 다시 시도할 수 있습니다.</div>
|
|
424
456
|
</div>
|
|
425
457
|
)}
|
|
426
458
|
|
|
427
459
|
<div style={{ display: "flex", gap: "0.75rem", justifyContent: "center", marginTop: "1rem" }}>
|
|
428
|
-
<Button variant="ghost" onClick={onBack} disabled={busy}
|
|
460
|
+
<Button variant="ghost" onClick={onBack} disabled={busy}>다른 모델 고르기</Button>
|
|
429
461
|
|
|
430
462
|
{stage !== "done" ? (
|
|
431
463
|
<Button
|
|
432
464
|
onClick={start}
|
|
433
465
|
disabled={busy || !model.supported}
|
|
434
466
|
>
|
|
435
|
-
{busy ? "
|
|
467
|
+
{busy ? "모델 준비 중..." : "다운로드하고 시작하기"}
|
|
436
468
|
</Button>
|
|
437
469
|
) : (
|
|
438
|
-
<Button onClick={onComplete}>
|
|
470
|
+
<Button onClick={onComplete}>Brain으로 들어가기</Button>
|
|
439
471
|
)}
|
|
440
472
|
</div>
|
|
441
473
|
|
|
442
474
|
<div style={{ fontSize: "0.72rem", color: "hsl(var(--fg-muted))", marginTop: "0.9rem" }}>
|
|
443
|
-
|
|
475
|
+
모든 작업은 이 컴퓨터에서 진행됩니다.
|
|
444
476
|
</div>
|
|
445
477
|
</div>
|
|
446
478
|
);
|
|
@@ -590,7 +590,7 @@ function AdminPanel() {
|
|
|
590
590
|
const mode = useAppStore((state) => state.mode);
|
|
591
591
|
const summary = useQuery({ queryKey: ["adminSummary"], queryFn: latticeApi.adminSummary });
|
|
592
592
|
const users = useQuery({ queryKey: ["adminUsers"], queryFn: latticeApi.adminUsers });
|
|
593
|
-
const audit = useQuery({ queryKey: ["adminAudit"], queryFn: latticeApi.adminAudit });
|
|
593
|
+
const audit = useQuery({ queryKey: ["adminAudit"], queryFn: () => latticeApi.adminAudit() });
|
|
594
594
|
const roles = useQuery({ queryKey: ["adminRoles"], queryFn: latticeApi.adminRoles });
|
|
595
595
|
const policies = useQuery({ queryKey: ["adminPolicies"], queryFn: latticeApi.adminPolicies });
|
|
596
596
|
const hardening = useQuery({ queryKey: ["adminProductHardening"], queryFn: latticeApi.adminProductHardening });
|
package/frontend/src/styles.css
CHANGED
|
@@ -1298,6 +1298,107 @@ body {
|
|
|
1298
1298
|
mask-image: linear-gradient(transparent, black 6%, black 92%, transparent);
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
|
+
.brain-overview-panel {
|
|
1302
|
+
margin: 0 0 1rem;
|
|
1303
|
+
border: 1px solid hsl(var(--border) / 0.58);
|
|
1304
|
+
border-radius: 8px;
|
|
1305
|
+
background: hsl(var(--surface-glass));
|
|
1306
|
+
padding: 0.8rem;
|
|
1307
|
+
box-shadow: 0 14px 34px hsl(200 30% 3% / 0.16);
|
|
1308
|
+
backdrop-filter: blur(18px);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.brain-overview-head {
|
|
1312
|
+
display: flex;
|
|
1313
|
+
align-items: center;
|
|
1314
|
+
justify-content: space-between;
|
|
1315
|
+
gap: 0.8rem;
|
|
1316
|
+
margin-bottom: 0.72rem;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.brain-overview-head span {
|
|
1320
|
+
display: block;
|
|
1321
|
+
color: hsl(var(--fg-muted));
|
|
1322
|
+
font-size: 0.68rem;
|
|
1323
|
+
font-weight: 820;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.brain-overview-head strong {
|
|
1327
|
+
display: block;
|
|
1328
|
+
margin-top: 0.14rem;
|
|
1329
|
+
font-size: 0.94rem;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.brain-overview-head button {
|
|
1333
|
+
min-height: 2rem;
|
|
1334
|
+
border: 1px solid hsl(var(--brain-core) / 0.38);
|
|
1335
|
+
border-radius: 999px;
|
|
1336
|
+
background: hsl(var(--brain-core) / 0.08);
|
|
1337
|
+
color: hsl(var(--fg));
|
|
1338
|
+
padding: 0 0.75rem;
|
|
1339
|
+
font-size: 0.78rem;
|
|
1340
|
+
font-weight: 760;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.brain-overview-grid {
|
|
1344
|
+
display: grid;
|
|
1345
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1346
|
+
gap: 0.62rem;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.brain-overview-column {
|
|
1350
|
+
display: grid;
|
|
1351
|
+
min-height: 6.4rem;
|
|
1352
|
+
align-content: start;
|
|
1353
|
+
gap: 0.34rem;
|
|
1354
|
+
border: 1px solid hsl(var(--border) / 0.5);
|
|
1355
|
+
border-radius: 8px;
|
|
1356
|
+
background: hsl(var(--bg) / 0.38);
|
|
1357
|
+
color: hsl(var(--fg));
|
|
1358
|
+
padding: 0.66rem;
|
|
1359
|
+
text-align: left;
|
|
1360
|
+
transition: border-color 150ms ease, background 150ms ease;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
.brain-overview-column:hover,
|
|
1364
|
+
.brain-overview-column:focus-visible {
|
|
1365
|
+
border-color: hsl(var(--brain-core) / 0.65);
|
|
1366
|
+
background: hsl(var(--brain-core) / 0.07);
|
|
1367
|
+
outline: none;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.brain-overview-column span {
|
|
1371
|
+
color: hsl(var(--fg-muted));
|
|
1372
|
+
font-size: 0.68rem;
|
|
1373
|
+
font-weight: 820;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
.brain-overview-column strong {
|
|
1377
|
+
display: block;
|
|
1378
|
+
overflow: hidden;
|
|
1379
|
+
text-overflow: ellipsis;
|
|
1380
|
+
white-space: nowrap;
|
|
1381
|
+
font-size: 0.8rem;
|
|
1382
|
+
line-height: 1.2;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
.brain-overview-column em {
|
|
1386
|
+
color: hsl(var(--fg-muted));
|
|
1387
|
+
font-size: 0.8rem;
|
|
1388
|
+
font-style: normal;
|
|
1389
|
+
line-height: 1.35;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
.brain-save-feedback {
|
|
1393
|
+
border: 1px solid hsl(var(--memory) / 0.38);
|
|
1394
|
+
border-radius: 8px;
|
|
1395
|
+
background: hsl(var(--memory) / 0.08);
|
|
1396
|
+
color: hsl(var(--fg));
|
|
1397
|
+
padding: 0.58rem 0.72rem;
|
|
1398
|
+
font-size: 0.84rem;
|
|
1399
|
+
font-weight: 720;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1301
1402
|
.brain-message {
|
|
1302
1403
|
max-width: 78%;
|
|
1303
1404
|
margin-bottom: 1.1rem;
|
|
@@ -1741,6 +1842,43 @@ body {
|
|
|
1741
1842
|
line-height: 1.05;
|
|
1742
1843
|
}
|
|
1743
1844
|
|
|
1845
|
+
.brain-depth-actions {
|
|
1846
|
+
position: absolute;
|
|
1847
|
+
left: 50%;
|
|
1848
|
+
bottom: 4.15rem;
|
|
1849
|
+
z-index: 6;
|
|
1850
|
+
display: flex;
|
|
1851
|
+
max-width: min(94vw, 31rem);
|
|
1852
|
+
transform: translateX(-50%);
|
|
1853
|
+
gap: 0.35rem;
|
|
1854
|
+
border: 1px solid hsl(var(--border) / 0.52);
|
|
1855
|
+
border-radius: 999px;
|
|
1856
|
+
background: hsl(var(--surface-glass));
|
|
1857
|
+
padding: 0.32rem;
|
|
1858
|
+
box-shadow: 0 14px 38px hsl(200 30% 3% / 0.22);
|
|
1859
|
+
backdrop-filter: blur(18px);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.brain-depth-actions button {
|
|
1863
|
+
min-height: 2rem;
|
|
1864
|
+
border: 0;
|
|
1865
|
+
border-radius: 999px;
|
|
1866
|
+
background: transparent;
|
|
1867
|
+
color: hsl(var(--fg-muted));
|
|
1868
|
+
padding: 0 0.68rem;
|
|
1869
|
+
font-size: 0.78rem;
|
|
1870
|
+
font-weight: 760;
|
|
1871
|
+
white-space: nowrap;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
.brain-depth-actions button:hover,
|
|
1875
|
+
.brain-depth-actions button:focus-visible,
|
|
1876
|
+
.brain-depth-actions button.is-active {
|
|
1877
|
+
background: hsl(var(--brain-core) / 0.12);
|
|
1878
|
+
color: hsl(var(--fg));
|
|
1879
|
+
outline: none;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1744
1882
|
.memory-fragment,
|
|
1745
1883
|
.concept-signal,
|
|
1746
1884
|
.graph-node {
|
|
@@ -2349,6 +2487,43 @@ body {
|
|
|
2349
2487
|
gap: 0.45rem;
|
|
2350
2488
|
}
|
|
2351
2489
|
|
|
2490
|
+
.admin-log-filters {
|
|
2491
|
+
display: grid;
|
|
2492
|
+
grid-template-columns: minmax(0, 1fr) minmax(8.5rem, 0.65fr) auto;
|
|
2493
|
+
gap: 0.45rem;
|
|
2494
|
+
align-items: center;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
.admin-log-filters label {
|
|
2498
|
+
display: flex;
|
|
2499
|
+
min-width: 0;
|
|
2500
|
+
align-items: center;
|
|
2501
|
+
gap: 0.4rem;
|
|
2502
|
+
border: 1px solid hsl(var(--border) / 0.54);
|
|
2503
|
+
border-radius: 8px;
|
|
2504
|
+
background: hsl(var(--bg) / 0.34);
|
|
2505
|
+
padding: 0.44rem 0.52rem;
|
|
2506
|
+
color: hsl(var(--fg-muted));
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.admin-log-filters input,
|
|
2510
|
+
.admin-log-filters select {
|
|
2511
|
+
min-width: 0;
|
|
2512
|
+
width: 100%;
|
|
2513
|
+
border: 0;
|
|
2514
|
+
outline: 0;
|
|
2515
|
+
background: transparent;
|
|
2516
|
+
color: hsl(var(--fg));
|
|
2517
|
+
font: inherit;
|
|
2518
|
+
font-size: 0.78rem;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
.admin-log-filters span,
|
|
2522
|
+
.admin-retention span {
|
|
2523
|
+
color: hsl(var(--fg-muted));
|
|
2524
|
+
font-size: 0.76rem;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2352
2527
|
.admin-list-row {
|
|
2353
2528
|
display: flex;
|
|
2354
2529
|
min-height: 3.15rem;
|
|
@@ -2427,6 +2602,20 @@ body {
|
|
|
2427
2602
|
font-size: 0.74rem;
|
|
2428
2603
|
}
|
|
2429
2604
|
|
|
2605
|
+
.admin-retention {
|
|
2606
|
+
display: grid;
|
|
2607
|
+
gap: 0.18rem;
|
|
2608
|
+
border: 1px solid hsl(var(--border) / 0.48);
|
|
2609
|
+
border-radius: 8px;
|
|
2610
|
+
background: hsl(var(--bg) / 0.35);
|
|
2611
|
+
padding: 0.68rem;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
.admin-retention strong {
|
|
2615
|
+
color: hsl(var(--fg));
|
|
2616
|
+
font-size: 0.88rem;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2430
2619
|
.brain-image-input {
|
|
2431
2620
|
display: inline-flex;
|
|
2432
2621
|
cursor: pointer;
|
|
@@ -2583,6 +2772,22 @@ body {
|
|
|
2583
2772
|
.brain-organism { width: 200px; height: 200px; }
|
|
2584
2773
|
.brain-conversation { padding-bottom: 5.5rem; }
|
|
2585
2774
|
.brain-conversation-header { flex-wrap: wrap; }
|
|
2775
|
+
.brain-depth-actions {
|
|
2776
|
+
bottom: 3.55rem;
|
|
2777
|
+
width: min(100vw - 1rem, 24rem);
|
|
2778
|
+
overflow-x: auto;
|
|
2779
|
+
justify-content: flex-start;
|
|
2780
|
+
}
|
|
2781
|
+
.brain-depth-actions button {
|
|
2782
|
+
flex: 0 0 auto;
|
|
2783
|
+
}
|
|
2784
|
+
.brain-overview-grid {
|
|
2785
|
+
grid-template-columns: 1fr;
|
|
2786
|
+
}
|
|
2787
|
+
.brain-overview-head {
|
|
2788
|
+
align-items: flex-start;
|
|
2789
|
+
flex-direction: column;
|
|
2790
|
+
}
|
|
2586
2791
|
.brain-care-summary {
|
|
2587
2792
|
display: grid;
|
|
2588
2793
|
}
|
|
@@ -14,7 +14,7 @@ from datetime import datetime
|
|
|
14
14
|
from typing import Any, Callable, Dict, List, Optional
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
MULTI_AGENT_VERSION = "4.7.
|
|
17
|
+
MULTI_AGENT_VERSION = "4.7.2"
|
|
18
18
|
|
|
19
19
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
20
20
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED