ltcai 6.4.0 → 6.6.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 +31 -28
- package/docs/CHANGELOG.md +62 -0
- package/frontend/openapi.json +65 -1
- package/frontend/src/api/client.ts +2 -0
- package/frontend/src/api/openapi.ts +86 -0
- package/frontend/src/features/brain/BrainComposer.tsx +20 -1
- package/frontend/src/features/brain/BrainConversation.tsx +48 -4
- package/frontend/src/features/brain/BrainHome.tsx +61 -1
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +8 -1
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +76 -1
- package/frontend/src/features/brain/brainData.ts +125 -1
- package/frontend/src/features/brain/types.ts +52 -0
- package/frontend/src/i18n.ts +66 -0
- package/frontend/src/styles.css +261 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/ingest.py +41 -6
- package/lattice_brain/ingestion.py +1 -0
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/knowledge_graph.py +11 -0
- package/latticeai/api/memory.py +20 -0
- package/latticeai/app_factory.py +2 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/chat_wiring.py +2 -0
- package/latticeai/runtime/router_registration.py +3 -0
- package/latticeai/services/memory_service.py +188 -2
- package/latticeai/services/router_context.py +1 -0
- package/latticeai/services/upload_service.py +12 -0
- 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-Div5vMlq.css → index-C8toxDpv.css} +1 -1
- package/static/app/assets/index-CbvcAQ6B.js +16 -0
- package/static/app/assets/index-CbvcAQ6B.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-t1jx1BR9.js +0 -16
- package/static/app/assets/index-t1jx1BR9.js.map +0 -1
|
@@ -5,7 +5,7 @@ import { type BrainState, LivingBrain, triggerBrainRecall } from "@/components/L
|
|
|
5
5
|
import { useAppStore } from "@/store/appStore";
|
|
6
6
|
import { t } from "@/i18n";
|
|
7
7
|
import { BrainConversation } from "./BrainConversation";
|
|
8
|
-
import { buildMemoryFragments, currentModelName, parseKnowledgeGraph } from "./brainData";
|
|
8
|
+
import { buildBrainProof, buildBrainReadiness, buildMemoryFragments, currentModelName, parseKnowledgeGraph } from "./brainData";
|
|
9
9
|
import { DepthEmergence } from "./DepthEmergence";
|
|
10
10
|
import { DEPTHS, type BrainDepth, type MemoryFragment, type Message } from "./types";
|
|
11
11
|
|
|
@@ -29,6 +29,8 @@ export function BrainHome({
|
|
|
29
29
|
const [graphSearch, setGraphSearch] = React.useState("");
|
|
30
30
|
const [selectedGraphId, setSelectedGraphId] = React.useState<string | null>(null);
|
|
31
31
|
const [memoryFeedback, setMemoryFeedback] = React.useState<string | null>(null);
|
|
32
|
+
const [uploadingDocument, setUploadingDocument] = React.useState(false);
|
|
33
|
+
const [lastRecallQuery, setLastRecallQuery] = React.useState("");
|
|
32
34
|
const streamRef = React.useRef<HTMLDivElement>(null);
|
|
33
35
|
const recallTimerRef = React.useRef<number | null>(null);
|
|
34
36
|
|
|
@@ -36,6 +38,10 @@ export function BrainHome({
|
|
|
36
38
|
const historyQ = useQuery({ queryKey: ["chatHistory"], queryFn: latticeApi.chatHistory });
|
|
37
39
|
const graphQ = useQuery({ queryKey: ["graph"], queryFn: latticeApi.graph });
|
|
38
40
|
const modelsQ = useQuery({ queryKey: ["models"], queryFn: latticeApi.models });
|
|
41
|
+
const brainProofQ = useQuery({
|
|
42
|
+
queryKey: ["memoryBrainProof", lastRecallQuery],
|
|
43
|
+
queryFn: () => latticeApi.memoryBrainProof(lastRecallQuery, 3),
|
|
44
|
+
});
|
|
39
45
|
|
|
40
46
|
const memoryFragments = React.useMemo(
|
|
41
47
|
() => buildMemoryFragments(memoriesQ.data?.data, historyQ.data?.data),
|
|
@@ -46,11 +52,19 @@ export function BrainHome({
|
|
|
46
52
|
() => graphModel.nodes.slice(0, 10),
|
|
47
53
|
[graphModel.nodes],
|
|
48
54
|
);
|
|
55
|
+
const brainReadiness = React.useMemo(
|
|
56
|
+
() => buildBrainReadiness(memoriesQ.data?.data, memoryFragments.length, knowledgeConcepts.length),
|
|
57
|
+
[knowledgeConcepts.length, memoriesQ.data, memoryFragments.length],
|
|
58
|
+
);
|
|
49
59
|
const relationshipThreads = React.useMemo(
|
|
50
60
|
() => graphModel.edges.slice(0, 10),
|
|
51
61
|
[graphModel.edges],
|
|
52
62
|
);
|
|
53
63
|
const modelName = React.useMemo(() => currentModelName(modelsQ.data?.data), [modelsQ.data]);
|
|
64
|
+
const brainProof = React.useMemo(
|
|
65
|
+
() => buildBrainProof(brainProofQ.data?.data, modelName),
|
|
66
|
+
[brainProofQ.data, modelName],
|
|
67
|
+
);
|
|
54
68
|
const currentDepth = DEPTHS[explorationDepth - 1];
|
|
55
69
|
const starterPrompts = React.useMemo(
|
|
56
70
|
() => [
|
|
@@ -119,12 +133,39 @@ export function BrainHome({
|
|
|
119
133
|
});
|
|
120
134
|
} else {
|
|
121
135
|
setMemoryFeedback(t(language, "brain.saved", { topics: knowledgeConcepts.length, memories: memoryFragments.length }));
|
|
136
|
+
setLastRecallQuery(text);
|
|
122
137
|
}
|
|
123
138
|
} finally {
|
|
124
139
|
setStreaming(false);
|
|
125
140
|
void qc.invalidateQueries({ queryKey: ["chatHistory"] });
|
|
126
141
|
void qc.invalidateQueries({ queryKey: ["memoryManager"] });
|
|
127
142
|
void qc.invalidateQueries({ queryKey: ["graph"] });
|
|
143
|
+
void qc.invalidateQueries({ queryKey: ["memoryBrainProof"] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function uploadDocument(file: File) {
|
|
148
|
+
if (uploadingDocument) return;
|
|
149
|
+
|
|
150
|
+
setUploadingDocument(true);
|
|
151
|
+
setMemoryFeedback(t(language, "brain.upload.pending", { name: file.name }));
|
|
152
|
+
onBrainChange("recalling", 0.86);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const result = await latticeApi.uploadDocument(file);
|
|
156
|
+
if (result.error) {
|
|
157
|
+
setMemoryFeedback(t(language, "brain.upload.failed", { reason: result.error }));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setMemoryFeedback(t(language, "brain.upload.saved", { name: file.name }));
|
|
162
|
+
setLastRecallQuery(file.name);
|
|
163
|
+
triggerBrainRecall();
|
|
164
|
+
void qc.invalidateQueries({ queryKey: ["memoryManager"] });
|
|
165
|
+
void qc.invalidateQueries({ queryKey: ["graph"] });
|
|
166
|
+
void qc.invalidateQueries({ queryKey: ["memoryBrainProof"] });
|
|
167
|
+
} finally {
|
|
168
|
+
setUploadingDocument(false);
|
|
128
169
|
}
|
|
129
170
|
}
|
|
130
171
|
|
|
@@ -186,6 +227,21 @@ export function BrainHome({
|
|
|
186
227
|
<button type="button" className={explorationDepth === 5 ? "is-active" : ""} onClick={() => jumpToDepth(5)}>{t(language, "brain.view.graph")}</button>
|
|
187
228
|
</div>
|
|
188
229
|
|
|
230
|
+
<div className="brain-depth-rail" aria-label={t(language, "brain.depthRail.aria")}>
|
|
231
|
+
{DEPTHS.map((depth) => (
|
|
232
|
+
<button
|
|
233
|
+
key={depth.level}
|
|
234
|
+
type="button"
|
|
235
|
+
className={depth.level <= explorationDepth ? "is-revealed" : ""}
|
|
236
|
+
aria-current={depth.level === explorationDepth ? "step" : undefined}
|
|
237
|
+
onClick={() => jumpToDepth(depth.level)}
|
|
238
|
+
>
|
|
239
|
+
<span>{depth.level}</span>
|
|
240
|
+
<strong>{t(language, `brain.depth.${depth.level}`)}</strong>
|
|
241
|
+
</button>
|
|
242
|
+
))}
|
|
243
|
+
</div>
|
|
244
|
+
|
|
189
245
|
<div className="brain-field-layer" aria-hidden={explorationDepth < 2}>
|
|
190
246
|
<DepthEmergence
|
|
191
247
|
depth={explorationDepth}
|
|
@@ -222,9 +278,13 @@ export function BrainHome({
|
|
|
222
278
|
streamRef={streamRef}
|
|
223
279
|
memories={memoryFragments}
|
|
224
280
|
concepts={knowledgeConcepts}
|
|
281
|
+
readiness={brainReadiness}
|
|
282
|
+
proof={brainProof}
|
|
283
|
+
uploadingDocument={uploadingDocument}
|
|
225
284
|
onOpenDepth={jumpToDepth}
|
|
226
285
|
onDraftChange={setDraft}
|
|
227
286
|
onImageDataChange={setImageData}
|
|
287
|
+
onUploadDocument={(file) => void uploadDocument(file)}
|
|
228
288
|
onSend={() => void send()}
|
|
229
289
|
/>
|
|
230
290
|
</main>
|
|
@@ -14,7 +14,14 @@ export function BrainMemoryLayer({
|
|
|
14
14
|
}) {
|
|
15
15
|
const language = useAppStore((state) => state.language);
|
|
16
16
|
const visible = memories.slice(0, depth >= 3 ? 8 : 6);
|
|
17
|
-
if (!visible.length)
|
|
17
|
+
if (!visible.length) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="memory-fragment is-empty">
|
|
20
|
+
<span>{t(language, "brain.memory.empty.kicker")}</span>
|
|
21
|
+
<strong>{t(language, "brain.memory.empty")}</strong>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
return (
|
|
20
27
|
<>
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { BrainCircuit, DatabaseZap, Repeat2 } from "lucide-react";
|
|
2
3
|
import { t } from "@/i18n";
|
|
3
4
|
import { useAppStore } from "@/store/appStore";
|
|
4
|
-
import type { BrainDepth, KnowledgeConcept, MemoryFragment } from "./types";
|
|
5
|
+
import type { BrainDepth, BrainProof, BrainReadiness, KnowledgeConcept, MemoryFragment } from "./types";
|
|
5
6
|
|
|
6
7
|
export function BrainOverviewPanel({
|
|
7
8
|
memories,
|
|
8
9
|
concepts,
|
|
10
|
+
readiness,
|
|
11
|
+
proof,
|
|
9
12
|
onOpenDepth,
|
|
10
13
|
}: {
|
|
11
14
|
memories: MemoryFragment[];
|
|
12
15
|
concepts: KnowledgeConcept[];
|
|
16
|
+
readiness: BrainReadiness;
|
|
17
|
+
proof: BrainProof;
|
|
13
18
|
onOpenDepth: (depth: BrainDepth) => void;
|
|
14
19
|
}) {
|
|
15
20
|
const language = useAppStore((state) => state.language);
|
|
16
21
|
const recent = memories.slice(0, 3);
|
|
17
22
|
const older = memories.slice(3, 6);
|
|
18
23
|
const topics = concepts.slice(0, 4);
|
|
24
|
+
const hasDurableProof = proof.proofs.hasDurableEvidence || proof.modelContinuity.proven;
|
|
25
|
+
const hasRecallProof = proof.recall.items.length > 0;
|
|
26
|
+
const modelProofValue = proof.modelContinuity.proven && proof.claims.keepsContextAcrossModels
|
|
27
|
+
? proof.modelContinuity.activeModel || t(language, "brain.proof.noModel")
|
|
28
|
+
: t(language, "brain.proof.modelPending");
|
|
19
29
|
|
|
20
30
|
return (
|
|
21
31
|
<section className="brain-overview-panel" aria-label={t(language, "brain.aria.overview")}>
|
|
@@ -46,10 +56,75 @@ export function BrainOverviewPanel({
|
|
|
46
56
|
onOpen={() => onOpenDepth(3)}
|
|
47
57
|
/>
|
|
48
58
|
</div>
|
|
59
|
+
<div className="brain-readiness-strip" data-state={readiness.state}>
|
|
60
|
+
<div>
|
|
61
|
+
<span>{t(language, "brain.readiness.kicker")}</span>
|
|
62
|
+
<strong>{t(language, readiness.titleKey)}</strong>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="brain-readiness-meter" aria-label={t(language, "brain.readiness.aria")}>
|
|
65
|
+
<i style={{ width: `${readiness.score}%` }} />
|
|
66
|
+
</div>
|
|
67
|
+
<button type="button" onClick={() => onOpenDepth(readiness.depth)}>
|
|
68
|
+
{t(language, readiness.actionKey)}
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="brain-proof-strip" aria-label={t(language, "brain.proof.aria")}>
|
|
72
|
+
<BrainProofPoint
|
|
73
|
+
icon={<DatabaseZap className="h-4 w-4" />}
|
|
74
|
+
label={t(language, "brain.proof.context")}
|
|
75
|
+
value={hasDurableProof
|
|
76
|
+
? t(language, "brain.proof.contextValue", { count: proof.proofs.durableItems })
|
|
77
|
+
: t(language, "brain.proof.contextEmpty")}
|
|
78
|
+
/>
|
|
79
|
+
<BrainProofPoint
|
|
80
|
+
icon={<Repeat2 className="h-4 w-4" />}
|
|
81
|
+
label={t(language, "brain.proof.model")}
|
|
82
|
+
value={modelProofValue}
|
|
83
|
+
/>
|
|
84
|
+
<BrainProofPoint
|
|
85
|
+
icon={<BrainCircuit className="h-4 w-4" />}
|
|
86
|
+
label={t(language, "brain.proof.store")}
|
|
87
|
+
value={t(language, "brain.proof.storeValue", {
|
|
88
|
+
topics: proof.proofs.graphConcepts,
|
|
89
|
+
vectors: proof.proofs.vectorItems,
|
|
90
|
+
})}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
{hasRecallProof ? (
|
|
94
|
+
<button type="button" className="brain-recall-proof" onClick={() => onOpenDepth(2)}>
|
|
95
|
+
<span>{t(language, "brain.proof.recall")}</span>
|
|
96
|
+
<strong>{proof.recall.items[0].title}</strong>
|
|
97
|
+
<small>{proof.recall.items[0].snippet || proof.recall.query}</small>
|
|
98
|
+
</button>
|
|
99
|
+
) : (
|
|
100
|
+
<div className="brain-recall-proof is-empty" role="status">
|
|
101
|
+
<span>{t(language, "brain.proof.recallEmpty.kicker")}</span>
|
|
102
|
+
<strong>{t(language, hasDurableProof ? "brain.proof.recallPending" : "brain.proof.recallEmpty.title")}</strong>
|
|
103
|
+
<small>{t(language, hasDurableProof ? "brain.proof.recallPending.detail" : "brain.proof.recallEmpty.detail")}</small>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
49
106
|
</section>
|
|
50
107
|
);
|
|
51
108
|
}
|
|
52
109
|
|
|
110
|
+
function BrainProofPoint({
|
|
111
|
+
icon,
|
|
112
|
+
label,
|
|
113
|
+
value,
|
|
114
|
+
}: {
|
|
115
|
+
icon: React.ReactNode;
|
|
116
|
+
label: string;
|
|
117
|
+
value: string;
|
|
118
|
+
}) {
|
|
119
|
+
return (
|
|
120
|
+
<div className="brain-proof-point">
|
|
121
|
+
{icon}
|
|
122
|
+
<span>{label}</span>
|
|
123
|
+
<strong>{value}</strong>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
53
128
|
function BrainOverviewColumn({
|
|
54
129
|
title,
|
|
55
130
|
empty,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asArray } from "@/lib/utils";
|
|
2
|
-
import type { ApiRecord, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
|
|
2
|
+
import type { ApiRecord, BrainDepth, BrainProof, BrainReadiness, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
|
|
3
3
|
import { clamp } from "./graphLayout";
|
|
4
4
|
|
|
5
5
|
export function buildMemoryFragments(memoryData: unknown, historyData: unknown): MemoryFragment[] {
|
|
@@ -60,6 +60,77 @@ export function currentModelName(data: unknown) {
|
|
|
60
60
|
return firstLoaded ? textValue(firstLoaded, ["name", "id", "model_id"], "local mind") : "local mind";
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export function buildBrainReadiness(memoryData: unknown, fallbackMemoryCount: number, fallbackConceptCount: number): BrainReadiness {
|
|
64
|
+
const memory = isRecord(memoryData) ? memoryData : {};
|
|
65
|
+
const backend = isRecord(memory.brain_readiness) ? memory.brain_readiness : {};
|
|
66
|
+
const backendState = textValue(backend, ["state"]);
|
|
67
|
+
const backendDepth = numberValue(backend, ["depth"]);
|
|
68
|
+
const backendScore = numberValue(backend, ["score"]);
|
|
69
|
+
if ((backendState === "quiet" || backendState === "forming" || backendState === "alive") && isBrainDepth(backendDepth)) {
|
|
70
|
+
const signals = isRecord(backend.signals) ? backend.signals : {};
|
|
71
|
+
return {
|
|
72
|
+
score: clamp(Math.round(backendScore || 0), 0, 100),
|
|
73
|
+
state: backendState,
|
|
74
|
+
depth: backendDepth,
|
|
75
|
+
titleKey: textValue(backend, ["title_key", "titleKey"], `brain.readiness.${backendState}`),
|
|
76
|
+
actionKey: textValue(backend, ["action_key", "actionKey"], readinessActionKey(backendState)),
|
|
77
|
+
source: "memory_service",
|
|
78
|
+
signals: {
|
|
79
|
+
memoryCount: numberValue(signals, ["memory_count", "memoryCount"]),
|
|
80
|
+
conceptCount: numberValue(signals, ["concept_count", "conceptCount"]),
|
|
81
|
+
relationshipCount: numberValue(signals, ["relationship_count", "relationshipCount"]),
|
|
82
|
+
healthySources: numberValue(signals, ["healthy_sources", "healthySources"]),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return fallbackBrainReadiness(fallbackMemoryCount, fallbackConceptCount);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildBrainProof(data: unknown, fallbackModelName = ""): BrainProof {
|
|
90
|
+
const proof = isRecord(data) ? data : {};
|
|
91
|
+
const modelContinuity = isRecord(proof.model_continuity) ? proof.model_continuity : {};
|
|
92
|
+
const proofs = isRecord(proof.proofs) ? proof.proofs : {};
|
|
93
|
+
const recall = isRecord(proof.recall) ? proof.recall : {};
|
|
94
|
+
const claims = isRecord(proof.claims) ? proof.claims : {};
|
|
95
|
+
const durableItems = numberValue(proofs, ["durable_items", "durableItems"]);
|
|
96
|
+
return {
|
|
97
|
+
status: textValue(proof, ["status"], "quiet"),
|
|
98
|
+
modelContinuity: {
|
|
99
|
+
activeModel: textValue(modelContinuity, ["active_model", "activeModel"], fallbackModelName),
|
|
100
|
+
brainOwner: textValue(modelContinuity, ["brain_owner", "brainOwner"], "lattice_brain"),
|
|
101
|
+
capability: booleanValue(modelContinuity, ["capability"], true),
|
|
102
|
+
survivesModelSwitch: booleanValue(modelContinuity, ["survives_model_switch", "survivesModelSwitch"], false),
|
|
103
|
+
proven: booleanValue(modelContinuity, ["proven"], false),
|
|
104
|
+
contextStore: textValue(modelContinuity, ["context_store", "contextStore"], "workspace + conversation + graph + vector"),
|
|
105
|
+
},
|
|
106
|
+
proofs: {
|
|
107
|
+
durableItems,
|
|
108
|
+
hasDurableEvidence: booleanValue(proofs, ["has_durable_evidence", "hasDurableEvidence"], durableItems > 0),
|
|
109
|
+
workspaceMemories: numberValue(proofs, ["workspace_memories", "workspaceMemories"]),
|
|
110
|
+
conversations: numberValue(proofs, ["conversations"]),
|
|
111
|
+
graphConcepts: numberValue(proofs, ["graph_concepts", "graphConcepts"]),
|
|
112
|
+
vectorItems: numberValue(proofs, ["vector_items", "vectorItems"]),
|
|
113
|
+
healthySources: numberValue(proofs, ["healthy_sources", "healthySources"]),
|
|
114
|
+
},
|
|
115
|
+
recall: {
|
|
116
|
+
query: textValue(recall, ["query"]),
|
|
117
|
+
count: numberValue(recall, ["count"]),
|
|
118
|
+
items: asArray<ApiRecord>(recall.items).map((item, index) => ({
|
|
119
|
+
id: textValue(item, ["id"], `recall-${index}`),
|
|
120
|
+
source: titleValue(item, ["source"], "Memory"),
|
|
121
|
+
title: textValue(item, ["title"], "Memory"),
|
|
122
|
+
snippet: textValue(item, ["snippet"]),
|
|
123
|
+
score: numberValue(item, ["score"]),
|
|
124
|
+
})),
|
|
125
|
+
},
|
|
126
|
+
claims: {
|
|
127
|
+
canRecallUserContext: booleanValue(claims, ["can_recall_user_context", "canRecallUserContext"], false),
|
|
128
|
+
keepsContextAcrossModels: booleanValue(claims, ["keeps_context_across_models", "keepsContextAcrossModels"], false),
|
|
129
|
+
isKnowledgeStore: booleanValue(claims, ["is_knowledge_store", "isKnowledgeStore"], false),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
63
134
|
function uniqueById<T extends { id: string }>(items: T[]) {
|
|
64
135
|
const seen = new Set<string>();
|
|
65
136
|
return items.filter((item) => {
|
|
@@ -69,6 +140,51 @@ function uniqueById<T extends { id: string }>(items: T[]) {
|
|
|
69
140
|
});
|
|
70
141
|
}
|
|
71
142
|
|
|
143
|
+
function fallbackBrainReadiness(memoryCount: number, conceptCount: number): BrainReadiness {
|
|
144
|
+
const score = Math.min(100, Math.round(memoryCount * 12 + conceptCount * 10));
|
|
145
|
+
if (memoryCount < 1) {
|
|
146
|
+
return {
|
|
147
|
+
score: Math.max(12, score),
|
|
148
|
+
state: "quiet",
|
|
149
|
+
depth: 2,
|
|
150
|
+
titleKey: "brain.readiness.quiet",
|
|
151
|
+
actionKey: "brain.readiness.start",
|
|
152
|
+
source: "frontend_fallback",
|
|
153
|
+
signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (conceptCount < 3) {
|
|
157
|
+
return {
|
|
158
|
+
score: Math.max(38, score),
|
|
159
|
+
state: "forming",
|
|
160
|
+
depth: 3,
|
|
161
|
+
titleKey: "brain.readiness.forming",
|
|
162
|
+
actionKey: "brain.readiness.grow",
|
|
163
|
+
source: "frontend_fallback",
|
|
164
|
+
signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
score: Math.max(72, score),
|
|
169
|
+
state: "alive",
|
|
170
|
+
depth: 5,
|
|
171
|
+
titleKey: "brain.readiness.alive",
|
|
172
|
+
actionKey: "brain.readiness.map",
|
|
173
|
+
source: "frontend_fallback",
|
|
174
|
+
signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function readinessActionKey(state: "quiet" | "forming" | "alive") {
|
|
179
|
+
if (state === "quiet") return "brain.readiness.start";
|
|
180
|
+
if (state === "forming") return "brain.readiness.grow";
|
|
181
|
+
return "brain.readiness.map";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isBrainDepth(value: number): value is BrainDepth {
|
|
185
|
+
return value >= 1 && value <= 5 && Number.isInteger(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
72
188
|
export function isRecord(value: unknown): value is ApiRecord {
|
|
73
189
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
74
190
|
}
|
|
@@ -96,3 +212,11 @@ function numberValue(record: ApiRecord, keys: string[]) {
|
|
|
96
212
|
}
|
|
97
213
|
return 0;
|
|
98
214
|
}
|
|
215
|
+
|
|
216
|
+
function booleanValue(record: ApiRecord, keys: string[], fallback: boolean) {
|
|
217
|
+
for (const key of keys) {
|
|
218
|
+
const value = record[key];
|
|
219
|
+
if (typeof value === "boolean") return value;
|
|
220
|
+
}
|
|
221
|
+
return fallback;
|
|
222
|
+
}
|
|
@@ -35,6 +35,58 @@ export type KnowledgeGraphModel = {
|
|
|
35
35
|
edges: RelationshipThread[];
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
export type BrainReadiness = {
|
|
39
|
+
score: number;
|
|
40
|
+
state: "quiet" | "forming" | "alive";
|
|
41
|
+
depth: BrainDepth;
|
|
42
|
+
titleKey: string;
|
|
43
|
+
actionKey: string;
|
|
44
|
+
source: "memory_service" | "frontend_fallback";
|
|
45
|
+
signals: {
|
|
46
|
+
memoryCount: number;
|
|
47
|
+
conceptCount: number;
|
|
48
|
+
relationshipCount: number;
|
|
49
|
+
healthySources: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type BrainProof = {
|
|
54
|
+
status: "quiet" | "forming" | "alive" | string;
|
|
55
|
+
modelContinuity: {
|
|
56
|
+
activeModel: string;
|
|
57
|
+
brainOwner: string;
|
|
58
|
+
capability: boolean;
|
|
59
|
+
survivesModelSwitch: boolean;
|
|
60
|
+
proven: boolean;
|
|
61
|
+
contextStore: string;
|
|
62
|
+
};
|
|
63
|
+
proofs: {
|
|
64
|
+
durableItems: number;
|
|
65
|
+
hasDurableEvidence: boolean;
|
|
66
|
+
workspaceMemories: number;
|
|
67
|
+
conversations: number;
|
|
68
|
+
graphConcepts: number;
|
|
69
|
+
vectorItems: number;
|
|
70
|
+
healthySources: number;
|
|
71
|
+
};
|
|
72
|
+
recall: {
|
|
73
|
+
query: string;
|
|
74
|
+
count: number;
|
|
75
|
+
items: Array<{
|
|
76
|
+
id: string;
|
|
77
|
+
source: string;
|
|
78
|
+
title: string;
|
|
79
|
+
snippet: string;
|
|
80
|
+
score: number;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
claims: {
|
|
84
|
+
canRecallUserContext: boolean;
|
|
85
|
+
keepsContextAcrossModels: boolean;
|
|
86
|
+
isKnowledgeStore: boolean;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
38
90
|
export const DEPTHS: Array<{ level: BrainDepth; labelKey: string; state: BrainState }> = [
|
|
39
91
|
{ level: 1, labelKey: "brain.depthLabel.1", state: "idle" },
|
|
40
92
|
{ level: 2, labelKey: "brain.depthLabel.2", state: "recalling" },
|
package/frontend/src/i18n.ts
CHANGED
|
@@ -31,6 +31,7 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
31
31
|
"brain.aria.home": "Lattice Brain",
|
|
32
32
|
"brain.aria.exploration": "Brain 탐색",
|
|
33
33
|
"brain.aria.quickViews": "Brain 빠른 보기",
|
|
34
|
+
"brain.depthRail.aria": "Brain 깊이 진행 상태",
|
|
34
35
|
"brain.aria.conversation": "대화",
|
|
35
36
|
"brain.aria.ownership": "Brain 소유 보장",
|
|
36
37
|
"brain.aria.starterPrompts": "시작 프롬프트",
|
|
@@ -52,11 +53,19 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
52
53
|
"brain.prompt.know": "이 문서를 내 Brain에 넣고 요약해줘: ",
|
|
53
54
|
"brain.prompt.plan": "지난 결정들을 나중에 찾을 수 있게 정리해줘: ",
|
|
54
55
|
"brain.placeholder": "Brain에게 말하기...",
|
|
56
|
+
"brain.upload.cta": "파일/문서 넣기",
|
|
57
|
+
"brain.upload.ctaShort": "문서",
|
|
58
|
+
"brain.upload.uploading": "넣는 중",
|
|
59
|
+
"brain.upload.hint": "업로드하면 실제 기억과 그래프로 다시 보입니다.",
|
|
60
|
+
"brain.upload.pending": "{name}을 Brain에 넣는 중입니다.",
|
|
61
|
+
"brain.upload.saved": "{name}이 Brain에 저장됐습니다.",
|
|
62
|
+
"brain.upload.failed": "문서를 넣지 못했습니다: {reason}",
|
|
55
63
|
"brain.image": "이미지",
|
|
56
64
|
"brain.unavailable": "지금은 답할 수 없음",
|
|
57
65
|
"brain.imageAttached": "이미지 첨부됨",
|
|
58
66
|
"brain.send": "보내기",
|
|
59
67
|
"brain.saved": "기억에 저장됨 · 연결된 주제 {topics}개 · 관련 기억 {memories}개",
|
|
68
|
+
"brain.saved.detail": "출처와 함께 나중에 다시 불러올 수 있습니다.",
|
|
60
69
|
"brain.recalled": "기억을 다시 꺼냈습니다: {title}",
|
|
61
70
|
"brain.overview.kicker": "Brain 한눈에 보기",
|
|
62
71
|
"brain.overview.title": "기억과 주제를 바로 확인하세요.",
|
|
@@ -67,6 +76,30 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
67
76
|
"brain.overview.olderEmpty": "대화가 쌓이면 과거 기억이 보입니다.",
|
|
68
77
|
"brain.overview.topics": "주요 주제",
|
|
69
78
|
"brain.overview.topicsEmpty": "주제가 형성되는 중입니다.",
|
|
79
|
+
"brain.readiness.kicker": "Brain 준비도",
|
|
80
|
+
"brain.readiness.aria": "Brain 준비도 점수",
|
|
81
|
+
"brain.readiness.quiet": "첫 기억을 기다리고 있습니다.",
|
|
82
|
+
"brain.readiness.forming": "기억이 주제로 자라는 중입니다.",
|
|
83
|
+
"brain.readiness.alive": "기억과 관계가 연결되어 있습니다.",
|
|
84
|
+
"brain.readiness.start": "첫 기억 보기",
|
|
85
|
+
"brain.readiness.grow": "주제 키우기",
|
|
86
|
+
"brain.readiness.map": "지도 보기",
|
|
87
|
+
"brain.proof.aria": "Brain 증거",
|
|
88
|
+
"brain.proof.context": "다시 불러올 맥락",
|
|
89
|
+
"brain.proof.contextValue": "{count}개 저장됨",
|
|
90
|
+
"brain.proof.contextEmpty": "첫 기억 대기",
|
|
91
|
+
"brain.proof.model": "모델 교체 후에도 유지",
|
|
92
|
+
"brain.proof.noModel": "모델 미로드",
|
|
93
|
+
"brain.proof.modelPending": "저장 후 증명됨",
|
|
94
|
+
"brain.proof.store": "지식 저장소",
|
|
95
|
+
"brain.proof.storeValue": "주제 {topics}개 · 벡터 {vectors}개",
|
|
96
|
+
"brain.proof.recall": "방금 Brain이 다시 꺼낸 기억",
|
|
97
|
+
"brain.proof.recallEmpty.kicker": "Brain 증거 자리",
|
|
98
|
+
"brain.proof.recallEmpty.title": "첫 대화 후 여기에 다시 꺼낸 기억이 표시됩니다.",
|
|
99
|
+
"brain.proof.recallEmpty.detail": "아직 저장된 기억이 없어서 가짜 예시는 보여주지 않습니다.",
|
|
100
|
+
"brain.proof.recallPending": "기억은 저장됐고 recall 증거를 준비 중입니다.",
|
|
101
|
+
"brain.proof.recallPending.detail": "인덱싱이 끝나면 이 자리에서 실제 기억을 다시 보여줍니다.",
|
|
102
|
+
"brain.memory.empty.kicker": "첫 기억",
|
|
70
103
|
"brain.memory.empty": "기억이 조용합니다.",
|
|
71
104
|
"brain.knowledge.empty": "지식이 형성되는 중입니다.",
|
|
72
105
|
"brain.graph.empty": "아직 맞는 지식이 없습니다.",
|
|
@@ -352,6 +385,7 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
352
385
|
"brain.aria.home": "Lattice Brain",
|
|
353
386
|
"brain.aria.exploration": "Brain exploration",
|
|
354
387
|
"brain.aria.quickViews": "Brain quick views",
|
|
388
|
+
"brain.depthRail.aria": "Brain depth progress",
|
|
355
389
|
"brain.aria.conversation": "Conversation",
|
|
356
390
|
"brain.aria.ownership": "Brain ownership guarantees",
|
|
357
391
|
"brain.aria.starterPrompts": "Starter prompts",
|
|
@@ -373,11 +407,19 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
373
407
|
"brain.prompt.know": "Put this document into my Brain and summarize it: ",
|
|
374
408
|
"brain.prompt.plan": "Organize past decisions so I can find them later: ",
|
|
375
409
|
"brain.placeholder": "Talk to your Brain...",
|
|
410
|
+
"brain.upload.cta": "Add file or document",
|
|
411
|
+
"brain.upload.ctaShort": "Document",
|
|
412
|
+
"brain.upload.uploading": "Adding...",
|
|
413
|
+
"brain.upload.hint": "After upload, it reappears as real memory and graph context.",
|
|
414
|
+
"brain.upload.pending": "Adding {name} to Brain.",
|
|
415
|
+
"brain.upload.saved": "{name} was saved to Brain.",
|
|
416
|
+
"brain.upload.failed": "Could not add the document: {reason}",
|
|
376
417
|
"brain.image": "Image",
|
|
377
418
|
"brain.unavailable": "Unavailable",
|
|
378
419
|
"brain.imageAttached": "Image attached",
|
|
379
420
|
"brain.send": "Send",
|
|
380
421
|
"brain.saved": "Saved to memory · {topics} linked topics · {memories} related memories",
|
|
422
|
+
"brain.saved.detail": "Available later with source-aware recall.",
|
|
381
423
|
"brain.recalled": "Recalled this memory: {title}",
|
|
382
424
|
"brain.overview.kicker": "Brain at a glance",
|
|
383
425
|
"brain.overview.title": "See memories and topics immediately.",
|
|
@@ -388,6 +430,30 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
388
430
|
"brain.overview.olderEmpty": "Earlier memories appear as conversations accumulate.",
|
|
389
431
|
"brain.overview.topics": "Main topics",
|
|
390
432
|
"brain.overview.topicsEmpty": "Topics are still forming.",
|
|
433
|
+
"brain.readiness.kicker": "Brain readiness",
|
|
434
|
+
"brain.readiness.aria": "Brain readiness score",
|
|
435
|
+
"brain.readiness.quiet": "Waiting for the first memory.",
|
|
436
|
+
"brain.readiness.forming": "Memories are becoming topics.",
|
|
437
|
+
"brain.readiness.alive": "Memories and relationships are connected.",
|
|
438
|
+
"brain.readiness.start": "View first memory",
|
|
439
|
+
"brain.readiness.grow": "Grow topics",
|
|
440
|
+
"brain.readiness.map": "Open map",
|
|
441
|
+
"brain.proof.aria": "Brain proof",
|
|
442
|
+
"brain.proof.context": "Recallable context",
|
|
443
|
+
"brain.proof.contextValue": "{count} saved",
|
|
444
|
+
"brain.proof.contextEmpty": "Waiting for first memory",
|
|
445
|
+
"brain.proof.model": "Survives model switches",
|
|
446
|
+
"brain.proof.noModel": "No model loaded",
|
|
447
|
+
"brain.proof.modelPending": "Proven after saving",
|
|
448
|
+
"brain.proof.store": "Knowledge store",
|
|
449
|
+
"brain.proof.storeValue": "{topics} topics · {vectors} vectors",
|
|
450
|
+
"brain.proof.recall": "Just recalled by Brain",
|
|
451
|
+
"brain.proof.recallEmpty.kicker": "Brain proof slot",
|
|
452
|
+
"brain.proof.recallEmpty.title": "After your first exchange, recalled memory appears here.",
|
|
453
|
+
"brain.proof.recallEmpty.detail": "No saved memory yet, so Lattice does not show a fake example.",
|
|
454
|
+
"brain.proof.recallPending": "Memory is saved; recall proof is preparing.",
|
|
455
|
+
"brain.proof.recallPending.detail": "When indexing finishes, this slot shows the actual memory Brain can bring back.",
|
|
456
|
+
"brain.memory.empty.kicker": "First memory",
|
|
391
457
|
"brain.memory.empty": "Memory is quiet",
|
|
392
458
|
"brain.knowledge.empty": "Knowledge is forming",
|
|
393
459
|
"brain.graph.empty": "No matching knowledge yet",
|