ltcai 6.0.0 → 6.2.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 (54) hide show
  1. package/README.md +33 -35
  2. package/docs/CHANGELOG.md +68 -0
  3. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +1 -1
  4. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +1 -1
  5. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +1 -1
  6. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +1 -1
  7. package/frontend/src/App.tsx +3 -1281
  8. package/frontend/src/components/LanguageSwitcher.tsx +23 -0
  9. package/frontend/src/components/ProductFlow.tsx +32 -662
  10. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +688 -0
  11. package/frontend/src/features/admin/AdminConsole.tsx +294 -0
  12. package/frontend/src/features/brain/BrainHome.tsx +999 -0
  13. package/frontend/src/features/brain/brainData.ts +98 -0
  14. package/frontend/src/features/brain/graphLayout.ts +26 -0
  15. package/frontend/src/features/brain/types.ts +44 -0
  16. package/frontend/src/features/review/ReviewCard.tsx +15 -10
  17. package/frontend/src/i18n.ts +208 -0
  18. package/frontend/src/styles.css +240 -0
  19. package/lattice_brain/__init__.py +1 -1
  20. package/lattice_brain/runtime/multi_agent.py +1 -1
  21. package/latticeai/__init__.py +1 -1
  22. package/latticeai/api/chat.py +52 -33
  23. package/latticeai/api/tools.py +50 -23
  24. package/latticeai/app_factory.py +65 -47
  25. package/latticeai/cli/__init__.py +1 -0
  26. package/latticeai/cli/entrypoint.py +283 -0
  27. package/latticeai/cli/runtime.py +37 -0
  28. package/latticeai/core/marketplace.py +1 -1
  29. package/latticeai/core/workspace_os.py +1 -1
  30. package/latticeai/integrations/__init__.py +0 -0
  31. package/latticeai/integrations/telegram_bot.py +1009 -0
  32. package/latticeai/runtime/lifespan_runtime.py +1 -1
  33. package/latticeai/runtime/platform_runtime_wiring.py +87 -0
  34. package/latticeai/runtime/router_registration.py +49 -30
  35. package/latticeai/services/app_context.py +1 -0
  36. package/latticeai/services/p_reinforce.py +258 -0
  37. package/latticeai/services/router_context.py +52 -0
  38. package/latticeai/services/tool_dispatch.py +82 -25
  39. package/ltcai_cli.py +7 -305
  40. package/p_reinforce.py +4 -255
  41. package/package.json +2 -1
  42. package/scripts/release_smoke.py +133 -0
  43. package/scripts/wheel_smoke.py +4 -0
  44. package/src-tauri/Cargo.lock +1 -1
  45. package/src-tauri/Cargo.toml +1 -1
  46. package/src-tauri/tauri.conf.json +1 -1
  47. package/static/app/asset-manifest.json +5 -5
  48. package/static/app/assets/{index-xRn29gI8.css → index-B2-1Gm0q.css} +1 -1
  49. package/static/app/assets/index-D91Rz5--.js +16 -0
  50. package/static/app/assets/index-D91Rz5--.js.map +1 -0
  51. package/static/app/index.html +2 -2
  52. package/telegram_bot.py +9 -1008
  53. package/static/app/assets/index-D2zafMYb.js +0 -16
  54. package/static/app/assets/index-D2zafMYb.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { asArray } from "@/lib/utils";
2
+ import type { ApiRecord, KnowledgeConcept, KnowledgeGraphModel, MemoryFragment, RelationshipThread } from "./types";
3
+ import { clamp } from "./graphLayout";
4
+
5
+ export function buildMemoryFragments(memoryData: unknown, historyData: unknown): MemoryFragment[] {
6
+ const memory = isRecord(memoryData) ? memoryData : {};
7
+ const sourceRows = asArray<ApiRecord>(memory.sources).length
8
+ ? asArray<ApiRecord>(memory.sources)
9
+ : asArray<ApiRecord>(memory.tiers);
10
+ const sourceFragments = sourceRows.map((item, index) => ({
11
+ id: textValue(item, ["id", "source", "label"], `memory-${index}`),
12
+ title: textValue(item, ["title", "label", "source", "path", "name"], "Workspace memory"),
13
+ kind: titleValue(item, ["type", "source_type", "kind", "health"], "Memory"),
14
+ }));
15
+ const conversationFragments = asArray<ApiRecord>(historyData).map((item, index) => ({
16
+ id: textValue(item, ["id", "conversation_id"], `conversation-${index}`),
17
+ title: textValue(item, ["title", "summary", "id"], "Conversation"),
18
+ kind: "Conversation",
19
+ }));
20
+
21
+ return uniqueById([...sourceFragments, ...conversationFragments]).slice(0, 10);
22
+ }
23
+
24
+ export function parseKnowledgeGraph(data: unknown): KnowledgeGraphModel {
25
+ const graph = isRecord(data) ? data : {};
26
+ const rawNodes = asArray<ApiRecord>(graph.nodes);
27
+ const rawEdges = asArray<ApiRecord>(graph.edges);
28
+ const nodes = rawNodes.flatMap((node): KnowledgeConcept[] => {
29
+ const id = textValue(node, ["id", "node_id", "title", "label"]);
30
+ if (!id) return [];
31
+ const metadata = isRecord(node.metadata) ? node.metadata : {};
32
+ const type = titleValue(node, ["type", "kind", "category"], "Concept");
33
+ const label = textValue(node, ["title", "label", "name"], id.replace(/^[^:]+:/, ""));
34
+ const summary = textValue(node, ["summary", "description", "snippet"]) || textValue(metadata, ["summary", "description", "relative_path", "filename"]);
35
+ const importance = clamp(numberValue(node, ["importance_norm", "importance", "score"]) || 0.5, 0.08, 1);
36
+ return [{ id, label, type, summary, importance }];
37
+ }).sort((left, right) => right.importance - left.importance);
38
+ const ids = new Set(nodes.map((node) => node.id));
39
+ const edges = rawEdges.flatMap((edge, index): RelationshipThread[] => {
40
+ const source = textValue(edge, ["from", "source", "source_id"]);
41
+ const target = textValue(edge, ["to", "target", "target_id"]);
42
+ if (!source || !target || !ids.has(source) || !ids.has(target)) return [];
43
+ return [{
44
+ id: textValue(edge, ["id"], `edge-${index}`),
45
+ source,
46
+ target,
47
+ label: titleValue(edge, ["type", "label", "relationship"], "Relates"),
48
+ weight: numberValue(edge, ["weight", "score", "confidence"]) || 1,
49
+ }];
50
+ });
51
+ return { nodes, edges };
52
+ }
53
+
54
+ export function currentModelName(data: unknown) {
55
+ const record = isRecord(data) ? data : {};
56
+ const current = textValue(record, ["current", "current_model", "local_model"]);
57
+ if (current) return current;
58
+ const loaded = asArray<ApiRecord>(record.loaded || record.loaded_models);
59
+ const firstLoaded = loaded.find((item) => item.id || item.name || item.model_id);
60
+ return firstLoaded ? textValue(firstLoaded, ["name", "id", "model_id"], "local mind") : "local mind";
61
+ }
62
+
63
+ function uniqueById<T extends { id: string }>(items: T[]) {
64
+ const seen = new Set<string>();
65
+ return items.filter((item) => {
66
+ if (seen.has(item.id)) return false;
67
+ seen.add(item.id);
68
+ return true;
69
+ });
70
+ }
71
+
72
+ export function isRecord(value: unknown): value is ApiRecord {
73
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
74
+ }
75
+
76
+ export function textValue(record: ApiRecord, keys: string[], fallback = "") {
77
+ for (const key of keys) {
78
+ const value = record[key];
79
+ if (typeof value === "string" && value.trim()) return value;
80
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
81
+ }
82
+ return fallback;
83
+ }
84
+
85
+ function titleValue(record: ApiRecord, keys: string[], fallback = "") {
86
+ const value = textValue(record, keys, fallback);
87
+ return value
88
+ .replace(/[_-]+/g, " ")
89
+ .replace(/\b\w/g, (character) => character.toUpperCase());
90
+ }
91
+
92
+ function numberValue(record: ApiRecord, keys: string[]) {
93
+ for (const key of keys) {
94
+ const value = Number(record[key]);
95
+ if (Number.isFinite(value)) return value;
96
+ }
97
+ return 0;
98
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import type { KnowledgeConcept } from "./types";
3
+
4
+ export function layoutGraphNodes(nodes: KnowledgeConcept[], radiusX: number, radiusY: number) {
5
+ return nodes.map((node, index) => {
6
+ const point = polarPoint(index, nodes.length, radiusX, radiusY, -88);
7
+ return { node, x: point.x, y: point.y };
8
+ });
9
+ }
10
+
11
+ export function polarPoint(index: number, total: number, radiusX: number, radiusY: number, offsetDegrees = -90) {
12
+ const count = Math.max(total, 1);
13
+ const angle = ((360 / count) * index + offsetDegrees) * Math.PI / 180;
14
+ return {
15
+ x: 50 + Math.cos(angle) * radiusX,
16
+ y: 50 + Math.sin(angle) * radiusY,
17
+ };
18
+ }
19
+
20
+ export function layerStyle(values: Record<string, string>) {
21
+ return values as React.CSSProperties;
22
+ }
23
+
24
+ export function clamp(value: number, min: number, max: number) {
25
+ return Math.max(min, Math.min(max, value));
26
+ }
@@ -0,0 +1,44 @@
1
+ import type { BrainState } from "@/components/LivingBrain";
2
+
3
+ export type ApiRecord = Record<string, unknown>;
4
+ export type BrainDepth = 1 | 2 | 3 | 4 | 5;
5
+
6
+ export type Message = {
7
+ role: "user" | "assistant";
8
+ content: string;
9
+ };
10
+
11
+ export type MemoryFragment = {
12
+ id: string;
13
+ title: string;
14
+ kind: string;
15
+ };
16
+
17
+ export type KnowledgeConcept = {
18
+ id: string;
19
+ label: string;
20
+ type: string;
21
+ summary: string;
22
+ importance: number;
23
+ };
24
+
25
+ export type RelationshipThread = {
26
+ id: string;
27
+ source: string;
28
+ target: string;
29
+ label: string;
30
+ weight: number;
31
+ };
32
+
33
+ export type KnowledgeGraphModel = {
34
+ nodes: KnowledgeConcept[];
35
+ edges: RelationshipThread[];
36
+ };
37
+
38
+ export const DEPTHS: Array<{ level: BrainDepth; label: string; state: BrainState }> = [
39
+ { level: 1, label: "Living Brain", state: "idle" },
40
+ { level: 2, label: "Memory Layer", state: "recalling" },
41
+ { level: 3, label: "Knowledge Layer", state: "synthesizing" },
42
+ { level: 4, label: "Relationship Layer", state: "planning" },
43
+ { level: 5, label: "Knowledge Graph", state: "synthesizing" },
44
+ ];
@@ -71,16 +71,21 @@ export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
71
71
  ) : null}
72
72
 
73
73
  {actionable ? (
74
- <div className="mt-4 flex flex-wrap gap-2">
75
- <ActionButton
76
- label="Run now"
77
- successLabel={hadRun ? "Regenerated" : "Executed"}
78
- action={() => onAction(item, "run_now", hadRun)}
79
- invalidate={[]}
80
- />
81
- <ActionButton label="Approve" action={() => onAction(item, "approve")} invalidate={[]} />
82
- {!snoozed ? <ActionButton label="Snooze 1 day" action={() => onAction(item, "snooze")} invalidate={[]} /> : null}
83
- <ActionButton label="Dismiss" action={() => onAction(item, "dismiss")} invalidate={[]} variant="destructive" />
74
+ <div className="mt-4 grid gap-2">
75
+ <p className="text-xs leading-5 text-muted-foreground">
76
+ Run now previews the action without approving it. Approve or dismiss when the result looks right.
77
+ </p>
78
+ <div className="flex flex-wrap gap-2" aria-label="Review actions">
79
+ <ActionButton
80
+ label="Run now"
81
+ successLabel={hadRun ? "Regenerated" : "Executed"}
82
+ action={() => onAction(item, "run_now", hadRun)}
83
+ invalidate={[]}
84
+ />
85
+ <ActionButton label="Approve" action={() => onAction(item, "approve")} invalidate={[]} />
86
+ {!snoozed ? <ActionButton label="Snooze 1 day" action={() => onAction(item, "snooze")} invalidate={[]} /> : null}
87
+ <ActionButton label="Dismiss" action={() => onAction(item, "dismiss")} invalidate={[]} variant="destructive" />
88
+ </div>
84
89
  </div>
85
90
  ) : null}
86
91
  {feedback ? (
@@ -31,6 +31,10 @@ export const COPY: Record<Language, TextMap> = {
31
31
  "brain.empty.kicker": "내 오래가는 기억",
32
32
  "brain.empty.title": "잊으면 안 되는 맥락부터 말해 주세요.",
33
33
  "brain.empty.body": "문서, 대화, 프로젝트, 결정이 이 컴퓨터의 Brain에 쌓이고 나중에 주제와 관계로 다시 보입니다.",
34
+ "brain.empty.trail.label": "첫 Brain 흐름",
35
+ "brain.empty.trail.save": "1. 첫 기억 저장",
36
+ "brain.empty.trail.recall": "2. Brain Home에서 확인",
37
+ "brain.empty.trail.backup": "3. 백업으로 소유",
34
38
  "brain.prompt.remember": "이 프로젝트 목표를 기억해줘: ",
35
39
  "brain.prompt.know": "이 문서를 내 Brain에 넣고 요약해줘: ",
36
40
  "brain.prompt.plan": "지난 결정들을 나중에 찾을 수 있게 정리해줘: ",
@@ -75,6 +79,96 @@ export const COPY: Record<Language, TextMap> = {
75
79
  "admin.kicker": "분리된 관리자 작업공간",
76
80
  "admin.title": "Admin Console",
77
81
  "admin.body": "사용자, 로그, 보안, Brain 상태는 일반 사용자 화면과 분리됩니다.",
82
+ "admin.overview": "관리 개요",
83
+ "admin.metric.users": "사용자",
84
+ "admin.metric.logs": "최근 로그",
85
+ "admin.metric.security": "보안",
86
+ "admin.metric.index": "Brain 인덱스",
87
+ "admin.status.ready": "준비됨",
88
+ "admin.status.unavailable": "사용 불가",
89
+ "admin.status.indexed": "인덱싱됨",
90
+ "admin.status.unknown": "알 수 없음",
91
+ "admin.panel.users": "사용자 디렉터리",
92
+ "admin.panel.people": "사람",
93
+ "admin.panel.roles": "역할 권한",
94
+ "admin.panel.access": "접근",
95
+ "admin.panel.logs": "활동 로그",
96
+ "admin.panel.audit": "감사",
97
+ "admin.panel.securityEvents": "보안 이벤트",
98
+ "admin.panel.protection": "보호",
99
+ "admin.panel.brainOps": "Brain 운영",
100
+ "admin.panel.maintenance": "유지보수",
101
+ "admin.empty.users": "관리 API가 보고한 사용자가 없습니다.",
102
+ "admin.empty.roles": "보고된 역할 매트릭스가 없습니다.",
103
+ "admin.empty.audit": "최근 감사 이벤트가 없습니다.",
104
+ "admin.empty.security": "보고된 보안 이벤트가 없습니다.",
105
+ "admin.fallback.localUser": "로컬 사용자",
106
+ "admin.fallback.member": "멤버",
107
+ "admin.fallback.role": "역할",
108
+ "admin.fallback.noCaps": "권한 없음",
109
+ "admin.filters.label": "감사 로그 필터",
110
+ "admin.filters.search": "로그 검색",
111
+ "admin.filters.searchAria": "감사 로그 검색",
112
+ "admin.filters.severityAria": "심각도별 필터",
113
+ "admin.filters.all": "모든 심각도",
114
+ "admin.filters.informational": "정보",
115
+ "admin.filters.notice": "알림",
116
+ "admin.filters.warning": "경고",
117
+ "admin.filters.high": "높음",
118
+ "admin.filters.matched": "{count}개 일치",
119
+ "admin.brain.summaryFallback": "로컬 Brain 서비스는 사용자 대화와 분리되어 있습니다.",
120
+ "admin.brain.rebuilding": "재구성 중",
121
+ "admin.brain.rebuild": "인덱스 재구성",
122
+ "admin.policy.fallback": "정책",
123
+ "admin.policy.quiet": "정책 API 대기 중",
124
+ "admin.retention.days": "{days}일 보관",
125
+ "admin.retention.detail": "{events}개 보관 · {candidates}개 내보내기/정리 검토 가능",
126
+ "admin.log.event": "이벤트",
127
+ "admin.log.system": "시스템",
128
+ "admin.log.recently": "최근",
129
+ "admin.source.loading": "불러오는 중",
130
+ "admin.source.live": "라이브",
131
+ "admin.index.ready": "인덱스 상태 준비됨",
132
+ "flow.analysis.fact.computer": "컴퓨터",
133
+ "flow.analysis.fact.memory": "메모리",
134
+ "flow.analysis.fact.graphics": "그래픽",
135
+ "flow.analysis.fact.support": "로컬 지원",
136
+ "flow.analysis.fact.models": "모델",
137
+ "flow.analysis.checking": "확인 중",
138
+ "flow.analysis.fact.computerDetail": "운영체제와 칩",
139
+ "flow.analysis.fact.memoryDetail": "로컬 사고를 위한 여유 공간",
140
+ "flow.analysis.fact.graphicsDetail": "로컬 가속 지원",
141
+ "flow.analysis.fact.supportDetail": "설치된 모델 도우미",
142
+ "flow.analysis.fact.modelsDetail": "설치된 로컬 Brain",
143
+ "flow.analysis.apple": "Apple Silicon Mac",
144
+ "flow.analysis.detected": "감지됨",
145
+ "flow.analysis.localReady": "로컬 가속 준비됨",
146
+ "flow.analysis.standardLocal": "표준 로컬 모드",
147
+ "flow.analysis.supportReady": "준비됨",
148
+ "flow.analysis.supportInstall": "준비 예정",
149
+ "flow.analysis.modelsInstalled": "{count}개 이미 설치됨",
150
+ "flow.analysis.noModels": "아직 설치된 모델 없음",
151
+ "flow.analysis.readyDetail": "로컬 Digital Brain 사용 준비 완료",
152
+ "flow.analysis.memoryReadyDetail": "추천 모델에 충분한 컨텍스트",
153
+ "flow.analysis.graphicsReadyDetail": "Lattice가 가장 좋은 경로를 선택합니다.",
154
+ "flow.analysis.supportReadyDetail": "설치된 모델 도우미가 감지되었습니다.",
155
+ "flow.analysis.supportInstallDetail": "Lattice가 필요한 항목을 추가합니다.",
156
+ "flow.analysis.modelsReadyDetail": "즉시 로드할 수 있는 모델이 있습니다.",
157
+ "flow.analysis.modelsInstallDetail": "Lattice가 첫 설치를 안내합니다.",
158
+ "flow.recommend.reason.best": "최적 경험",
159
+ "flow.recommend.reason.faster": "빠른 선택",
160
+ "flow.recommend.reason.advanced": "고급 선택",
161
+ "flow.recommend.reason.default": "추천",
162
+ "flow.recommend.sizeReady": "준비됨",
163
+ "flow.recommend.fallbackName": "추천 Brain",
164
+ "flow.install.stage.download": "모델 파일을 받는 중입니다.",
165
+ "flow.install.stage.validate": "Brain이 응답할 수 있는지 확인 중입니다.",
166
+ "flow.install.stage.load": "Brain을 불러오는 중입니다.",
167
+ "flow.install.stage.error": "확인이 필요한 일이 있습니다.",
168
+ "flow.install.step.install": "준비",
169
+ "flow.install.step.download": "다운로드",
170
+ "flow.install.step.validate": "확인",
171
+ "flow.install.step.load": "로드",
78
172
  "flow.shell": "내 로컬 AI 브레인 만들기",
79
173
  "flow.login.title": "내 AI 브레인의 주인을 정합니다.",
80
174
  "flow.login.body": "Lattice AI는 내 지식과 맥락을 이 컴퓨터에 보관하는 로컬 우선 AI 브레인입니다. 모델은 바꿀 수 있고, 외부 전송은 사용자가 선택할 때만 시작됩니다.",
@@ -107,6 +201,7 @@ export const COPY: Record<Language, TextMap> = {
107
201
  "flow.recommend.primary": "추천대로 시작하기",
108
202
  "flow.recommend.unsupported": "이 컴퓨터에서 추가 확인이 필요합니다",
109
203
  "flow.recommend.back": "뒤로",
204
+ "flow.recommend.skip": "모델 없이 Brain 열기",
110
205
  "flow.recommend.hint": "잘 모르겠다면 추천대로 시작하면 됩니다.",
111
206
  "flow.install.title": "모델을 설치하고 시작합니다.",
112
207
  "flow.install.body": "이 모델이 Brain의 로컬 목소리가 됩니다. 인터넷은 다운로드할 때만 필요하고, 실행과 기억은 이 컴퓨터에서 유지됩니다.",
@@ -119,7 +214,16 @@ export const COPY: Record<Language, TextMap> = {
119
214
  "flow.install.start": "다운로드하고 시작하기",
120
215
  "flow.install.busy": "모델 준비 중...",
121
216
  "flow.install.enter": "Brain으로 들어가기",
217
+ "flow.install.later": "나중에 하기",
122
218
  "flow.install.local": "모델 다운로드와 외부 통신은 사용자가 시작할 때만 진행됩니다.",
219
+ "flow.consent.title": "다운로드 전 확인",
220
+ "flow.consent.body": "시작하면 아래 모델 파일을 외부 저장소에서 받아 이 컴퓨터에 저장합니다.",
221
+ "flow.consent.ready": "이미 준비된 모델이거나, 선택하기 전에는 외부 접속을 시작하지 않습니다.",
222
+ "flow.consent.size": "다운로드 크기",
223
+ "flow.consent.sizeUnknown": "런타임 확인 후 표시",
224
+ "flow.consent.location": "저장 위치",
225
+ "flow.consent.external": "외부 접속 대상",
226
+ "flow.consent.externalNone": "선택 전 외부 접속 없음",
123
227
  },
124
228
  en: {
125
229
  "language.label": "Language",
@@ -144,6 +248,10 @@ export const COPY: Record<Language, TextMap> = {
144
248
  "brain.empty.kicker": "Durable memory",
145
249
  "brain.empty.title": "Start with context that should not be forgotten.",
146
250
  "brain.empty.body": "Documents, conversations, projects, and decisions accumulate in the Brain on this computer, then reappear as topics and relationships.",
251
+ "brain.empty.trail.label": "First Brain flow",
252
+ "brain.empty.trail.save": "1. Save first memory",
253
+ "brain.empty.trail.recall": "2. See it in Brain Home",
254
+ "brain.empty.trail.backup": "3. Own it with backup",
147
255
  "brain.prompt.remember": "Remember this project goal: ",
148
256
  "brain.prompt.know": "Put this document into my Brain and summarize it: ",
149
257
  "brain.prompt.plan": "Organize past decisions so I can find them later: ",
@@ -188,6 +296,96 @@ export const COPY: Record<Language, TextMap> = {
188
296
  "admin.kicker": "Separate admin workspace",
189
297
  "admin.title": "Admin Console",
190
298
  "admin.body": "Users, logs, security, and Brain health stay out of the normal user experience.",
299
+ "admin.overview": "Admin overview",
300
+ "admin.metric.users": "Users",
301
+ "admin.metric.logs": "Recent logs",
302
+ "admin.metric.security": "Security",
303
+ "admin.metric.index": "Brain index",
304
+ "admin.status.ready": "Ready",
305
+ "admin.status.unavailable": "Unavailable",
306
+ "admin.status.indexed": "Indexed",
307
+ "admin.status.unknown": "Unknown",
308
+ "admin.panel.users": "User Directory",
309
+ "admin.panel.people": "People",
310
+ "admin.panel.roles": "Role Permissions",
311
+ "admin.panel.access": "Access",
312
+ "admin.panel.logs": "Activity Logs",
313
+ "admin.panel.audit": "Audit",
314
+ "admin.panel.securityEvents": "Security Events",
315
+ "admin.panel.protection": "Protection",
316
+ "admin.panel.brainOps": "Brain Operations",
317
+ "admin.panel.maintenance": "Maintenance",
318
+ "admin.empty.users": "No users reported by the admin API.",
319
+ "admin.empty.roles": "No role matrix reported.",
320
+ "admin.empty.audit": "No recent audit events.",
321
+ "admin.empty.security": "No security events reported.",
322
+ "admin.fallback.localUser": "Local user",
323
+ "admin.fallback.member": "member",
324
+ "admin.fallback.role": "role",
325
+ "admin.fallback.noCaps": "No caps",
326
+ "admin.filters.label": "Audit log filters",
327
+ "admin.filters.search": "Search logs",
328
+ "admin.filters.searchAria": "Search audit logs",
329
+ "admin.filters.severityAria": "Filter by severity",
330
+ "admin.filters.all": "All severities",
331
+ "admin.filters.informational": "Informational",
332
+ "admin.filters.notice": "Notice",
333
+ "admin.filters.warning": "Warning",
334
+ "admin.filters.high": "High",
335
+ "admin.filters.matched": "{count} matched",
336
+ "admin.brain.summaryFallback": "Local Brain services are separated from user chat.",
337
+ "admin.brain.rebuilding": "Rebuilding",
338
+ "admin.brain.rebuild": "Rebuild index",
339
+ "admin.policy.fallback": "Policy",
340
+ "admin.policy.quiet": "Policy API quiet",
341
+ "admin.retention.days": "{days} day retention",
342
+ "admin.retention.detail": "{events} retained · {candidates} ready for export/prune review",
343
+ "admin.log.event": "Event",
344
+ "admin.log.system": "system",
345
+ "admin.log.recently": "recently",
346
+ "admin.source.loading": "Loading",
347
+ "admin.source.live": "Live",
348
+ "admin.index.ready": "Index status ready",
349
+ "flow.analysis.fact.computer": "Computer",
350
+ "flow.analysis.fact.memory": "Memory",
351
+ "flow.analysis.fact.graphics": "Graphics",
352
+ "flow.analysis.fact.support": "Local Support",
353
+ "flow.analysis.fact.models": "Models",
354
+ "flow.analysis.checking": "Checking",
355
+ "flow.analysis.fact.computerDetail": "Operating system and chip",
356
+ "flow.analysis.fact.memoryDetail": "Available room for local thinking",
357
+ "flow.analysis.fact.graphicsDetail": "Local acceleration support",
358
+ "flow.analysis.fact.supportDetail": "Installed model helpers",
359
+ "flow.analysis.fact.modelsDetail": "Installed local Brains",
360
+ "flow.analysis.apple": "Apple Silicon Mac",
361
+ "flow.analysis.detected": "Detected",
362
+ "flow.analysis.localReady": "Local acceleration ready",
363
+ "flow.analysis.standardLocal": "Standard local mode",
364
+ "flow.analysis.supportReady": "Ready",
365
+ "flow.analysis.supportInstall": "Will be prepared",
366
+ "flow.analysis.modelsInstalled": "{count} already installed",
367
+ "flow.analysis.noModels": "None installed yet",
368
+ "flow.analysis.readyDetail": "Ready for local Digital Brain use",
369
+ "flow.analysis.memoryReadyDetail": "Enough context for the recommended model",
370
+ "flow.analysis.graphicsReadyDetail": "Lattice will choose the best available path",
371
+ "flow.analysis.supportReadyDetail": "Installed model helpers detected",
372
+ "flow.analysis.supportInstallDetail": "Lattice will add what is needed",
373
+ "flow.analysis.modelsReadyDetail": "One can be loaded immediately",
374
+ "flow.analysis.modelsInstallDetail": "Lattice will guide the first install",
375
+ "flow.recommend.reason.best": "Best Experience",
376
+ "flow.recommend.reason.faster": "Faster",
377
+ "flow.recommend.reason.advanced": "Advanced",
378
+ "flow.recommend.reason.default": "Recommended",
379
+ "flow.recommend.sizeReady": "ready",
380
+ "flow.recommend.fallbackName": "Recommended Brain",
381
+ "flow.install.stage.download": "Getting the model files.",
382
+ "flow.install.stage.validate": "Checking that the Brain can answer.",
383
+ "flow.install.stage.load": "Loading the Brain.",
384
+ "flow.install.stage.error": "Something needs attention.",
385
+ "flow.install.step.install": "Install",
386
+ "flow.install.step.download": "Download",
387
+ "flow.install.step.validate": "Validate",
388
+ "flow.install.step.load": "Load",
191
389
  "flow.shell": "Create your local AI Brain",
192
390
  "flow.login.title": "Choose the owner of your AI Brain.",
193
391
  "flow.login.body": "Lattice AI is a local-first Digital Brain that keeps your knowledge on this computer. Models can change; external transfer starts only when you choose it.",
@@ -220,6 +418,7 @@ export const COPY: Record<Language, TextMap> = {
220
418
  "flow.recommend.primary": "Start with recommendation",
221
419
  "flow.recommend.unsupported": "This computer needs one more check",
222
420
  "flow.recommend.back": "Back",
421
+ "flow.recommend.skip": "Open Brain without a model",
223
422
  "flow.recommend.hint": "If unsure, start with the recommendation.",
224
423
  "flow.install.title": "Install the model and start.",
225
424
  "flow.install.body": "This model becomes your Brain's local voice. Internet is needed only for download; execution and memory stay on this computer.",
@@ -232,7 +431,16 @@ export const COPY: Record<Language, TextMap> = {
232
431
  "flow.install.start": "Download and start",
233
432
  "flow.install.busy": "Preparing model...",
234
433
  "flow.install.enter": "Enter Brain",
434
+ "flow.install.later": "Do later",
235
435
  "flow.install.local": "Downloads and external communication start only when you choose them.",
436
+ "flow.consent.title": "Before downloading",
437
+ "flow.consent.body": "Starting will fetch these model files from an external repository and store them on this computer.",
438
+ "flow.consent.ready": "This model is already ready, or no external connection starts until you choose it.",
439
+ "flow.consent.size": "Download size",
440
+ "flow.consent.sizeUnknown": "Reported by runtime",
441
+ "flow.consent.location": "Storage location",
442
+ "flow.consent.external": "External target",
443
+ "flow.consent.externalNone": "No external connection before selection",
236
444
  },
237
445
  };
238
446