memorix 1.1.7 → 1.1.8
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/CHANGELOG.md +13 -0
- package/CLAUDE.md +6 -1
- package/README.md +21 -0
- package/README.zh-CN.md +21 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +730 -150
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/index.html +201 -201
- package/dist/dashboard/static/style.css +3584 -3584
- package/dist/index.js +15 -8
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +15 -8
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/SETUP.md +10 -0
- package/docs/dev-log/progress.txt +35 -26
- package/package.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/agent-integrations.ts +623 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +255 -255
- package/src/cli/commands/doctor.ts +23 -0
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +66 -66
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +73 -73
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +96 -96
- package/src/cli/commands/message.ts +121 -121
- package/src/cli/commands/poll.ts +70 -70
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +132 -132
- package/src/cli/commands/repair.ts +60 -0
- package/src/cli/commands/retention.ts +108 -108
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/setup.ts +3 -3
- package/src/cli/commands/skills.ts +123 -123
- package/src/cli/commands/task.ts +192 -192
- package/src/cli/commands/transfer.ts +73 -73
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Panels.tsx +632 -632
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +742 -742
- package/src/cli/tui/data.ts +547 -547
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/theme.ts +178 -178
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/context-pack.ts +5 -1
- package/src/codegraph/lite-provider.ts +5 -1
- package/src/codegraph/project-context.ts +5 -1
- package/src/compact/token-budget.ts +74 -74
- package/src/config/behavior.ts +59 -59
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/static/index.html +201 -201
- package/src/dashboard/static/style.css +3584 -3584
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/git/noise-filter.ts +210 -210
- package/src/hooks/installers/index.ts +4 -4
- package/src/hooks/official-skills.ts +1 -1
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/rules/memorix-agent-rules.md +2 -2
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +107 -107
- package/src/memory/consolidation.ts +302 -302
- package/src/memory/disclosure-policy.ts +141 -141
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +433 -433
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +523 -523
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +380 -380
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +219 -219
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/sdk.ts +327 -327
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/file-lock.ts +100 -100
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/obs-store.ts +255 -255
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/session-store.ts +259 -259
- package/src/store/sqlite-store.ts +339 -339
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +167 -167
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/wiki/generator.ts +237 -237
- package/src/wiki/knowledge-graph.ts +334 -334
- package/src/wiki/types.ts +85 -85
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/cli/tui/Sidebar.tsx
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
-
*
|
|
4
|
-
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
-
* with arrow symbol, brand-colored section headers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { Box, Text, useInput } from 'ink';
|
|
10
|
-
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
-
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
-
import type { ViewType } from './theme.js';
|
|
13
|
-
|
|
14
|
-
interface SidebarProps {
|
|
15
|
-
health: HealthInfo;
|
|
16
|
-
background: BackgroundInfo;
|
|
17
|
-
onAction: (cmd: string) => void;
|
|
18
|
-
activeView: ViewType;
|
|
19
|
-
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
-
isFocused?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ACTIONS = [
|
|
24
|
-
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
-
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
-
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
-
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
-
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
-
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
-
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
-
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
-
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
-
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const SIDEBAR_WIDTH = 32;
|
|
37
|
-
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
-
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
-
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
-
|
|
41
|
-
function separator(width: number): string {
|
|
42
|
-
return SEP.thin.repeat(width);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function colorForMode(mode: string): string {
|
|
46
|
-
const normalized = mode.toLowerCase();
|
|
47
|
-
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
-
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
-
return COLORS.warning;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function charWidth(ch: string): number {
|
|
53
|
-
const cp = ch.codePointAt(0)!;
|
|
54
|
-
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
-
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fit(text: string, max: number): string {
|
|
60
|
-
if (max <= 0) return '';
|
|
61
|
-
let width = 0;
|
|
62
|
-
for (const ch of text) {
|
|
63
|
-
width += charWidth(ch);
|
|
64
|
-
if (width > max) break;
|
|
65
|
-
}
|
|
66
|
-
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
-
if (max === 1) return '…';
|
|
68
|
-
let result = '';
|
|
69
|
-
let used = 0;
|
|
70
|
-
for (const ch of text) {
|
|
71
|
-
const cw = charWidth(ch);
|
|
72
|
-
if (used + cw > max - 1) break;
|
|
73
|
-
result += ch;
|
|
74
|
-
used += cw;
|
|
75
|
-
}
|
|
76
|
-
const truncated = `${result}…`;
|
|
77
|
-
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
-
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
-
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
-
|
|
84
|
-
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
-
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
-
useInput((ch, key) => {
|
|
87
|
-
// Esc: return home from any secondary view
|
|
88
|
-
if (key.escape && activeView !== 'home') {
|
|
89
|
-
onAction('/home');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const cmd = KEY_TO_CMD[ch];
|
|
93
|
-
if (cmd) {
|
|
94
|
-
onAction(cmd);
|
|
95
|
-
}
|
|
96
|
-
}, { isActive: isFocused });
|
|
97
|
-
|
|
98
|
-
// Map view types to sidebar action commands for highlight
|
|
99
|
-
const activeCmd = ACTIONS.find(a => {
|
|
100
|
-
const viewMap: Record<string, string> = {
|
|
101
|
-
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
-
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
-
'/project': 'project', '/configure': 'configure',
|
|
104
|
-
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
-
};
|
|
106
|
-
return viewMap[a.cmd] === activeView;
|
|
107
|
-
})?.cmd;
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<Box
|
|
111
|
-
flexDirection="column"
|
|
112
|
-
width={SIDEBAR_WIDTH}
|
|
113
|
-
borderStyle="single"
|
|
114
|
-
borderColor={COLORS.border}
|
|
115
|
-
paddingX={1}
|
|
116
|
-
>
|
|
117
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
-
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
-
{ACTIONS.map((action) => {
|
|
121
|
-
const isActive = action.cmd === activeCmd;
|
|
122
|
-
return (
|
|
123
|
-
<Box key={action.key}>
|
|
124
|
-
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
-
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
-
</Text>
|
|
127
|
-
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
-
</Box>
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
</Box>
|
|
132
|
-
|
|
133
|
-
<Box flexDirection="column">
|
|
134
|
-
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
-
|
|
137
|
-
<Box>
|
|
138
|
-
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
-
<Text color={
|
|
140
|
-
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
-
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
-
: COLORS.muted
|
|
143
|
-
}>
|
|
144
|
-
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
<Box>
|
|
148
|
-
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
-
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
-
</Box>
|
|
151
|
-
<Box>
|
|
152
|
-
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
-
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
-
</Box>
|
|
155
|
-
|
|
156
|
-
<Box marginTop={1} flexDirection="column">
|
|
157
|
-
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
-
<Box>
|
|
160
|
-
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
-
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
-
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
-
</Text>
|
|
164
|
-
</Box>
|
|
165
|
-
{background.port && (
|
|
166
|
-
<Box>
|
|
167
|
-
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
-
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
-
</Box>
|
|
170
|
-
)}
|
|
171
|
-
</Box>
|
|
172
|
-
</Box>
|
|
173
|
-
|
|
174
|
-
<Box marginTop={1}>
|
|
175
|
-
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
-
</Box>
|
|
177
|
-
</Box>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
+
* with arrow symbol, brand-colored section headers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text, useInput } from 'ink';
|
|
10
|
+
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
+
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
+
import type { ViewType } from './theme.js';
|
|
13
|
+
|
|
14
|
+
interface SidebarProps {
|
|
15
|
+
health: HealthInfo;
|
|
16
|
+
background: BackgroundInfo;
|
|
17
|
+
onAction: (cmd: string) => void;
|
|
18
|
+
activeView: ViewType;
|
|
19
|
+
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
+
isFocused?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ACTIONS = [
|
|
24
|
+
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
+
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
+
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
+
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
+
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
+
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
+
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
+
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
+
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
+
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const SIDEBAR_WIDTH = 32;
|
|
37
|
+
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
+
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
+
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
+
|
|
41
|
+
function separator(width: number): string {
|
|
42
|
+
return SEP.thin.repeat(width);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function colorForMode(mode: string): string {
|
|
46
|
+
const normalized = mode.toLowerCase();
|
|
47
|
+
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
+
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
+
return COLORS.warning;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function charWidth(ch: string): number {
|
|
53
|
+
const cp = ch.codePointAt(0)!;
|
|
54
|
+
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
+
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fit(text: string, max: number): string {
|
|
60
|
+
if (max <= 0) return '';
|
|
61
|
+
let width = 0;
|
|
62
|
+
for (const ch of text) {
|
|
63
|
+
width += charWidth(ch);
|
|
64
|
+
if (width > max) break;
|
|
65
|
+
}
|
|
66
|
+
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
+
if (max === 1) return '…';
|
|
68
|
+
let result = '';
|
|
69
|
+
let used = 0;
|
|
70
|
+
for (const ch of text) {
|
|
71
|
+
const cw = charWidth(ch);
|
|
72
|
+
if (used + cw > max - 1) break;
|
|
73
|
+
result += ch;
|
|
74
|
+
used += cw;
|
|
75
|
+
}
|
|
76
|
+
const truncated = `${result}…`;
|
|
77
|
+
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
+
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
+
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
+
|
|
84
|
+
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
+
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
+
useInput((ch, key) => {
|
|
87
|
+
// Esc: return home from any secondary view
|
|
88
|
+
if (key.escape && activeView !== 'home') {
|
|
89
|
+
onAction('/home');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const cmd = KEY_TO_CMD[ch];
|
|
93
|
+
if (cmd) {
|
|
94
|
+
onAction(cmd);
|
|
95
|
+
}
|
|
96
|
+
}, { isActive: isFocused });
|
|
97
|
+
|
|
98
|
+
// Map view types to sidebar action commands for highlight
|
|
99
|
+
const activeCmd = ACTIONS.find(a => {
|
|
100
|
+
const viewMap: Record<string, string> = {
|
|
101
|
+
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
+
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
+
'/project': 'project', '/configure': 'configure',
|
|
104
|
+
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
+
};
|
|
106
|
+
return viewMap[a.cmd] === activeView;
|
|
107
|
+
})?.cmd;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Box
|
|
111
|
+
flexDirection="column"
|
|
112
|
+
width={SIDEBAR_WIDTH}
|
|
113
|
+
borderStyle="single"
|
|
114
|
+
borderColor={COLORS.border}
|
|
115
|
+
paddingX={1}
|
|
116
|
+
>
|
|
117
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
+
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
+
{ACTIONS.map((action) => {
|
|
121
|
+
const isActive = action.cmd === activeCmd;
|
|
122
|
+
return (
|
|
123
|
+
<Box key={action.key}>
|
|
124
|
+
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
+
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
+
</Text>
|
|
127
|
+
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
+
</Box>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</Box>
|
|
132
|
+
|
|
133
|
+
<Box flexDirection="column">
|
|
134
|
+
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
+
|
|
137
|
+
<Box>
|
|
138
|
+
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
+
<Text color={
|
|
140
|
+
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
+
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
+
: COLORS.muted
|
|
143
|
+
}>
|
|
144
|
+
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
+
</Text>
|
|
146
|
+
</Box>
|
|
147
|
+
<Box>
|
|
148
|
+
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
+
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
+
</Box>
|
|
151
|
+
<Box>
|
|
152
|
+
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
+
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
|
|
156
|
+
<Box marginTop={1} flexDirection="column">
|
|
157
|
+
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
+
<Box>
|
|
160
|
+
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
+
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
+
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
+
</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
{background.port && (
|
|
166
|
+
<Box>
|
|
167
|
+
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
+
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
)}
|
|
171
|
+
</Box>
|
|
172
|
+
</Box>
|
|
173
|
+
|
|
174
|
+
<Box marginTop={1}>
|
|
175
|
+
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
</Box>
|
|
178
|
+
);
|
|
179
|
+
}
|