ltcai 6.3.1 → 6.5.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.
Files changed (37) hide show
  1. package/README.md +35 -41
  2. package/docs/CHANGELOG.md +59 -0
  3. package/frontend/src/api/client.ts +1 -0
  4. package/frontend/src/features/brain/BrainConversation.tsx +12 -3
  5. package/frontend/src/features/brain/BrainHome.tsx +21 -1
  6. package/frontend/src/features/brain/BrainMemoryLayer.tsx +8 -1
  7. package/frontend/src/features/brain/BrainOverviewPanel.tsx +15 -1
  8. package/frontend/src/features/brain/brainData.ts +72 -1
  9. package/frontend/src/features/brain/types.ts +15 -0
  10. package/frontend/src/i18n.ts +22 -0
  11. package/frontend/src/styles.css +128 -0
  12. package/lattice_brain/__init__.py +3 -1
  13. package/lattice_brain/quality.py +409 -0
  14. package/lattice_brain/runtime/multi_agent.py +1 -1
  15. package/latticeai/__init__.py +1 -1
  16. package/latticeai/api/knowledge_graph.py +71 -10
  17. package/latticeai/api/local_files.py +2 -0
  18. package/latticeai/api/memory.py +12 -6
  19. package/latticeai/api/search.py +9 -1
  20. package/latticeai/api/tools.py +3 -0
  21. package/latticeai/core/marketplace.py +1 -1
  22. package/latticeai/core/workspace_os.py +1 -1
  23. package/latticeai/runtime/chat_wiring.py +1 -0
  24. package/latticeai/services/memory_service.py +130 -21
  25. package/latticeai/services/router_context.py +4 -0
  26. package/latticeai/services/search_service.py +61 -10
  27. package/package.json +1 -1
  28. package/src-tauri/Cargo.lock +1 -1
  29. package/src-tauri/Cargo.toml +1 -1
  30. package/src-tauri/tauri.conf.json +1 -1
  31. package/static/app/asset-manifest.json +5 -5
  32. package/static/app/assets/index-C1J_1441.js +16 -0
  33. package/static/app/assets/index-C1J_1441.js.map +1 -0
  34. package/static/app/assets/{index-Div5vMlq.css → index-CdCVz_i4.css} +1 -1
  35. package/static/app/index.html +2 -2
  36. package/static/app/assets/index-t1jx1BR9.js +0 -16
  37. package/static/app/assets/index-t1jx1BR9.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
- ![Login](output/release/v6.3.1/screenshots/01-login.png)
72
+ ![Login](output/release/v6.5.0/screenshots/01-login.png)
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
- ![Environment Analysis](output/release/v6.3.1/screenshots/02-environment-analysis.png)
79
+ ![Environment Analysis](output/release/v6.5.0/screenshots/02-environment-analysis.png)
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
- ![Recommended Models](output/release/v6.3.1/screenshots/03-recommended-models.png)
86
+ ![Recommended Models](output/release/v6.5.0/screenshots/03-recommended-models.png)
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
- ![Install and Load](output/release/v6.3.1/screenshots/04-install-load-progress.png)
93
+ ![Install and Load](output/release/v6.5.0/screenshots/04-install-load-progress.png)
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
- ![Brain Chat Home](output/release/v6.3.1/screenshots/05-brain-chat-home.png)
100
+ ![Brain Chat Home](output/release/v6.5.0/screenshots/05-brain-chat-home.png)
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
- ![Review Center](output/release/v6.3.1/screenshots/13-review-center.png)
107
+ ![Review Center](output/release/v6.5.0/screenshots/13-review-center.png)
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
- ![v6.3.1 Living Brain walkthrough](output/release/v6.3.1/gifs/v6.3.1-living-brain-walkthrough.gif)
123
+ ![v6.5.0 Living Brain walkthrough](output/release/v6.5.0/gifs/v6.5.0-living-brain-walkthrough.gif)
124
124
 
125
125
  Screenshot index and capture notes:
126
- [output/release/v6.3.1/SCREENSHOT_INDEX.md](output/release/v6.3.1/SCREENSHOT_INDEX.md)
126
+ [output/release/v6.5.0/SCREENSHOT_INDEX.md](output/release/v6.5.0/SCREENSHOT_INDEX.md)
127
127
 
128
128
  ## Install
129
129
 
@@ -200,39 +200,31 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
200
200
 
201
201
  ## Current Release Preparation
202
202
 
203
- The current development target is **6.3.1 Access Runtime / i18n Follow-up**:
204
-
205
- - `App.tsx` now delegates the main Brain and Admin surfaces to focused feature
206
- modules instead of owning the full product shell.
207
- - `ProductFlow.tsx` is an orchestration layer; onboarding screens live in
208
- dedicated components, use design-system classes, and avoid inline style
209
- layout.
210
- - Model download consent states download size, storage location, external
211
- target, and a "do later" path before any model fetch starts.
212
- - Admin Console and onboarding copy use the shared i18n map for Korean/English
213
- coverage instead of embedding user-facing English in component bodies.
214
- - Historical root modules now shrink into compatibility shims while runtime
215
- code lives under package modules.
216
- - Interaction/tool router assembly uses typed context objects to reduce
217
- app-factory parameter sprawl while preserving route order.
218
- - Release smoke automation now checks wheel install, npm tgz contents, static
219
- assets, and Tauri artifacts after exact-version artifact validation.
220
- - Brain archive, memory provenance, document ingestion, Review Center Run Now,
221
- local model runtime, root-shim smoke, and app-factory review wiring now have
222
- dedicated UX/backend hardening coverage for the 6.3 line.
223
- - Access-control helper closures are extracted from `app_factory.py` into a
224
- focused runtime module with direct unit coverage for role lookup, token
225
- extraction, user enforcement, admin enforcement, and public user projection.
226
- - Capture and Review Center copy now route through the shared Korean/English
227
- i18n map for tabs, placeholders, action labels, empty states, and feedback.
228
-
229
- Expected artifacts for 6.3.1 release must use exact filenames:
230
-
231
- - `dist/ltcai-6.3.1-py3-none-any.whl`
232
- - `dist/ltcai-6.3.1.tar.gz`
233
- - `ltcai-6.3.1.tgz`
234
- - `dist/ltcai-6.3.1.vsix`
235
- - `src-tauri/target/release/bundle/dmg/Lattice AI_6.3.1_aarch64.dmg`
203
+ The current development target is **6.5.0 Brain Experience Readiness**:
204
+
205
+ - Brain Home now gives normal users a readiness signal that explains whether
206
+ the Brain is waiting for its first memory, turning memories into topics, or
207
+ ready to explore as a connected map. That signal now comes from the backend
208
+ Memory Manager summary instead of a frontend-only estimate.
209
+ - The Living Brain surface now includes a persistent depth progress rail, so
210
+ users can understand and reverse the path from conversation to memory,
211
+ topics, relationships, and the full knowledge graph.
212
+ - Memory-save feedback now explicitly states that saved context is available
213
+ later with source-aware recall, closing the gap between "chat happened" and
214
+ "my Brain became more useful."
215
+ - The release keeps the 6.4.0 quality hardening intact and focuses on
216
+ end-user comprehension rather than adding a new storage or graph schema.
217
+ - The release remains local-first: no automatic web/email/calendar ingestion,
218
+ no package publishing, no production deployment, and no external reranker or
219
+ embedding API use without explicit opt-in.
220
+
221
+ Expected artifacts for 6.5.0 release must use exact filenames:
222
+
223
+ - `dist/ltcai-6.5.0-py3-none-any.whl`
224
+ - `dist/ltcai-6.5.0.tar.gz`
225
+ - `ltcai-6.5.0.tgz`
226
+ - `dist/ltcai-6.5.0.vsix`
227
+ - `src-tauri/target/release/bundle/dmg/Lattice AI_6.5.0_aarch64.dmg`
236
228
 
237
229
  Do not upload `dist/*`. Package registry publishing remains owner-run.
238
230
 
@@ -251,6 +243,8 @@ Do not upload `dist/*`. Package registry publishing remains owner-run.
251
243
 
252
244
  | Version | Theme |
253
245
  | --- | --- |
246
+ | 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 |
247
+ | 6.4.0 | Digital Brain Quality Hardening: workspace-scoped graph/search/memory reads and mutations, Brain quality primitives, structured context guardrails, and retrieval benchmark coverage |
254
248
  | 6.3.1 | Access Runtime / i18n Follow-up: app-factory access-control extraction, focused access runtime tests, and Capture/Review Center i18n coverage |
255
249
  | 6.3.0 | Product Hardening Completion: Brain archive/provenance/ingestion UX polish, Review Center Run Now contract hardening, local model runtime status, app-factory review wiring, shim smoke, i18n guard, and exact release artifacts |
256
250
  | 6.2.0 | Product Decomposition / Release Smoke Automation: App and ProductFlow feature extraction, legacy root shim shrink, model download consent UX, typed router contexts, localized smoke tests, and wheel/npm/static/Tauri release smoke |
package/docs/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  The top entry is the current release-preparation target. Older entries are
4
4
  historical and may describe behavior as it existed at that release.
5
5
 
6
+ ## [6.5.0] - 2026-06-18
7
+
8
+ > Brain Experience Readiness. Uses the 6.4.0 quality baseline to improve what
9
+ > normal users can understand: how alive the Brain is, where they are in the
10
+ > memory-to-graph journey, and what changed after a useful exchange is saved.
11
+
12
+ ### Added
13
+ - Brain Home readiness strip that classifies the current Brain as waiting for
14
+ its first memory, forming topics, or ready for map exploration.
15
+ - Backend-owned Brain readiness summary from `MemoryService`, exposed through
16
+ `/api/memory/manager` and `/api/memory/brain-quality`, so the UI no longer
17
+ has to infer Brain growth from local fragments.
18
+ - Persistent depth progress rail across the Living Brain surface so the
19
+ memory -> topics -> relationships -> graph journey is visible and reversible.
20
+ - Source-aware memory-save detail under chat feedback, making the recall loop
21
+ explicit after a conversation enriches the Brain.
22
+ - Visual regression assertions for Brain readiness, depth progress, and
23
+ source-aware memory-save feedback.
24
+
25
+ ### Changed
26
+ - Brain overview empty states now guide first-memory use without exposing graph
27
+ internals or administrator language.
28
+ - Brain readiness now uses the same memory, graph, relationship, and source
29
+ health signals that the Memory Manager reports, keeping product UX aligned
30
+ with backend Brain state.
31
+ - Package/runtime/static metadata is synchronized to 6.5.0; package publish and
32
+ deployment remain owner-run only.
33
+
34
+ ## [6.4.0] - 2026-06-17
35
+
36
+ > Digital Brain Quality Hardening. Tightens workspace-scoped Brain retrieval
37
+ > and memory mutation boundaries while adding non-destructive quality primitives
38
+ > for embeddings, retrieval, memory, graph validation, context assembly, and
39
+ > benchmarks.
40
+
41
+ ### Added
42
+ - `lattice_brain.quality` with embedding fallback labelling, drift/re-index
43
+ planning, BM25 lexical scoring, hybrid fusion, reranker fallback contracts,
44
+ memory candidate scoring/deduplication/conflict/retention helpers, graph edge
45
+ confidence/evidence metrics, structured context guardrails, and retrieval
46
+ benchmark metric calculation.
47
+ - `docs/v6.4/BRAIN_QUALITY_BASELINE.md` documenting the 6.4.0 Digital Brain
48
+ quality baseline, risk register, validation items, and deferred work.
49
+ - Refreshed README release evidence screenshots and walkthrough GIF under
50
+ `output/release/v6.4.0/`.
51
+ - Unit coverage for Brain quality primitives and workspace-scoped graph/search
52
+ and memory-manager mutation boundaries.
53
+
54
+ ### Changed
55
+ - `/api/graph*`, `/knowledge-graph/*`, and hybrid-search service paths now
56
+ preserve workspace scope across graph, node, neighborhood, relationship,
57
+ keyword, vector, graph, and hybrid retrieval.
58
+ - Memory Manager prune, compact, and clear operations now intersect requested
59
+ ids/kinds with the caller's scoped memory set.
60
+ - Memory Manager graph clear is blocked because the existing graph clear path
61
+ is not workspace-scoped.
62
+ - Package/runtime/static metadata is synchronized to 6.4.0; package publish and
63
+ deployment remain owner-run only.
64
+
6
65
  ## [6.3.1] - 2026-06-17
7
66
 
8
67
  > Access Runtime / i18n Follow-up. Closes the next app-factory decomposition
@@ -445,6 +445,7 @@ export const latticeApi = {
445
445
  },
446
446
  browserReadUrl: (url: string) => post("/api/browser/read-url", { url }, {}),
447
447
  memoryManager: () => get("/api/memory/manager", { sources: [], tiers: [], usage: {} }),
448
+ memoryBrainQuality: () => get("/api/memory/brain-quality", {}),
448
449
  memoryRecall: (query: string, limit = 20) => post("/api/memory/recall", { query, limit }, { matches: [] }),
449
450
  memoryCompact: () => post("/api/memory/compact", {}, {}),
450
451
  memoryRebuild: () => post("/api/memory/rebuild", { target: "vector" }, {}),
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
- import { ShieldCheck } from "lucide-react";
2
+ import { DatabaseZap, ShieldCheck } from "lucide-react";
3
3
  import { LanguageSwitcher } from "@/components/LanguageSwitcher";
4
4
  import { t, type Language } from "@/i18n";
5
- import type { BrainDepth, KnowledgeConcept, MemoryFragment, Message } from "./types";
5
+ import type { BrainDepth, BrainReadiness, KnowledgeConcept, MemoryFragment, Message } from "./types";
6
6
  import { BrainCarePanel } from "./BrainCarePanel";
7
7
  import { BrainComposer } from "./BrainComposer";
8
8
  import { BrainOverviewPanel } from "./BrainOverviewPanel";
@@ -20,6 +20,7 @@ export function BrainConversation({
20
20
  streamRef,
21
21
  memories,
22
22
  concepts,
23
+ readiness,
23
24
  onOpenDepth,
24
25
  onDraftChange,
25
26
  onImageDataChange,
@@ -37,6 +38,7 @@ export function BrainConversation({
37
38
  streamRef: React.RefObject<HTMLDivElement | null>;
38
39
  memories: MemoryFragment[];
39
40
  concepts: KnowledgeConcept[];
41
+ readiness: BrainReadiness;
40
42
  onOpenDepth: (depth: BrainDepth) => void;
41
43
  onDraftChange: (value: string) => void;
42
44
  onImageDataChange: (value: string | null) => void;
@@ -66,6 +68,7 @@ export function BrainConversation({
66
68
  <BrainOverviewPanel
67
69
  memories={memories}
68
70
  concepts={concepts}
71
+ readiness={readiness}
69
72
  onOpenDepth={onOpenDepth}
70
73
  />
71
74
  {messages.length === 0 ? (
@@ -79,7 +82,13 @@ export function BrainConversation({
79
82
  )}
80
83
  </div>
81
84
 
82
- {memoryFeedback ? <div className="brain-save-feedback" role="status">{memoryFeedback}</div> : null}
85
+ {memoryFeedback ? (
86
+ <div className="brain-save-feedback" role="status">
87
+ <DatabaseZap className="h-3.5 w-3.5" />
88
+ <span>{memoryFeedback}</span>
89
+ <small>{t(language, "brain.saved.detail")}</small>
90
+ </div>
91
+ ) : null}
83
92
 
84
93
  <BrainCarePanel language={language} />
85
94
 
@@ -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 { 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
 
@@ -46,6 +46,10 @@ export function BrainHome({
46
46
  () => graphModel.nodes.slice(0, 10),
47
47
  [graphModel.nodes],
48
48
  );
49
+ const brainReadiness = React.useMemo(
50
+ () => buildBrainReadiness(memoriesQ.data?.data, memoryFragments.length, knowledgeConcepts.length),
51
+ [knowledgeConcepts.length, memoriesQ.data, memoryFragments.length],
52
+ );
49
53
  const relationshipThreads = React.useMemo(
50
54
  () => graphModel.edges.slice(0, 10),
51
55
  [graphModel.edges],
@@ -186,6 +190,21 @@ export function BrainHome({
186
190
  <button type="button" className={explorationDepth === 5 ? "is-active" : ""} onClick={() => jumpToDepth(5)}>{t(language, "brain.view.graph")}</button>
187
191
  </div>
188
192
 
193
+ <div className="brain-depth-rail" aria-label={t(language, "brain.depthRail.aria")}>
194
+ {DEPTHS.map((depth) => (
195
+ <button
196
+ key={depth.level}
197
+ type="button"
198
+ className={depth.level <= explorationDepth ? "is-revealed" : ""}
199
+ aria-current={depth.level === explorationDepth ? "step" : undefined}
200
+ onClick={() => jumpToDepth(depth.level)}
201
+ >
202
+ <span>{depth.level}</span>
203
+ <strong>{t(language, `brain.depth.${depth.level}`)}</strong>
204
+ </button>
205
+ ))}
206
+ </div>
207
+
189
208
  <div className="brain-field-layer" aria-hidden={explorationDepth < 2}>
190
209
  <DepthEmergence
191
210
  depth={explorationDepth}
@@ -222,6 +241,7 @@ export function BrainHome({
222
241
  streamRef={streamRef}
223
242
  memories={memoryFragments}
224
243
  concepts={knowledgeConcepts}
244
+ readiness={brainReadiness}
225
245
  onOpenDepth={jumpToDepth}
226
246
  onDraftChange={setDraft}
227
247
  onImageDataChange={setImageData}
@@ -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) return <div className="memory-fragment is-empty">{t(language, "brain.memory.empty")}</div>;
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,15 +1,17 @@
1
1
  import * as React from "react";
2
2
  import { t } from "@/i18n";
3
3
  import { useAppStore } from "@/store/appStore";
4
- import type { BrainDepth, KnowledgeConcept, MemoryFragment } from "./types";
4
+ import type { BrainDepth, BrainReadiness, KnowledgeConcept, MemoryFragment } from "./types";
5
5
 
6
6
  export function BrainOverviewPanel({
7
7
  memories,
8
8
  concepts,
9
+ readiness,
9
10
  onOpenDepth,
10
11
  }: {
11
12
  memories: MemoryFragment[];
12
13
  concepts: KnowledgeConcept[];
14
+ readiness: BrainReadiness;
13
15
  onOpenDepth: (depth: BrainDepth) => void;
14
16
  }) {
15
17
  const language = useAppStore((state) => state.language);
@@ -46,6 +48,18 @@ export function BrainOverviewPanel({
46
48
  onOpen={() => onOpenDepth(3)}
47
49
  />
48
50
  </div>
51
+ <div className="brain-readiness-strip" data-state={readiness.state}>
52
+ <div>
53
+ <span>{t(language, "brain.readiness.kicker")}</span>
54
+ <strong>{t(language, readiness.titleKey)}</strong>
55
+ </div>
56
+ <div className="brain-readiness-meter" aria-label={t(language, "brain.readiness.aria")}>
57
+ <i style={{ width: `${readiness.score}%` }} />
58
+ </div>
59
+ <button type="button" onClick={() => onOpenDepth(readiness.depth)}>
60
+ {t(language, readiness.actionKey)}
61
+ </button>
62
+ </div>
49
63
  </section>
50
64
  );
51
65
  }
@@ -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, 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,32 @@ 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
+
63
89
  function uniqueById<T extends { id: string }>(items: T[]) {
64
90
  const seen = new Set<string>();
65
91
  return items.filter((item) => {
@@ -69,6 +95,51 @@ function uniqueById<T extends { id: string }>(items: T[]) {
69
95
  });
70
96
  }
71
97
 
98
+ function fallbackBrainReadiness(memoryCount: number, conceptCount: number): BrainReadiness {
99
+ const score = Math.min(100, Math.round(memoryCount * 12 + conceptCount * 10));
100
+ if (memoryCount < 1) {
101
+ return {
102
+ score: Math.max(12, score),
103
+ state: "quiet",
104
+ depth: 2,
105
+ titleKey: "brain.readiness.quiet",
106
+ actionKey: "brain.readiness.start",
107
+ source: "frontend_fallback",
108
+ signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
109
+ };
110
+ }
111
+ if (conceptCount < 3) {
112
+ return {
113
+ score: Math.max(38, score),
114
+ state: "forming",
115
+ depth: 3,
116
+ titleKey: "brain.readiness.forming",
117
+ actionKey: "brain.readiness.grow",
118
+ source: "frontend_fallback",
119
+ signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
120
+ };
121
+ }
122
+ return {
123
+ score: Math.max(72, score),
124
+ state: "alive",
125
+ depth: 5,
126
+ titleKey: "brain.readiness.alive",
127
+ actionKey: "brain.readiness.map",
128
+ source: "frontend_fallback",
129
+ signals: { memoryCount, conceptCount, relationshipCount: 0, healthySources: 0 },
130
+ };
131
+ }
132
+
133
+ function readinessActionKey(state: "quiet" | "forming" | "alive") {
134
+ if (state === "quiet") return "brain.readiness.start";
135
+ if (state === "forming") return "brain.readiness.grow";
136
+ return "brain.readiness.map";
137
+ }
138
+
139
+ function isBrainDepth(value: number): value is BrainDepth {
140
+ return value >= 1 && value <= 5 && Number.isInteger(value);
141
+ }
142
+
72
143
  export function isRecord(value: unknown): value is ApiRecord {
73
144
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
74
145
  }
@@ -35,6 +35,21 @@ 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
+
38
53
  export const DEPTHS: Array<{ level: BrainDepth; labelKey: string; state: BrainState }> = [
39
54
  { level: 1, labelKey: "brain.depthLabel.1", state: "idle" },
40
55
  { level: 2, labelKey: "brain.depthLabel.2", state: "recalling" },
@@ -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": "시작 프롬프트",
@@ -57,6 +58,7 @@ export const COPY: Record<Language, TextMap> = {
57
58
  "brain.imageAttached": "이미지 첨부됨",
58
59
  "brain.send": "보내기",
59
60
  "brain.saved": "기억에 저장됨 · 연결된 주제 {topics}개 · 관련 기억 {memories}개",
61
+ "brain.saved.detail": "출처와 함께 나중에 다시 불러올 수 있습니다.",
60
62
  "brain.recalled": "기억을 다시 꺼냈습니다: {title}",
61
63
  "brain.overview.kicker": "Brain 한눈에 보기",
62
64
  "brain.overview.title": "기억과 주제를 바로 확인하세요.",
@@ -67,6 +69,15 @@ export const COPY: Record<Language, TextMap> = {
67
69
  "brain.overview.olderEmpty": "대화가 쌓이면 과거 기억이 보입니다.",
68
70
  "brain.overview.topics": "주요 주제",
69
71
  "brain.overview.topicsEmpty": "주제가 형성되는 중입니다.",
72
+ "brain.readiness.kicker": "Brain 준비도",
73
+ "brain.readiness.aria": "Brain 준비도 점수",
74
+ "brain.readiness.quiet": "첫 기억을 기다리고 있습니다.",
75
+ "brain.readiness.forming": "기억이 주제로 자라는 중입니다.",
76
+ "brain.readiness.alive": "기억과 관계가 연결되어 있습니다.",
77
+ "brain.readiness.start": "첫 기억 보기",
78
+ "brain.readiness.grow": "주제 키우기",
79
+ "brain.readiness.map": "지도 보기",
80
+ "brain.memory.empty.kicker": "첫 기억",
70
81
  "brain.memory.empty": "기억이 조용합니다.",
71
82
  "brain.knowledge.empty": "지식이 형성되는 중입니다.",
72
83
  "brain.graph.empty": "아직 맞는 지식이 없습니다.",
@@ -352,6 +363,7 @@ export const COPY: Record<Language, TextMap> = {
352
363
  "brain.aria.home": "Lattice Brain",
353
364
  "brain.aria.exploration": "Brain exploration",
354
365
  "brain.aria.quickViews": "Brain quick views",
366
+ "brain.depthRail.aria": "Brain depth progress",
355
367
  "brain.aria.conversation": "Conversation",
356
368
  "brain.aria.ownership": "Brain ownership guarantees",
357
369
  "brain.aria.starterPrompts": "Starter prompts",
@@ -378,6 +390,7 @@ export const COPY: Record<Language, TextMap> = {
378
390
  "brain.imageAttached": "Image attached",
379
391
  "brain.send": "Send",
380
392
  "brain.saved": "Saved to memory · {topics} linked topics · {memories} related memories",
393
+ "brain.saved.detail": "Available later with source-aware recall.",
381
394
  "brain.recalled": "Recalled this memory: {title}",
382
395
  "brain.overview.kicker": "Brain at a glance",
383
396
  "brain.overview.title": "See memories and topics immediately.",
@@ -388,6 +401,15 @@ export const COPY: Record<Language, TextMap> = {
388
401
  "brain.overview.olderEmpty": "Earlier memories appear as conversations accumulate.",
389
402
  "brain.overview.topics": "Main topics",
390
403
  "brain.overview.topicsEmpty": "Topics are still forming.",
404
+ "brain.readiness.kicker": "Brain readiness",
405
+ "brain.readiness.aria": "Brain readiness score",
406
+ "brain.readiness.quiet": "Waiting for the first memory.",
407
+ "brain.readiness.forming": "Memories are becoming topics.",
408
+ "brain.readiness.alive": "Memories and relationships are connected.",
409
+ "brain.readiness.start": "View first memory",
410
+ "brain.readiness.grow": "Grow topics",
411
+ "brain.readiness.map": "Open map",
412
+ "brain.memory.empty.kicker": "First memory",
391
413
  "brain.memory.empty": "Memory is quiet",
392
414
  "brain.knowledge.empty": "Knowledge is forming",
393
415
  "brain.graph.empty": "No matching knowledge yet",