ltcai 4.7.2 → 5.0.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 +32 -32
- package/docs/CHANGELOG.md +39 -0
- package/frontend/src/App.tsx +104 -69
- package/frontend/src/components/ProductFlow.tsx +102 -69
- package/frontend/src/i18n.ts +245 -0
- package/frontend/src/store/appStore.ts +18 -0
- package/frontend/src/styles.css +36 -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/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +1 -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-KlQ04wVv.css → index-DuYYT2oh.css} +1 -1
- package/static/app/assets/index-FR1UZkCD.js +16 -0
- package/static/app/assets/index-FR1UZkCD.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-DdAB4yfa.js +0 -16
- package/static/app/assets/index-DdAB4yfa.js.map +0 -1
package/frontend/src/App.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import { type BrainState, LivingBrain, triggerBrainRecall } from "@/components/L
|
|
|
23
23
|
import { ProductFlow, readProductFlowComplete } from "@/components/ProductFlow";
|
|
24
24
|
import { useAppStore } from "@/store/appStore";
|
|
25
25
|
import { asArray } from "@/lib/utils";
|
|
26
|
+
import { LANGUAGE_LABELS, t, type Language } from "@/i18n";
|
|
26
27
|
|
|
27
28
|
type ApiRecord = Record<string, unknown>;
|
|
28
29
|
type BrainDepth = 1 | 2 | 3 | 4 | 5;
|
|
@@ -68,21 +69,17 @@ const DEPTHS: Array<{ level: BrainDepth; label: string; state: BrainState }> = [
|
|
|
68
69
|
{ level: 5, label: "Knowledge Graph", state: "synthesizing" },
|
|
69
70
|
];
|
|
70
71
|
|
|
71
|
-
const STARTER_PROMPTS = [
|
|
72
|
-
"Remember this decision: ",
|
|
73
|
-
"What do I already know about ",
|
|
74
|
-
"Help me turn this project context into a plan: ",
|
|
75
|
-
];
|
|
76
|
-
|
|
77
72
|
export default function App() {
|
|
78
73
|
const theme = useAppStore((state) => state.theme);
|
|
74
|
+
const language = useAppStore((state) => state.language);
|
|
79
75
|
const [flowComplete, setFlowComplete] = React.useState(readProductFlowComplete);
|
|
80
76
|
const route = useHashRoute();
|
|
81
77
|
const { state: brainState, intensity, setBrain } = useBrainState();
|
|
82
78
|
|
|
83
79
|
React.useEffect(() => {
|
|
84
80
|
document.documentElement.dataset.theme = theme;
|
|
85
|
-
|
|
81
|
+
document.documentElement.lang = language === "ko" ? "ko" : "en";
|
|
82
|
+
}, [theme, language]);
|
|
86
83
|
|
|
87
84
|
React.useEffect(() => {
|
|
88
85
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -153,6 +150,7 @@ function BrainHome({
|
|
|
153
150
|
onBrainChange: (state: BrainState, intensity?: number) => void;
|
|
154
151
|
}) {
|
|
155
152
|
const qc = useQueryClient();
|
|
153
|
+
const language = useAppStore((state) => state.language);
|
|
156
154
|
const [messages, setMessages] = React.useState<Message[]>([]);
|
|
157
155
|
const [draft, setDraft] = React.useState("");
|
|
158
156
|
const [imageData, setImageData] = React.useState<string | null>(null);
|
|
@@ -185,6 +183,14 @@ function BrainHome({
|
|
|
185
183
|
);
|
|
186
184
|
const modelName = React.useMemo(() => currentModelName(modelsQ.data?.data), [modelsQ.data]);
|
|
187
185
|
const currentDepth = DEPTHS[explorationDepth - 1];
|
|
186
|
+
const starterPrompts = React.useMemo(
|
|
187
|
+
() => [
|
|
188
|
+
t(language, "brain.prompt.remember"),
|
|
189
|
+
t(language, "brain.prompt.know"),
|
|
190
|
+
t(language, "brain.prompt.plan"),
|
|
191
|
+
],
|
|
192
|
+
[language],
|
|
193
|
+
);
|
|
188
194
|
|
|
189
195
|
React.useEffect(() => {
|
|
190
196
|
if (streaming) onBrainChange("thinking", 0.94);
|
|
@@ -243,7 +249,7 @@ function BrainHome({
|
|
|
243
249
|
return next;
|
|
244
250
|
});
|
|
245
251
|
} else {
|
|
246
|
-
setMemoryFeedback(
|
|
252
|
+
setMemoryFeedback(t(language, "brain.saved", { topics: knowledgeConcepts.length, memories: memoryFragments.length }));
|
|
247
253
|
}
|
|
248
254
|
} finally {
|
|
249
255
|
setStreaming(false);
|
|
@@ -282,7 +288,7 @@ function BrainHome({
|
|
|
282
288
|
setExplorationDepth((depth) => Math.max(depth, 2) as BrainDepth);
|
|
283
289
|
setMessages((items) => [
|
|
284
290
|
...items,
|
|
285
|
-
{ role: "assistant", content:
|
|
291
|
+
{ role: "assistant", content: t(language, "brain.recalled", { title: fragment.title }) },
|
|
286
292
|
]);
|
|
287
293
|
}
|
|
288
294
|
|
|
@@ -300,15 +306,15 @@ function BrainHome({
|
|
|
300
306
|
/>
|
|
301
307
|
|
|
302
308
|
<div className="brain-depth-badge" aria-live="polite">
|
|
303
|
-
<span>
|
|
304
|
-
<strong>{
|
|
309
|
+
<span>{t(language, "brain.level")} {explorationDepth}</span>
|
|
310
|
+
<strong>{t(language, `brain.depth.${explorationDepth}`)}</strong>
|
|
305
311
|
</div>
|
|
306
312
|
|
|
307
313
|
<div className="brain-depth-actions" aria-label="Brain quick views">
|
|
308
|
-
<button type="button" className={explorationDepth === 2 ? "is-active" : ""} onClick={() => jumpToDepth(2)}
|
|
309
|
-
<button type="button" className={explorationDepth === 3 ? "is-active" : ""} onClick={() => jumpToDepth(3)}
|
|
310
|
-
<button type="button" className={explorationDepth === 4 ? "is-active" : ""} onClick={() => jumpToDepth(4)}
|
|
311
|
-
<button type="button" className={explorationDepth === 5 ? "is-active" : ""} onClick={() => jumpToDepth(5)}
|
|
314
|
+
<button type="button" className={explorationDepth === 2 ? "is-active" : ""} onClick={() => jumpToDepth(2)}>{t(language, "brain.view.memories")}</button>
|
|
315
|
+
<button type="button" className={explorationDepth === 3 ? "is-active" : ""} onClick={() => jumpToDepth(3)}>{t(language, "brain.view.topics")}</button>
|
|
316
|
+
<button type="button" className={explorationDepth === 4 ? "is-active" : ""} onClick={() => jumpToDepth(4)}>{t(language, "brain.view.relationships")}</button>
|
|
317
|
+
<button type="button" className={explorationDepth === 5 ? "is-active" : ""} onClick={() => jumpToDepth(5)}>{t(language, "brain.view.graph")}</button>
|
|
312
318
|
</div>
|
|
313
319
|
|
|
314
320
|
<div className="brain-field-layer" aria-hidden={explorationDepth < 2}>
|
|
@@ -328,7 +334,7 @@ function BrainHome({
|
|
|
328
334
|
|
|
329
335
|
{explorationDepth > 1 ? (
|
|
330
336
|
<button className="brain-surface-control" type="button" onClick={surface}>
|
|
331
|
-
|
|
337
|
+
{t(language, "brain.surface")}
|
|
332
338
|
</button>
|
|
333
339
|
) : null}
|
|
334
340
|
</div>
|
|
@@ -337,18 +343,19 @@ function BrainHome({
|
|
|
337
343
|
<section className="brain-conversation" aria-label="Conversation">
|
|
338
344
|
<div className="brain-conversation-header">
|
|
339
345
|
<div>
|
|
340
|
-
<h1>
|
|
341
|
-
<span>{
|
|
346
|
+
<h1>{t(language, "brain.title")}</h1>
|
|
347
|
+
<span>{t(language, `brain.depth.${explorationDepth}`)}</span>
|
|
342
348
|
</div>
|
|
349
|
+
<LanguageSwitcher compact />
|
|
343
350
|
<div className="brain-ownership-strip" aria-label="Brain ownership guarantees">
|
|
344
|
-
<span>
|
|
345
|
-
<span>
|
|
346
|
-
<span>
|
|
351
|
+
<span>{t(language, "brain.local")}</span>
|
|
352
|
+
<span>{t(language, "brain.portable")}</span>
|
|
353
|
+
<span>{t(language, "brain.private")}</span>
|
|
347
354
|
</div>
|
|
348
355
|
<div>{modelName}</div>
|
|
349
356
|
<button className="brain-admin-link" type="button" onClick={() => navigateHash("/admin")}>
|
|
350
357
|
<ShieldCheck className="h-3.5 w-3.5" />
|
|
351
|
-
|
|
358
|
+
{t(language, "brain.admin")}
|
|
352
359
|
</button>
|
|
353
360
|
</div>
|
|
354
361
|
|
|
@@ -360,13 +367,11 @@ function BrainHome({
|
|
|
360
367
|
/>
|
|
361
368
|
{messages.length === 0 ? (
|
|
362
369
|
<div className="mind-empty">
|
|
363
|
-
<div className="mind-empty-kicker"
|
|
364
|
-
<div className="mind-empty-title"
|
|
365
|
-
<p>
|
|
366
|
-
문서, 대화, 프로젝트, 결정이 Brain에 쌓이고 나중에 주제와 관계로 다시 보입니다.
|
|
367
|
-
</p>
|
|
370
|
+
<div className="mind-empty-kicker">{t(language, "brain.empty.kicker")}</div>
|
|
371
|
+
<div className="mind-empty-title">{t(language, "brain.empty.title")}</div>
|
|
372
|
+
<p>{t(language, "brain.empty.body")}</p>
|
|
368
373
|
<div className="mind-empty-prompts" aria-label="Starter prompts">
|
|
369
|
-
{
|
|
374
|
+
{starterPrompts.map((prompt) => (
|
|
370
375
|
<button key={prompt} type="button" onClick={() => setDraft(prompt)}>
|
|
371
376
|
{prompt}
|
|
372
377
|
</button>
|
|
@@ -384,7 +389,7 @@ function BrainHome({
|
|
|
384
389
|
|
|
385
390
|
{memoryFeedback ? <div className="brain-save-feedback" role="status">{memoryFeedback}</div> : null}
|
|
386
391
|
|
|
387
|
-
<BrainCarePanel />
|
|
392
|
+
<BrainCarePanel language={language} />
|
|
388
393
|
|
|
389
394
|
<div className="brain-composer">
|
|
390
395
|
<textarea
|
|
@@ -396,12 +401,12 @@ function BrainHome({
|
|
|
396
401
|
void send();
|
|
397
402
|
}
|
|
398
403
|
}}
|
|
399
|
-
placeholder=
|
|
404
|
+
placeholder={t(language, "brain.placeholder")}
|
|
400
405
|
/>
|
|
401
406
|
<div className="brain-composer-actions">
|
|
402
407
|
<label className="brain-image-input">
|
|
403
408
|
<ImagePlus className="h-3.5 w-3.5" />
|
|
404
|
-
<span>
|
|
409
|
+
<span>{t(language, "brain.image")}</span>
|
|
405
410
|
<input
|
|
406
411
|
type="file"
|
|
407
412
|
accept="image/*"
|
|
@@ -412,9 +417,9 @@ function BrainHome({
|
|
|
412
417
|
}}
|
|
413
418
|
/>
|
|
414
419
|
</label>
|
|
415
|
-
{imageData ? <span className="brain-quiet-success">
|
|
420
|
+
{imageData ? <span className="brain-quiet-success">{t(language, "brain.imageAttached")}</span> : null}
|
|
416
421
|
<Button onClick={() => void send()} disabled={!draft.trim() || streaming} className="rounded-full px-5">
|
|
417
|
-
<Send className="h-4 w-4" />
|
|
422
|
+
<Send className="h-4 w-4" /> {t(language, "brain.send")}
|
|
418
423
|
</Button>
|
|
419
424
|
</div>
|
|
420
425
|
</div>
|
|
@@ -423,8 +428,30 @@ function BrainHome({
|
|
|
423
428
|
);
|
|
424
429
|
}
|
|
425
430
|
|
|
431
|
+
function LanguageSwitcher({ compact = false }: { compact?: boolean }) {
|
|
432
|
+
const language = useAppStore((state) => state.language);
|
|
433
|
+
const setLanguage = useAppStore((state) => state.setLanguage);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<div className={compact ? "language-switcher compact" : "language-switcher"} aria-label={t(language, "language.label")}>
|
|
437
|
+
{(["ko", "en"] as Language[]).map((item) => (
|
|
438
|
+
<button
|
|
439
|
+
key={item}
|
|
440
|
+
type="button"
|
|
441
|
+
className={language === item ? "is-active" : ""}
|
|
442
|
+
onClick={() => setLanguage(item)}
|
|
443
|
+
aria-pressed={language === item}
|
|
444
|
+
>
|
|
445
|
+
{LANGUAGE_LABELS[item]}
|
|
446
|
+
</button>
|
|
447
|
+
))}
|
|
448
|
+
</div>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
426
452
|
function AdminConsole({ onBack }: { onBack: () => void }) {
|
|
427
453
|
const qc = useQueryClient();
|
|
454
|
+
const language = useAppStore((state) => state.language);
|
|
428
455
|
const [filters, setFilters] = React.useState<AdminFilterState>({ q: "", actor: "", action: "", severity: "", limit: 50 });
|
|
429
456
|
const { summaryQ, statsQ, usersQ, auditQ, securityQ, securityEventsQ, policiesQ, rolesQ, retentionQ, indexQ } = useAdminConsoleData(filters);
|
|
430
457
|
const rebuildIndex = useMutation({
|
|
@@ -444,13 +471,14 @@ function AdminConsole({ onBack }: { onBack: () => void }) {
|
|
|
444
471
|
<header className="admin-console-header">
|
|
445
472
|
<button className="admin-back-button" type="button" onClick={onBack}>
|
|
446
473
|
<ArrowLeft className="h-4 w-4" />
|
|
447
|
-
|
|
474
|
+
{t(language, "admin.back")}
|
|
448
475
|
</button>
|
|
449
476
|
<div>
|
|
450
|
-
<span>
|
|
451
|
-
<h1>
|
|
452
|
-
<p>
|
|
477
|
+
<span>{t(language, "admin.kicker")}</span>
|
|
478
|
+
<h1>{t(language, "admin.title")}</h1>
|
|
479
|
+
<p>{t(language, "admin.body")}</p>
|
|
453
480
|
</div>
|
|
481
|
+
<LanguageSwitcher compact />
|
|
454
482
|
</header>
|
|
455
483
|
|
|
456
484
|
<section className="admin-metrics" aria-label="Admin overview">
|
|
@@ -643,7 +671,7 @@ function AdminList({ items, empty, render }: { items: unknown[]; empty: string;
|
|
|
643
671
|
return <div className="admin-list">{items.map((item, index) => <div key={index} className="admin-list-row">{render(item)}</div>)}</div>;
|
|
644
672
|
}
|
|
645
673
|
|
|
646
|
-
function BrainCarePanel() {
|
|
674
|
+
function BrainCarePanel({ language }: { language: Language }) {
|
|
647
675
|
const qc = useQueryClient();
|
|
648
676
|
const [expanded, setExpanded] = React.useState(false);
|
|
649
677
|
const [archivePath, setArchivePath] = React.useState("");
|
|
@@ -678,7 +706,7 @@ function BrainCarePanel() {
|
|
|
678
706
|
const backupStatus = backupHealthLabel(backupHealthQ.data?.data);
|
|
679
707
|
|
|
680
708
|
return (
|
|
681
|
-
<section className={`brain-care-panel ${expanded ? "is-expanded" : "is-collapsed"}`} aria-label=
|
|
709
|
+
<section className={`brain-care-panel ${expanded ? "is-expanded" : "is-collapsed"}`} aria-label={t(language, "care.title")}>
|
|
682
710
|
<button
|
|
683
711
|
className="brain-care-summary"
|
|
684
712
|
type="button"
|
|
@@ -687,11 +715,11 @@ function BrainCarePanel() {
|
|
|
687
715
|
onClick={() => setExpanded((value) => !value)}
|
|
688
716
|
>
|
|
689
717
|
<span className="brain-care-summary-main">
|
|
690
|
-
<span><ShieldCheck className="h-3.5 w-3.5" />
|
|
691
|
-
<strong>
|
|
718
|
+
<span><ShieldCheck className="h-3.5 w-3.5" /> {t(language, "care.title")}</span>
|
|
719
|
+
<strong>{t(language, "care.subtitle")}</strong>
|
|
692
720
|
</span>
|
|
693
721
|
<div className="brain-care-proof" aria-label="Ownership model">
|
|
694
|
-
<span>
|
|
722
|
+
<span>{t(language, "care.private")}</span>
|
|
695
723
|
<span>{portableFormat}</span>
|
|
696
724
|
<span>{backupStatus}</span>
|
|
697
725
|
</div>
|
|
@@ -703,22 +731,25 @@ function BrainCarePanel() {
|
|
|
703
731
|
<div className="brain-care-actions">
|
|
704
732
|
<CareButton
|
|
705
733
|
icon={<Download className="h-3.5 w-3.5" />}
|
|
706
|
-
label="
|
|
707
|
-
detail=
|
|
734
|
+
label={t(language, "care.export")}
|
|
735
|
+
detail={t(language, "care.export.detail")}
|
|
736
|
+
pendingLabel={t(language, "care.working")}
|
|
708
737
|
pending={exportGraph.isPending}
|
|
709
738
|
onClick={() => exportGraph.mutate()}
|
|
710
739
|
/>
|
|
711
740
|
<CareButton
|
|
712
741
|
icon={<DatabaseBackup className="h-3.5 w-3.5" />}
|
|
713
|
-
label="
|
|
714
|
-
detail=
|
|
742
|
+
label={t(language, "care.backup")}
|
|
743
|
+
detail={t(language, "care.backup.detail")}
|
|
744
|
+
pendingLabel={t(language, "care.working")}
|
|
715
745
|
pending={backupGraph.isPending}
|
|
716
746
|
onClick={() => backupGraph.mutate()}
|
|
717
747
|
/>
|
|
718
748
|
<CareButton
|
|
719
749
|
icon={<Archive className="h-3.5 w-3.5" />}
|
|
720
|
-
label="
|
|
721
|
-
detail=
|
|
750
|
+
label={t(language, "care.archive")}
|
|
751
|
+
detail={t(language, "care.archive.detail")}
|
|
752
|
+
pendingLabel={t(language, "care.working")}
|
|
722
753
|
pending={archiveBrain.isPending}
|
|
723
754
|
disabled={!passphrase.trim()}
|
|
724
755
|
onClick={() => archiveBrain.mutate()}
|
|
@@ -729,15 +760,15 @@ function BrainCarePanel() {
|
|
|
729
760
|
<input
|
|
730
761
|
value={archivePath}
|
|
731
762
|
onChange={(event) => setArchivePath(event.target.value)}
|
|
732
|
-
placeholder="
|
|
733
|
-
aria-label="
|
|
763
|
+
placeholder={t(language, "care.path.placeholder")}
|
|
764
|
+
aria-label={t(language, "care.path.label")}
|
|
734
765
|
/>
|
|
735
766
|
<input
|
|
736
767
|
type="password"
|
|
737
768
|
value={passphrase}
|
|
738
769
|
onChange={(event) => setPassphrase(event.target.value)}
|
|
739
|
-
placeholder="
|
|
740
|
-
aria-label="
|
|
770
|
+
placeholder={t(language, "care.passphrase.placeholder")}
|
|
771
|
+
aria-label={t(language, "care.passphrase.label")}
|
|
741
772
|
/>
|
|
742
773
|
<div className="brain-care-archive-actions">
|
|
743
774
|
<Button
|
|
@@ -746,7 +777,7 @@ function BrainCarePanel() {
|
|
|
746
777
|
disabled={!archivePath.trim() || inspectArchive.isPending}
|
|
747
778
|
onClick={() => inspectArchive.mutate()}
|
|
748
779
|
>
|
|
749
|
-
<Eye className="h-3.5 w-3.5" />
|
|
780
|
+
<Eye className="h-3.5 w-3.5" /> {t(language, "care.inspect")}
|
|
750
781
|
</Button>
|
|
751
782
|
<Button
|
|
752
783
|
variant="outline"
|
|
@@ -754,7 +785,7 @@ function BrainCarePanel() {
|
|
|
754
785
|
disabled={!archivePath.trim() || !passphrase.trim() || restorePreview.isPending}
|
|
755
786
|
onClick={() => restorePreview.mutate()}
|
|
756
787
|
>
|
|
757
|
-
<RotateCcw className="h-3.5 w-3.5" />
|
|
788
|
+
<RotateCcw className="h-3.5 w-3.5" /> {t(language, "care.restorePreview")}
|
|
758
789
|
</Button>
|
|
759
790
|
</div>
|
|
760
791
|
</div>
|
|
@@ -765,7 +796,7 @@ function BrainCarePanel() {
|
|
|
765
796
|
</div>
|
|
766
797
|
) : (
|
|
767
798
|
<p className="brain-care-note">
|
|
768
|
-
|
|
799
|
+
{t(language, "care.note")}
|
|
769
800
|
</p>
|
|
770
801
|
)}
|
|
771
802
|
</div>
|
|
@@ -792,6 +823,7 @@ function CareButton({
|
|
|
792
823
|
icon,
|
|
793
824
|
label,
|
|
794
825
|
detail,
|
|
826
|
+
pendingLabel,
|
|
795
827
|
pending,
|
|
796
828
|
disabled,
|
|
797
829
|
onClick,
|
|
@@ -799,6 +831,7 @@ function CareButton({
|
|
|
799
831
|
icon: React.ReactNode;
|
|
800
832
|
label: string;
|
|
801
833
|
detail: string;
|
|
834
|
+
pendingLabel: string;
|
|
802
835
|
pending?: boolean;
|
|
803
836
|
disabled?: boolean;
|
|
804
837
|
onClick: () => void;
|
|
@@ -807,7 +840,7 @@ function CareButton({
|
|
|
807
840
|
<button className="brain-care-button" type="button" disabled={disabled || pending} onClick={onClick}>
|
|
808
841
|
{icon}
|
|
809
842
|
<span>
|
|
810
|
-
<strong>{pending ?
|
|
843
|
+
<strong>{pending ? pendingLabel : label}</strong>
|
|
811
844
|
<small>{detail}</small>
|
|
812
845
|
</span>
|
|
813
846
|
</button>
|
|
@@ -872,6 +905,7 @@ function BrainOverviewPanel({
|
|
|
872
905
|
concepts: KnowledgeConcept[];
|
|
873
906
|
onOpenDepth: (depth: BrainDepth) => void;
|
|
874
907
|
}) {
|
|
908
|
+
const language = useAppStore((state) => state.language);
|
|
875
909
|
const recent = memories.slice(0, 3);
|
|
876
910
|
const older = memories.slice(3, 6);
|
|
877
911
|
const topics = concepts.slice(0, 4);
|
|
@@ -880,27 +914,27 @@ function BrainOverviewPanel({
|
|
|
880
914
|
<section className="brain-overview-panel" aria-label="Brain overview">
|
|
881
915
|
<div className="brain-overview-head">
|
|
882
916
|
<div>
|
|
883
|
-
<span>
|
|
884
|
-
<strong
|
|
917
|
+
<span>{t(language, "brain.overview.kicker")}</span>
|
|
918
|
+
<strong>{t(language, "brain.overview.title")}</strong>
|
|
885
919
|
</div>
|
|
886
|
-
<button type="button" onClick={() => onOpenDepth(5)}
|
|
920
|
+
<button type="button" onClick={() => onOpenDepth(5)}>{t(language, "brain.overview.graph")}</button>
|
|
887
921
|
</div>
|
|
888
922
|
<div className="brain-overview-grid">
|
|
889
923
|
<BrainOverviewColumn
|
|
890
|
-
title=
|
|
891
|
-
empty=
|
|
924
|
+
title={t(language, "brain.overview.recent")}
|
|
925
|
+
empty={t(language, "brain.overview.recentEmpty")}
|
|
892
926
|
items={recent.map((memory) => memory.title)}
|
|
893
927
|
onOpen={() => onOpenDepth(2)}
|
|
894
928
|
/>
|
|
895
929
|
<BrainOverviewColumn
|
|
896
|
-
title=
|
|
897
|
-
empty=
|
|
930
|
+
title={t(language, "brain.overview.older")}
|
|
931
|
+
empty={t(language, "brain.overview.olderEmpty")}
|
|
898
932
|
items={older.map((memory) => memory.title)}
|
|
899
933
|
onOpen={() => onOpenDepth(2)}
|
|
900
934
|
/>
|
|
901
935
|
<BrainOverviewColumn
|
|
902
|
-
title=
|
|
903
|
-
empty=
|
|
936
|
+
title={t(language, "brain.overview.topics")}
|
|
937
|
+
empty={t(language, "brain.overview.topicsEmpty")}
|
|
904
938
|
items={topics.map((concept) => concept.label)}
|
|
905
939
|
onOpen={() => onOpenDepth(3)}
|
|
906
940
|
/>
|
|
@@ -1044,6 +1078,7 @@ function EmergentKnowledgeGraph({
|
|
|
1044
1078
|
onSearch: (value: string) => void;
|
|
1045
1079
|
onSelect: (id: string | null) => void;
|
|
1046
1080
|
}) {
|
|
1081
|
+
const language = useAppStore((state) => state.language);
|
|
1047
1082
|
const query = search.trim().toLowerCase();
|
|
1048
1083
|
const visibleNodes = React.useMemo(() => {
|
|
1049
1084
|
const filtered = model.nodes.filter((node) => {
|
|
@@ -1111,7 +1146,7 @@ function EmergentKnowledgeGraph({
|
|
|
1111
1146
|
))}
|
|
1112
1147
|
</div>
|
|
1113
1148
|
) : (
|
|
1114
|
-
<div className="brain-graph-empty">
|
|
1149
|
+
<div className="brain-graph-empty">{t(language, "brain.graph.empty")}</div>
|
|
1115
1150
|
)}
|
|
1116
1151
|
|
|
1117
1152
|
<div className="brain-graph-focus">
|
|
@@ -1119,11 +1154,11 @@ function EmergentKnowledgeGraph({
|
|
|
1119
1154
|
<>
|
|
1120
1155
|
<span>{selected.type}</span>
|
|
1121
1156
|
<strong>{selected.label}</strong>
|
|
1122
|
-
<p>{selected.summary || "
|
|
1123
|
-
<p
|
|
1157
|
+
<p>{selected.summary || t(language, "brain.graph.summaryFallback")}</p>
|
|
1158
|
+
<p>{t(language, "brain.graph.focused")}</p>
|
|
1124
1159
|
</>
|
|
1125
1160
|
) : (
|
|
1126
|
-
<p
|
|
1161
|
+
<p>{t(language, "brain.graph.emptyFocus")}</p>
|
|
1127
1162
|
)}
|
|
1128
1163
|
</div>
|
|
1129
1164
|
</section>
|