ltcai 6.7.0 → 7.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 +29 -31
- package/docs/CHANGELOG.md +25 -0
- package/frontend/src/api/client.ts +5 -0
- package/frontend/src/features/brain/BrainConversation.tsx +163 -1
- package/frontend/src/features/brain/BrainHome.tsx +108 -1
- package/frontend/src/features/brain/types.ts +13 -0
- package/frontend/src/i18n.ts +70 -0
- package/frontend/src/styles.css +212 -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/scripts/brain_quality_eval.py +118 -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 +28 -28
- package/static/app/assets/{Act-DGN37eR4.js → Act-DhLaHnrT.js} +2 -2
- package/static/app/assets/{Act-DGN37eR4.js.map → Act-DhLaHnrT.js.map} +1 -1
- package/static/app/assets/{Brain-HDcIwPHW.js → Brain-DS7NULyY.js} +3 -3
- package/static/app/assets/{Brain-HDcIwPHW.js.map → Brain-DS7NULyY.js.map} +1 -1
- package/static/app/assets/Capture-CAKouj52.js +2 -0
- package/static/app/assets/Capture-CAKouj52.js.map +1 -0
- package/static/app/assets/{Library-BbagRFyd.js → Library-B_hW4AwR.js} +2 -2
- package/static/app/assets/{Library-BbagRFyd.js.map → Library-B_hW4AwR.js.map} +1 -1
- package/static/app/assets/{System-C6pd3Mp_.js → System-9OAoXWPz.js} +2 -2
- package/static/app/assets/{System-C6pd3Mp_.js.map → System-9OAoXWPz.js.map} +1 -1
- package/static/app/assets/index-C-7aHabu.js +17 -0
- package/static/app/assets/index-C-7aHabu.js.map +1 -0
- package/static/app/assets/{index-ztppMg0c.css → index-DKFpn5Kn.css} +1 -1
- package/static/app/assets/{primitives-Bcm5lZRu.js → primitives-DcO2H3yh.js} +2 -2
- package/static/app/assets/{primitives-Bcm5lZRu.js.map → primitives-DcO2H3yh.js.map} +1 -1
- package/static/app/assets/{textarea-C2PLu6_O.js → textarea-DR2Tyy_6.js} +2 -2
- package/static/app/assets/{textarea-C2PLu6_O.js.map → textarea-DR2Tyy_6.js.map} +1 -1
- package/static/app/index.html +2 -2
- package/static/app/assets/Capture--eZfb_Ex.js +0 -2
- package/static/app/assets/Capture--eZfb_Ex.js.map +0 -1
- package/static/app/assets/index-C950aES_.js +0 -17
- package/static/app/assets/index-C950aES_.js.map +0 -1
package/README.md
CHANGED
|
@@ -69,42 +69,42 @@ You need Lattice AI when:
|
|
|
69
69
|
Choose the owner of the Brain. The profile is not a SaaS account by default; it
|
|
70
70
|
is the local identity for the knowledge you keep.
|
|
71
71
|
|
|
72
|
-

|
|
73
73
|
|
|
74
74
|
### 2. Environment Analysis
|
|
75
75
|
|
|
76
76
|
See what kind of local AI experience this computer can support before choosing a
|
|
77
77
|
model.
|
|
78
78
|
|
|
79
|
-

|
|
80
80
|
|
|
81
81
|
### 3. Recommended Models
|
|
82
82
|
|
|
83
83
|
Start with a short list: safest recommendation, faster model, stronger model.
|
|
84
84
|
Advanced details stay available without overwhelming first-time users.
|
|
85
85
|
|
|
86
|
-

|
|
87
87
|
|
|
88
88
|
### 4. Install And Load
|
|
89
89
|
|
|
90
90
|
Download and load only after consent. Lattice explains model size, local
|
|
91
91
|
execution, and network use before work starts.
|
|
92
92
|
|
|
93
|
-

|
|
94
94
|
|
|
95
95
|
### 5. Brain Chat
|
|
96
96
|
|
|
97
97
|
Talk normally. Useful decisions and context become memory, then appear later as
|
|
98
98
|
topics, relationships, and graph structure.
|
|
99
99
|
|
|
100
|
-

|
|
101
101
|
|
|
102
102
|
### 6. Review Center
|
|
103
103
|
|
|
104
104
|
Automation results are staged for review before they become durable decisions.
|
|
105
105
|
Snooze, unsnooze, run now, approve, and dismiss actions stay explicit.
|
|
106
106
|
|
|
107
|
-

|
|
108
108
|
|
|
109
109
|
## Brain Depths
|
|
110
110
|
|
|
@@ -120,10 +120,10 @@ The user travels inward from everyday memory to deeper structure:
|
|
|
120
120
|
|
|
121
121
|
Walkthrough:
|
|
122
122
|
|
|
123
|
-

|
|
124
124
|
|
|
125
125
|
Screenshot index and capture notes:
|
|
126
|
-
[output/release/
|
|
126
|
+
[output/release/v7.0.0/SCREENSHOT_INDEX.md](output/release/v7.0.0/SCREENSHOT_INDEX.md)
|
|
127
127
|
|
|
128
128
|
## Install
|
|
129
129
|
|
|
@@ -200,29 +200,26 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
|
|
|
200
200
|
|
|
201
201
|
## Current Release
|
|
202
202
|
|
|
203
|
-
The current release is **
|
|
204
|
-
|
|
205
|
-
- Brain Home
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- `dist/ltcai-
|
|
222
|
-
- `
|
|
223
|
-
- `ltcai-6.7.0.tgz`
|
|
224
|
-
- `dist/ltcai-6.7.0.vsix`
|
|
225
|
-
- `src-tauri/target/release/bundle/dmg/Lattice AI_6.7.0_aarch64.dmg`
|
|
203
|
+
The current release is **7.0.0 Brain Productization Loop**:
|
|
204
|
+
|
|
205
|
+
- Brain Home starts with ingestion: files, folders, notes, and web pages can be
|
|
206
|
+
added directly from the first screen.
|
|
207
|
+
- Every Brain answer can show memory proof and source citations instead of
|
|
208
|
+
asking users to trust an ungrounded chat bubble.
|
|
209
|
+
- The model-continuity demo lets users recheck the same Brain evidence after
|
|
210
|
+
changing models.
|
|
211
|
+
- The first-run loop is optimized for five minutes: add source, ask, see proof,
|
|
212
|
+
then switch or deepen.
|
|
213
|
+
- CI now includes a deterministic recall/KG quality eval so the Brain proof
|
|
214
|
+
promise is guarded before release.
|
|
215
|
+
|
|
216
|
+
Expected artifacts for 7.0.0 release must use exact filenames:
|
|
217
|
+
|
|
218
|
+
- `dist/ltcai-7.0.0-py3-none-any.whl`
|
|
219
|
+
- `dist/ltcai-7.0.0.tar.gz`
|
|
220
|
+
- `ltcai-7.0.0.tgz`
|
|
221
|
+
- `dist/ltcai-7.0.0.vsix`
|
|
222
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_7.0.0_aarch64.dmg`
|
|
226
223
|
|
|
227
224
|
Do not upload `dist/*`. Package registry publishing remains owner-run.
|
|
228
225
|
|
|
@@ -241,6 +238,7 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
|
|
|
241
238
|
|
|
242
239
|
| Version | Theme |
|
|
243
240
|
| --- | --- |
|
|
241
|
+
| 7.0.0 | Brain Productization Loop: first-screen ingestion for files/folders/notes/web, answer-level memory proof and source citations, model-continuity demo flow, five-minute first-run loop, and recall/KG quality eval in CI |
|
|
244
242
|
| 6.7.0 | Brain IA Cleanup: reachable rich pages, separated product routes vs compatibility aliases, shared Brain shell navigation, and lazy-loaded rich pages |
|
|
245
243
|
| 6.6.0 | Brain Proof Runtime: backend-owned Brain proof API, model-continuity wiring, first-screen proof that saved context can be recalled across model changes, and direct Brain Home document upload |
|
|
246
244
|
| 6.5.0 | Brain Experience Readiness: Brain readiness signal, depth progress rail, source-aware memory-save feedback, and visual coverage for the first-memory loop |
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ The top entry is either the current unreleased main-branch work or the current
|
|
|
4
4
|
release line. Older entries are historical and may describe behavior as it
|
|
5
5
|
existed at that release.
|
|
6
6
|
|
|
7
|
+
## [7.0.0] - 2026-06-18
|
|
8
|
+
|
|
9
|
+
> Brain Productization Loop. Turns the Brain proof work into a first-five-minute
|
|
10
|
+
> product flow: add sources, ask a question, see proof/citations, and verify the
|
|
11
|
+
> same Brain evidence after switching models.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Brain Home ingestion panel for files, local folder paths, notes, and web URLs,
|
|
15
|
+
all backed by existing workspace-scoped ingestion routes.
|
|
16
|
+
- Answer-level Memory proof and source citation cards rendered under assistant
|
|
17
|
+
responses after Brain proof refreshes for the user's query.
|
|
18
|
+
- Model-continuity demo strip that lets the user recheck the same Brain
|
|
19
|
+
evidence and jump to model switching from the Brain flow.
|
|
20
|
+
- Deterministic `scripts/brain_quality_eval.py` recall/KG quality gate, wired
|
|
21
|
+
into CI after the unit suite.
|
|
22
|
+
- Visual mock coverage for Brain proof, document upload, note ingest, folder
|
|
23
|
+
indexing, and web URL ingestion endpoints.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Brain Home is now ingestion-first instead of chat-first: first screen action
|
|
27
|
+
labels are files/folders/notes/web, with deeper graph/model/settings still
|
|
28
|
+
reachable from the shell.
|
|
29
|
+
- Package/runtime/static metadata is synchronized to 7.0.0; package publish and
|
|
30
|
+
deployment remain owner-run only.
|
|
31
|
+
|
|
7
32
|
## [6.7.0] - 2026-06-18
|
|
8
33
|
|
|
9
34
|
> Brain IA Cleanup. Makes rich product pages reachable from Brain Home,
|
|
@@ -444,6 +444,11 @@ export const latticeApi = {
|
|
|
444
444
|
return res;
|
|
445
445
|
},
|
|
446
446
|
browserReadUrl: (url: string) => post("/api/browser/read-url", { url }, {}),
|
|
447
|
+
ingestNote: (content: string, title = "Brain note") => post(
|
|
448
|
+
"/knowledge-graph/ingest",
|
|
449
|
+
{ type: "note", content, title, source: "brain_home" },
|
|
450
|
+
{},
|
|
451
|
+
),
|
|
447
452
|
memoryManager: () => get("/api/memory/manager", { sources: [], tiers: [], usage: {} }),
|
|
448
453
|
memoryBrainQuality: () => get("/api/memory/brain-quality", {}),
|
|
449
454
|
memoryBrainProof: (query = "", limit = 3) => get("/api/memory/brain-proof", { proofs: {}, recall: { items: [] }, model_continuity: {}, claims: {} }, { q: query, limit }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Cpu, DatabaseZap, FileUp, Search, Settings, ShieldCheck } from "lucide-react";
|
|
2
|
+
import { Cpu, DatabaseZap, FileText, FileUp, FolderPlus, Globe2, Repeat2, Search, Settings, ShieldCheck } from "lucide-react";
|
|
3
3
|
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
|
4
4
|
import { t, type Language } from "@/i18n";
|
|
5
5
|
import type { BrainDepth, BrainProof, BrainReadiness, KnowledgeConcept, MemoryFragment, Message } from "./types";
|
|
@@ -27,6 +27,10 @@ export function BrainConversation({
|
|
|
27
27
|
onDraftChange,
|
|
28
28
|
onImageDataChange,
|
|
29
29
|
onUploadDocument,
|
|
30
|
+
onConnectFolder,
|
|
31
|
+
onIngestNote,
|
|
32
|
+
onIngestWeb,
|
|
33
|
+
onVerifyModelContinuity,
|
|
30
34
|
onSend,
|
|
31
35
|
}: {
|
|
32
36
|
language: Language;
|
|
@@ -48,6 +52,10 @@ export function BrainConversation({
|
|
|
48
52
|
onDraftChange: (value: string) => void;
|
|
49
53
|
onImageDataChange: (value: string | null) => void;
|
|
50
54
|
onUploadDocument: (file: File) => void;
|
|
55
|
+
onConnectFolder: (path: string) => void;
|
|
56
|
+
onIngestNote: (note: string) => void;
|
|
57
|
+
onIngestWeb: (url: string) => void;
|
|
58
|
+
onVerifyModelContinuity: () => void;
|
|
51
59
|
onSend: () => void;
|
|
52
60
|
}) {
|
|
53
61
|
return (
|
|
@@ -89,6 +97,14 @@ export function BrainConversation({
|
|
|
89
97
|
</div>
|
|
90
98
|
|
|
91
99
|
<div ref={streamRef} className="brain-stream">
|
|
100
|
+
<BrainIngestionPanel
|
|
101
|
+
language={language}
|
|
102
|
+
uploadingDocument={uploadingDocument}
|
|
103
|
+
onUploadDocument={onUploadDocument}
|
|
104
|
+
onConnectFolder={onConnectFolder}
|
|
105
|
+
onIngestNote={onIngestNote}
|
|
106
|
+
onIngestWeb={onIngestWeb}
|
|
107
|
+
/>
|
|
92
108
|
<BrainOverviewPanel
|
|
93
109
|
memories={memories}
|
|
94
110
|
concepts={concepts}
|
|
@@ -96,6 +112,12 @@ export function BrainConversation({
|
|
|
96
112
|
proof={proof}
|
|
97
113
|
onOpenDepth={onOpenDepth}
|
|
98
114
|
/>
|
|
115
|
+
<ModelContinuityDemo
|
|
116
|
+
language={language}
|
|
117
|
+
proof={proof}
|
|
118
|
+
modelName={modelName}
|
|
119
|
+
onVerify={onVerifyModelContinuity}
|
|
120
|
+
/>
|
|
99
121
|
{messages.length === 0 ? (
|
|
100
122
|
<BrainEmptyState
|
|
101
123
|
language={language}
|
|
@@ -108,6 +130,9 @@ export function BrainConversation({
|
|
|
108
130
|
messages.map((message, index) => (
|
|
109
131
|
<div key={`${message.role}-${index}`} className={`brain-message ${message.role}`}>
|
|
110
132
|
<div className="brain-message-bubble">{message.content}</div>
|
|
133
|
+
{message.role === "assistant" && message.proof ? (
|
|
134
|
+
<AnswerProofCard language={language} proof={message.proof} />
|
|
135
|
+
) : null}
|
|
111
136
|
</div>
|
|
112
137
|
))
|
|
113
138
|
)}
|
|
@@ -138,6 +163,143 @@ export function BrainConversation({
|
|
|
138
163
|
);
|
|
139
164
|
}
|
|
140
165
|
|
|
166
|
+
function BrainIngestionPanel({
|
|
167
|
+
language,
|
|
168
|
+
uploadingDocument,
|
|
169
|
+
onUploadDocument,
|
|
170
|
+
onConnectFolder,
|
|
171
|
+
onIngestNote,
|
|
172
|
+
onIngestWeb,
|
|
173
|
+
}: {
|
|
174
|
+
language: Language;
|
|
175
|
+
uploadingDocument: boolean;
|
|
176
|
+
onUploadDocument: (file: File) => void;
|
|
177
|
+
onConnectFolder: (path: string) => void;
|
|
178
|
+
onIngestNote: (note: string) => void;
|
|
179
|
+
onIngestWeb: (url: string) => void;
|
|
180
|
+
}) {
|
|
181
|
+
const [folderPath, setFolderPath] = React.useState("");
|
|
182
|
+
const [note, setNote] = React.useState("");
|
|
183
|
+
const [url, setUrl] = React.useState("");
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<section className="brain-ingestion-panel" aria-label={t(language, "brain.ingest.aria")}>
|
|
187
|
+
<div className="brain-ingestion-head">
|
|
188
|
+
<span>{t(language, "brain.ingest.kicker")}</span>
|
|
189
|
+
<strong>{t(language, "brain.ingest.title")}</strong>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="brain-ingestion-grid">
|
|
192
|
+
<label className={`brain-ingest-tile is-primary ${uploadingDocument ? "is-disabled" : ""}`}>
|
|
193
|
+
<FileUp className="h-4 w-4" />
|
|
194
|
+
<span>{uploadingDocument ? t(language, "brain.upload.uploading") : t(language, "brain.ingest.file")}</span>
|
|
195
|
+
<small>{t(language, "brain.ingest.file.detail")}</small>
|
|
196
|
+
<input
|
|
197
|
+
type="file"
|
|
198
|
+
accept=".pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,application/pdf,text/plain,text/markdown,text/csv"
|
|
199
|
+
className="sr-only"
|
|
200
|
+
disabled={uploadingDocument}
|
|
201
|
+
onChange={(event) => {
|
|
202
|
+
const file = event.target.files?.[0];
|
|
203
|
+
event.currentTarget.value = "";
|
|
204
|
+
if (file) onUploadDocument(file);
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
</label>
|
|
208
|
+
<form
|
|
209
|
+
className="brain-ingest-tile"
|
|
210
|
+
onSubmit={(event) => {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
onConnectFolder(folderPath);
|
|
213
|
+
setFolderPath("");
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
<FolderPlus className="h-4 w-4" />
|
|
217
|
+
<span>{t(language, "brain.ingest.folder")}</span>
|
|
218
|
+
<input value={folderPath} onChange={(event) => setFolderPath(event.target.value)} placeholder={t(language, "brain.ingest.folder.placeholder")} />
|
|
219
|
+
</form>
|
|
220
|
+
<form
|
|
221
|
+
className="brain-ingest-tile"
|
|
222
|
+
onSubmit={(event) => {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
onIngestNote(note);
|
|
225
|
+
setNote("");
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
<FileText className="h-4 w-4" />
|
|
229
|
+
<span>{t(language, "brain.ingest.note")}</span>
|
|
230
|
+
<input value={note} onChange={(event) => setNote(event.target.value)} placeholder={t(language, "brain.ingest.note.placeholder")} />
|
|
231
|
+
</form>
|
|
232
|
+
<form
|
|
233
|
+
className="brain-ingest-tile"
|
|
234
|
+
onSubmit={(event) => {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
onIngestWeb(url);
|
|
237
|
+
setUrl("");
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<Globe2 className="h-4 w-4" />
|
|
241
|
+
<span>{t(language, "brain.ingest.web")}</span>
|
|
242
|
+
<input value={url} onChange={(event) => setUrl(event.target.value)} placeholder={t(language, "brain.ingest.web.placeholder")} />
|
|
243
|
+
</form>
|
|
244
|
+
</div>
|
|
245
|
+
</section>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function ModelContinuityDemo({
|
|
250
|
+
language,
|
|
251
|
+
proof,
|
|
252
|
+
modelName,
|
|
253
|
+
onVerify,
|
|
254
|
+
}: {
|
|
255
|
+
language: Language;
|
|
256
|
+
proof: BrainProof;
|
|
257
|
+
modelName: string;
|
|
258
|
+
onVerify: () => void;
|
|
259
|
+
}) {
|
|
260
|
+
return (
|
|
261
|
+
<section className="brain-model-demo" aria-label={t(language, "brain.modelDemo.aria")}>
|
|
262
|
+
<div>
|
|
263
|
+
<span>{t(language, "brain.modelDemo.kicker")}</span>
|
|
264
|
+
<strong>{proof.modelContinuity.proven ? t(language, "brain.modelDemo.proven") : t(language, "brain.modelDemo.pending")}</strong>
|
|
265
|
+
<small>{t(language, "brain.modelDemo.detail", { model: proof.modelContinuity.activeModel || modelName })}</small>
|
|
266
|
+
</div>
|
|
267
|
+
<button type="button" onClick={onVerify}>
|
|
268
|
+
<Repeat2 className="h-3.5 w-3.5" />
|
|
269
|
+
{t(language, "brain.modelDemo.verify")}
|
|
270
|
+
</button>
|
|
271
|
+
<button type="button" onClick={() => navigateHash("/models")}>
|
|
272
|
+
<Cpu className="h-3.5 w-3.5" />
|
|
273
|
+
{t(language, "brain.modelDemo.change")}
|
|
274
|
+
</button>
|
|
275
|
+
</section>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function AnswerProofCard({ language, proof }: { language: Language; proof: NonNullable<Message["proof"]> }) {
|
|
280
|
+
return (
|
|
281
|
+
<div className="brain-answer-proof" aria-label={t(language, "brain.answerProof.aria")}>
|
|
282
|
+
<div className="brain-answer-proof-head">
|
|
283
|
+
<span>{t(language, "brain.answerProof.title")}</span>
|
|
284
|
+
<strong>{proof.provenAcrossModels ? t(language, "brain.answerProof.modelProven", { model: proof.model }) : t(language, "brain.answerProof.modelPending", { model: proof.model })}</strong>
|
|
285
|
+
</div>
|
|
286
|
+
{proof.citations.length ? (
|
|
287
|
+
<ol>
|
|
288
|
+
{proof.citations.map((citation) => (
|
|
289
|
+
<li key={citation.id}>
|
|
290
|
+
<span>{citation.source}</span>
|
|
291
|
+
<strong>{citation.title}</strong>
|
|
292
|
+
<small>{citation.snippet || proof.query}</small>
|
|
293
|
+
</li>
|
|
294
|
+
))}
|
|
295
|
+
</ol>
|
|
296
|
+
) : (
|
|
297
|
+
<small>{t(language, "brain.answerProof.empty")}</small>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
141
303
|
function BrainEmptyState({
|
|
142
304
|
language,
|
|
143
305
|
starterPrompts,
|
|
@@ -7,7 +7,7 @@ import { t } from "@/i18n";
|
|
|
7
7
|
import { BrainConversation } from "./BrainConversation";
|
|
8
8
|
import { buildBrainProof, buildBrainReadiness, buildMemoryFragments, currentModelName, parseKnowledgeGraph } from "./brainData";
|
|
9
9
|
import { DepthEmergence } from "./DepthEmergence";
|
|
10
|
-
import { DEPTHS, type BrainDepth, type MemoryFragment, type Message } from "./types";
|
|
10
|
+
import { DEPTHS, type BrainDepth, type BrainProof, type MemoryFragment, type Message, type MessageProof } from "./types";
|
|
11
11
|
|
|
12
12
|
export function BrainHome({
|
|
13
13
|
brainState,
|
|
@@ -134,6 +134,7 @@ export function BrainHome({
|
|
|
134
134
|
} else {
|
|
135
135
|
setMemoryFeedback(t(language, "brain.saved", { topics: knowledgeConcepts.length, memories: memoryFragments.length }));
|
|
136
136
|
setLastRecallQuery(text);
|
|
137
|
+
void attachAnswerProof(text);
|
|
137
138
|
}
|
|
138
139
|
} finally {
|
|
139
140
|
setStreaming(false);
|
|
@@ -144,6 +145,22 @@ export function BrainHome({
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
async function attachAnswerProof(query: string) {
|
|
149
|
+
const proofResult = await latticeApi.memoryBrainProof(query, 4);
|
|
150
|
+
const proof = buildBrainProof(proofResult.data, modelName);
|
|
151
|
+
const messageProof = toMessageProof(proof, query, modelName);
|
|
152
|
+
setMessages((items) => {
|
|
153
|
+
const next = [...items];
|
|
154
|
+
for (let index = next.length - 1; index >= 0; index -= 1) {
|
|
155
|
+
if (next[index].role === "assistant") {
|
|
156
|
+
next[index] = { ...next[index], proof: messageProof };
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return next;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
147
164
|
async function uploadDocument(file: File) {
|
|
148
165
|
if (uploadingDocument) return;
|
|
149
166
|
|
|
@@ -169,6 +186,78 @@ export function BrainHome({
|
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
|
|
189
|
+
async function connectFolder(path: string) {
|
|
190
|
+
const target = path.trim();
|
|
191
|
+
if (!target) return;
|
|
192
|
+
setMemoryFeedback(t(language, "brain.ingest.folder.pending", { path: target }));
|
|
193
|
+
onBrainChange("recalling", 0.84);
|
|
194
|
+
const result = await latticeApi.connectFolder(target);
|
|
195
|
+
if (result.error || !result.ok) {
|
|
196
|
+
setMemoryFeedback(t(language, "brain.ingest.folder.failed", { reason: result.error || "unavailable" }));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
setMemoryFeedback(t(language, "brain.ingest.folder.saved", { path: target }));
|
|
200
|
+
setLastRecallQuery(target);
|
|
201
|
+
triggerBrainRecall();
|
|
202
|
+
void refreshBrainProof(target);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function ingestNote(note: string) {
|
|
206
|
+
const content = note.trim();
|
|
207
|
+
if (!content) return;
|
|
208
|
+
setMemoryFeedback(t(language, "brain.ingest.note.pending"));
|
|
209
|
+
onBrainChange("recalling", 0.84);
|
|
210
|
+
const result = await latticeApi.ingestNote(content, content.slice(0, 80));
|
|
211
|
+
if (result.error || !result.ok) {
|
|
212
|
+
setMemoryFeedback(t(language, "brain.ingest.note.failed", { reason: result.error || "unavailable" }));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
setMemoryFeedback(t(language, "brain.ingest.note.saved"));
|
|
216
|
+
setLastRecallQuery(content.slice(0, 120));
|
|
217
|
+
triggerBrainRecall();
|
|
218
|
+
void refreshBrainProof(content.slice(0, 120));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function ingestWeb(url: string) {
|
|
222
|
+
const target = url.trim();
|
|
223
|
+
if (!target) return;
|
|
224
|
+
setMemoryFeedback(t(language, "brain.ingest.web.pending", { url: target }));
|
|
225
|
+
onBrainChange("recalling", 0.84);
|
|
226
|
+
const result = await latticeApi.browserReadUrl(target);
|
|
227
|
+
if (result.error || !result.ok) {
|
|
228
|
+
setMemoryFeedback(t(language, "brain.ingest.web.failed", { reason: result.error || "unavailable" }));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
setMemoryFeedback(t(language, "brain.ingest.web.saved", { url: target }));
|
|
232
|
+
setLastRecallQuery(target);
|
|
233
|
+
triggerBrainRecall();
|
|
234
|
+
void refreshBrainProof(target);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function refreshBrainProof(query = lastRecallQuery) {
|
|
238
|
+
await Promise.all([
|
|
239
|
+
qc.invalidateQueries({ queryKey: ["memoryManager"] }),
|
|
240
|
+
qc.invalidateQueries({ queryKey: ["graph"] }),
|
|
241
|
+
qc.invalidateQueries({ queryKey: ["memoryBrainProof"] }),
|
|
242
|
+
]);
|
|
243
|
+
if (query.trim()) {
|
|
244
|
+
await attachAnswerProof(query);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function verifyModelContinuity() {
|
|
249
|
+
const lastUserMessage = [...messages].reverse().find((message: Message) => message.role === "user");
|
|
250
|
+
const query = lastRecallQuery || lastUserMessage?.content || "";
|
|
251
|
+
if (!query.trim()) {
|
|
252
|
+
setMemoryFeedback(t(language, "brain.modelDemo.needQuestion"));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
setMemoryFeedback(t(language, "brain.modelDemo.checking", { model: modelName }));
|
|
256
|
+
await attachAnswerProof(query);
|
|
257
|
+
setLastRecallQuery(query);
|
|
258
|
+
setMemoryFeedback(t(language, "brain.modelDemo.done", { model: modelName }));
|
|
259
|
+
}
|
|
260
|
+
|
|
172
261
|
function deepen() {
|
|
173
262
|
setExplorationDepth((depth) => {
|
|
174
263
|
const next = Math.min(5, depth + 1) as BrainDepth;
|
|
@@ -285,8 +374,26 @@ export function BrainHome({
|
|
|
285
374
|
onDraftChange={setDraft}
|
|
286
375
|
onImageDataChange={setImageData}
|
|
287
376
|
onUploadDocument={(file) => void uploadDocument(file)}
|
|
377
|
+
onConnectFolder={(path) => void connectFolder(path)}
|
|
378
|
+
onIngestNote={(note) => void ingestNote(note)}
|
|
379
|
+
onIngestWeb={(url) => void ingestWeb(url)}
|
|
380
|
+
onVerifyModelContinuity={() => void verifyModelContinuity()}
|
|
288
381
|
onSend={() => void send()}
|
|
289
382
|
/>
|
|
290
383
|
</main>
|
|
291
384
|
);
|
|
292
385
|
}
|
|
386
|
+
|
|
387
|
+
function toMessageProof(proof: BrainProof, query: string, fallbackModelName: string): MessageProof {
|
|
388
|
+
return {
|
|
389
|
+
query,
|
|
390
|
+
model: proof.modelContinuity.activeModel || fallbackModelName,
|
|
391
|
+
provenAcrossModels: proof.modelContinuity.proven && proof.claims.keepsContextAcrossModels,
|
|
392
|
+
citations: proof.recall.items.slice(0, 4).map((item) => ({
|
|
393
|
+
id: item.id,
|
|
394
|
+
source: item.source,
|
|
395
|
+
title: item.title,
|
|
396
|
+
snippet: item.snippet,
|
|
397
|
+
})),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
@@ -6,6 +6,19 @@ export type BrainDepth = 1 | 2 | 3 | 4 | 5;
|
|
|
6
6
|
export type Message = {
|
|
7
7
|
role: "user" | "assistant";
|
|
8
8
|
content: string;
|
|
9
|
+
proof?: MessageProof;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type MessageProof = {
|
|
13
|
+
query: string;
|
|
14
|
+
model: string;
|
|
15
|
+
provenAcrossModels: boolean;
|
|
16
|
+
citations: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
source: string;
|
|
19
|
+
title: string;
|
|
20
|
+
snippet: string;
|
|
21
|
+
}>;
|
|
9
22
|
};
|
|
10
23
|
|
|
11
24
|
export type MemoryFragment = {
|
package/frontend/src/i18n.ts
CHANGED
|
@@ -65,6 +65,26 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
65
65
|
"brain.upload.pending": "{name}을 Brain에 넣는 중입니다.",
|
|
66
66
|
"brain.upload.saved": "{name}이 Brain에 저장됐습니다.",
|
|
67
67
|
"brain.upload.failed": "문서를 넣지 못했습니다: {reason}",
|
|
68
|
+
"brain.ingest.aria": "Brain에 넣기",
|
|
69
|
+
"brain.ingest.kicker": "5분 안에 Brain 만들기",
|
|
70
|
+
"brain.ingest.title": "파일, 폴더, 노트, 웹을 바로 넣으세요.",
|
|
71
|
+
"brain.ingest.file": "파일 넣기",
|
|
72
|
+
"brain.ingest.file.detail": "PDF, 문서, 표, 메모",
|
|
73
|
+
"brain.ingest.folder": "폴더 넣기",
|
|
74
|
+
"brain.ingest.folder.placeholder": "로컬 폴더 경로",
|
|
75
|
+
"brain.ingest.note": "노트 넣기",
|
|
76
|
+
"brain.ingest.note.placeholder": "결정이나 맥락을 붙여넣기",
|
|
77
|
+
"brain.ingest.web": "웹 넣기",
|
|
78
|
+
"brain.ingest.web.placeholder": "https://...",
|
|
79
|
+
"brain.ingest.folder.pending": "{path} 폴더를 Brain에 연결 중입니다.",
|
|
80
|
+
"brain.ingest.folder.saved": "{path} 폴더가 Brain에 연결됐습니다.",
|
|
81
|
+
"brain.ingest.folder.failed": "폴더를 넣지 못했습니다: {reason}",
|
|
82
|
+
"brain.ingest.note.pending": "노트를 Brain에 저장 중입니다.",
|
|
83
|
+
"brain.ingest.note.saved": "노트가 Brain에 저장됐습니다.",
|
|
84
|
+
"brain.ingest.note.failed": "노트를 넣지 못했습니다: {reason}",
|
|
85
|
+
"brain.ingest.web.pending": "{url}을 읽어 Brain에 넣는 중입니다.",
|
|
86
|
+
"brain.ingest.web.saved": "{url}이 Brain에 저장됐습니다.",
|
|
87
|
+
"brain.ingest.web.failed": "웹 페이지를 넣지 못했습니다: {reason}",
|
|
68
88
|
"brain.image": "이미지",
|
|
69
89
|
"brain.unavailable": "지금은 답할 수 없음",
|
|
70
90
|
"brain.imageAttached": "이미지 첨부됨",
|
|
@@ -104,6 +124,21 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
104
124
|
"brain.proof.recallEmpty.detail": "아직 저장된 기억이 없어서 가짜 예시는 보여주지 않습니다.",
|
|
105
125
|
"brain.proof.recallPending": "기억은 저장됐고 recall 증거를 준비 중입니다.",
|
|
106
126
|
"brain.proof.recallPending.detail": "인덱싱이 끝나면 이 자리에서 실제 기억을 다시 보여줍니다.",
|
|
127
|
+
"brain.modelDemo.aria": "모델 교체 증거",
|
|
128
|
+
"brain.modelDemo.kicker": "모델 독립 Brain",
|
|
129
|
+
"brain.modelDemo.proven": "현재 기억은 모델 밖에 남아 있습니다.",
|
|
130
|
+
"brain.modelDemo.pending": "첫 기억이 저장되면 모델 교체 증거가 생깁니다.",
|
|
131
|
+
"brain.modelDemo.detail": "현재 모델: {model}. 같은 질문으로 Brain 증거를 다시 확인할 수 있습니다.",
|
|
132
|
+
"brain.modelDemo.verify": "같은 증거 확인",
|
|
133
|
+
"brain.modelDemo.change": "모델 바꾸기",
|
|
134
|
+
"brain.modelDemo.needQuestion": "먼저 질문하거나 기억을 넣으면 같은 증거를 다시 확인할 수 있습니다.",
|
|
135
|
+
"brain.modelDemo.checking": "{model}에서 같은 Brain 증거를 다시 확인 중입니다.",
|
|
136
|
+
"brain.modelDemo.done": "{model}에서도 같은 Brain 증거를 확인했습니다.",
|
|
137
|
+
"brain.answerProof.aria": "답변 출처와 Memory proof",
|
|
138
|
+
"brain.answerProof.title": "Memory proof + source citation",
|
|
139
|
+
"brain.answerProof.modelProven": "{model} 밖에 남은 Brain 증거",
|
|
140
|
+
"brain.answerProof.modelPending": "{model} 응답 · 증거 저장 대기",
|
|
141
|
+
"brain.answerProof.empty": "아직 연결된 출처가 없습니다. 기억이 인덱싱되면 여기에 표시됩니다.",
|
|
107
142
|
"brain.memory.empty.kicker": "첫 기억",
|
|
108
143
|
"brain.memory.empty": "기억이 조용합니다.",
|
|
109
144
|
"brain.knowledge.empty": "지식이 형성되는 중입니다.",
|
|
@@ -424,6 +459,26 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
424
459
|
"brain.upload.pending": "Adding {name} to Brain.",
|
|
425
460
|
"brain.upload.saved": "{name} was saved to Brain.",
|
|
426
461
|
"brain.upload.failed": "Could not add the document: {reason}",
|
|
462
|
+
"brain.ingest.aria": "Add to Brain",
|
|
463
|
+
"brain.ingest.kicker": "Build a Brain in 5 minutes",
|
|
464
|
+
"brain.ingest.title": "Add files, folders, notes, and web pages immediately.",
|
|
465
|
+
"brain.ingest.file": "Add file",
|
|
466
|
+
"brain.ingest.file.detail": "PDFs, docs, tables, notes",
|
|
467
|
+
"brain.ingest.folder": "Add folder",
|
|
468
|
+
"brain.ingest.folder.placeholder": "Local folder path",
|
|
469
|
+
"brain.ingest.note": "Add note",
|
|
470
|
+
"brain.ingest.note.placeholder": "Paste a decision or context",
|
|
471
|
+
"brain.ingest.web": "Add web",
|
|
472
|
+
"brain.ingest.web.placeholder": "https://...",
|
|
473
|
+
"brain.ingest.folder.pending": "Connecting {path} to Brain.",
|
|
474
|
+
"brain.ingest.folder.saved": "{path} is connected to Brain.",
|
|
475
|
+
"brain.ingest.folder.failed": "Could not add the folder: {reason}",
|
|
476
|
+
"brain.ingest.note.pending": "Saving note to Brain.",
|
|
477
|
+
"brain.ingest.note.saved": "Note was saved to Brain.",
|
|
478
|
+
"brain.ingest.note.failed": "Could not add the note: {reason}",
|
|
479
|
+
"brain.ingest.web.pending": "Reading {url} into Brain.",
|
|
480
|
+
"brain.ingest.web.saved": "{url} was saved to Brain.",
|
|
481
|
+
"brain.ingest.web.failed": "Could not add the web page: {reason}",
|
|
427
482
|
"brain.image": "Image",
|
|
428
483
|
"brain.unavailable": "Unavailable",
|
|
429
484
|
"brain.imageAttached": "Image attached",
|
|
@@ -463,6 +518,21 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
463
518
|
"brain.proof.recallEmpty.detail": "No saved memory yet, so Lattice does not show a fake example.",
|
|
464
519
|
"brain.proof.recallPending": "Memory is saved; recall proof is preparing.",
|
|
465
520
|
"brain.proof.recallPending.detail": "When indexing finishes, this slot shows the actual memory Brain can bring back.",
|
|
521
|
+
"brain.modelDemo.aria": "Model switch proof",
|
|
522
|
+
"brain.modelDemo.kicker": "Model-independent Brain",
|
|
523
|
+
"brain.modelDemo.proven": "This memory lives outside the model.",
|
|
524
|
+
"brain.modelDemo.pending": "After the first memory is saved, model-switch proof appears.",
|
|
525
|
+
"brain.modelDemo.detail": "Current model: {model}. Recheck the same question against Brain evidence.",
|
|
526
|
+
"brain.modelDemo.verify": "Recheck same evidence",
|
|
527
|
+
"brain.modelDemo.change": "Switch model",
|
|
528
|
+
"brain.modelDemo.needQuestion": "Ask a question or add memory first, then recheck the same evidence.",
|
|
529
|
+
"brain.modelDemo.checking": "Rechecking the same Brain evidence with {model}.",
|
|
530
|
+
"brain.modelDemo.done": "The same Brain evidence is visible with {model}.",
|
|
531
|
+
"brain.answerProof.aria": "Answer sources and memory proof",
|
|
532
|
+
"brain.answerProof.title": "Memory proof + source citation",
|
|
533
|
+
"brain.answerProof.modelProven": "Brain evidence outside {model}",
|
|
534
|
+
"brain.answerProof.modelPending": "{model} response · evidence pending",
|
|
535
|
+
"brain.answerProof.empty": "No linked source yet. Indexed memory appears here.",
|
|
466
536
|
"brain.memory.empty.kicker": "First memory",
|
|
467
537
|
"brain.memory.empty": "Memory is quiet",
|
|
468
538
|
"brain.knowledge.empty": "Knowledge is forming",
|