apex-dev 3.0.2 → 3.1.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/dist/highlights-eq9cgrbb.scm +604 -0
- package/dist/highlights-ghv9g403.scm +205 -0
- package/dist/highlights-hk7bwhj4.scm +284 -0
- package/dist/highlights-r812a2qc.scm +150 -0
- package/dist/highlights-x6tmsnaa.scm +115 -0
- package/dist/index.js +62590 -0
- package/dist/injections-73j83es3.scm +27 -0
- package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/package.json +10 -4
- package/.config/amp/settings.json +0 -3
- package/.config/opencode/oh-my-opencode.json +0 -58
- package/.config/opencode/opencode.json +0 -6
- package/.local/share/amp/device-id.json +0 -3
- package/.local/share/amp/history.jsonl +0 -78
- package/.local/share/amp/session.json +0 -6
- package/.local/share/amp/threads/T-019c93b8-fce7-7083-aab9-d5f1c88a9545.json +0 -2528
- package/.local/share/amp/threads/T-019c93c8-4b7a-71df-94ac-867d8236a288.json +0 -7
- package/.local/share/amp/threads/T-019c93cd-5a7d-728e-8289-02e0ef4ca2ff.json +0 -680
- package/.local/share/amp/threads/T-019c93e7-83ca-7633-9eed-12bdcc118163.json +0 -873
- package/.local/share/amp/threads/T-019c93ea-ccd3-765a-88c9-42d7b631e977.json +0 -620
- package/.local/share/amp/threads/T-019c93ee-5977-71af-9ab7-c4611004b703.json +0 -1000
- package/.local/share/amp/threads/T-019c93f0-8328-71ed-a250-6da169cebfe1.json +0 -829
- package/.local/share/amp/threads/T-019c93f5-7bdd-703b-b2cd-0a04da64441a.json +0 -459
- package/.local/share/amp/threads/T-019c93f8-2b2e-733b-8249-9876546d9b5b.json +0 -764
- package/.local/share/amp/threads/T-019c93fd-fade-7195-a3b7-358f180d40b8.json +0 -7
- package/.local/share/amp/threads/T-019c93fe-2e56-705e-827e-eb99bd02e257.json +0 -3593
- package/.local/share/amp/threads/T-019c9408-6e64-77e1-9519-b913e3b24a03.json +0 -1559
- package/.local/share/amp/threads/T-019c9409-feeb-736d-b92c-4f7a263a643c.json +0 -7
- package/.local/share/amp/threads/T-019c940b-8d11-755b-b9e1-f923d8a5e6ba.json +0 -7
- package/.local/share/amp/threads/T-019c943a-6c5e-76a5-bf4e-170f7ad452ce.json +0 -979
- package/.local/share/amp/threads/T-019c94b2-1c8f-76d8-96d0-82449a028849.json +0 -1584
- package/.local/share/amp/threads/T-019c94b6-68f0-726e-92dd-90c5411ca28c.json +0 -7
- package/.local/share/amp/threads/T-019c94bf-a589-72a3-b3c2-a81359d9e0a6.json +0 -7
- package/.local/share/amp/threads/T-019c94e1-1bd9-70ab-b6f2-abd5cab4f4ce.json +0 -1035
- package/.local/share/amp/threads/T-019c94fd-cc4a-714b-896a-74f94020f6eb.json +0 -1310
- package/.local/share/amp/threads/T-019c9501-8976-7138-aca6-245a01a8fe9b.json +0 -7
- package/.local/share/amp/threads/T-019c9504-4b51-763e-8a9f-5d4cdfcf0cfa.json +0 -496
- package/.local/share/amp/threads/T-019c9506-4e3b-74fd-8eda-cedbf3793598.json +0 -2679
- package/.local/share/amp/threads/T-019c9508-178c-718c-88d2-caf816d64f65.json +0 -965
- package/.local/share/amp/threads/T-019c9509-2812-71fd-8fd2-923e29ad34fa.json +0 -7
- package/.local/share/amp/threads/T-019c950e-69fe-77d6-9854-fc73b77a3148.json +0 -4570
- package/.local/share/amp/threads/T-019c9707-6e2b-741c-b4d4-117026a78449.json +0 -2899
- package/.local/share/amp/threads/T-019c971b-6bc0-77b8-8868-f8956d3e71a8.json +0 -7
- package/.local/share/amp/threads/T-019c971b-c87c-75f3-a61f-beb18a1cb25f.json +0 -474
- package/.local/share/amp/threads/T-019c971d-d371-70ac-9805-5c739908e73b.json +0 -802
- package/.local/share/amp/threads/T-019c9722-d73d-74f1-9d1d-8fafaad0ede7.json +0 -7
- package/.local/share/amp/threads/T-019c9761-858c-719b-911f-bc2e4c8cbdde.json +0 -188
- package/.local/share/amp/threads/T-019c9761-f5f3-7606-a900-ebe7f10d6e37.json +0 -121
- package/.local/share/amp/threads/T-019c9763-b1ae-729d-90aa-f59938ce912e.json +0 -799
- package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +0 -1541
- package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +0 -7
- package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +0 -111
- package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +0 -7
- package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +0 -111
- package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +0 -71
- package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +0 -1611
- package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +0 -7
- package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +0 -1341
- package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +0 -163
- package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +0 -124
- package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +0 -1260
- package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +0 -403
- package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +0 -3422
- package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +0 -1830
- package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +0 -4061
- package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +0 -509
- package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +0 -2075
- package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +0 -7
- package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +0 -7
- package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +0 -1637
- package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +0 -3893
- package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +0 -7
- package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +0 -7
- package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +0 -7
- package/.local/share/amp/threads/T-019c9c56-5715-72e2-b8b4-87711a842dd1.json +0 -1799
- package/.local/share/amp/threads/T-019c9c5b-88b1-74cb-97e9-16b23e03daa2.json +0 -727
- package/.local/share/amp/threads/T-019c9c5c-3b3e-721c-ad2e-a2ef245dce3f.json +0 -738
- package/.local/share/amp/threads/T-019c9c5c-fd78-736f-9d29-a66d23839d40.json +0 -256
- package/.local/share/amp/threads/T-019c9c5d-db4a-74cd-ad2a-925fac87131d.json +0 -1859
- package/.local/share/kilo/kilo.db +0 -0
- package/.local/share/kilo/kilo.db-shm +0 -0
- package/.local/share/kilo/kilo.db-wal +0 -0
- package/.local/share/kilo/storage/migration +0 -1
- package/.local/share/kilo/storage/session_diff/ses_36bea4cb9ffe1b0j5HEL14KEaU.json +0 -1
- package/.local/share/kilo/storage/session_diff/ses_36beaa8f2ffeeZ3Y39SQ9UDWQQ.json +0 -1
- package/.local/share/kilo/telemetry-id +0 -1
- package/.local/share/opencode/auth.json +0 -6
- package/.local/share/opencode/opencode.db +0 -0
- package/.local/share/opencode/opencode.db-shm +0 -0
- package/.local/share/opencode/opencode.db-wal +0 -0
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c260708ffel4wG4yhdo0knDD.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -6
- package/.local/share/opencode/storage/migration +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c260708ffel4wG4yhdo0knDD.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c2af1c5ffegxEaOZOGcVykyy.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c2be235ffeOa6x8UCk1HW4kU.json +0 -1
- package/.local/share/opencode/tool-output/tool_c93da840c0016GrdyAkOnHGezU +0 -2330
- package/.local/share/opencode/tool-output/tool_c9411e784001cRoQqwVDb1a6lY +0 -1017
- package/.local/state/replit/log-query.db +0 -0
- package/.local/state/replit/log-query.db-shm +0 -0
- package/.local/state/replit/log-query.db-wal +0 -0
- package/.replit +0 -41
- package/.upm/store.json +0 -1
- package/AGENTS.md +0 -28
- package/bun.lock +0 -271
- package/generated-icon.png +0 -0
- package/hello.txt +0 -1
- package/index.jsx +0 -24
- package/src/agent.js +0 -504
- package/src/app.jsx +0 -96
- package/src/commands.js +0 -133
- package/src/components/AssistantMessage.jsx +0 -83
- package/src/components/ChatArea.jsx +0 -84
- package/src/components/DiffView.jsx +0 -26
- package/src/components/Divider.jsx +0 -8
- package/src/components/Header.jsx +0 -44
- package/src/components/HelpModal.jsx +0 -81
- package/src/components/InputBar.jsx +0 -32
- package/src/components/Spinner.jsx +0 -23
- package/src/components/StatusBar.jsx +0 -44
- package/src/components/SystemMessage.jsx +0 -31
- package/src/components/ThinkBlock.jsx +0 -29
- package/src/components/ToolCallItem.jsx +0 -43
- package/src/components/UserMessage.jsx +0 -11
- package/src/components/Welcome.jsx +0 -14
- package/src/config.js +0 -196
- package/src/hooks/useLayout.js +0 -15
- package/src/hooks/useStore.js +0 -6
- package/src/prompt.js +0 -101
- package/src/store.js +0 -99
- package/src/theme.js +0 -19
- package/src/thinking.js +0 -54
- package/src/toolExecutors.js +0 -853
- package/src/tools.js +0 -335
- package/src/utils.js +0 -32
- package/tsconfig.json +0 -10
package/src/commands.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
const { PROJECT_ROOT, session, resolvePath, getMode, setMode } = require('./config');
|
|
7
|
-
const { executeTool } = require('./toolExecutors');
|
|
8
|
-
const store = require('./store');
|
|
9
|
-
|
|
10
|
-
async function handleSlashCommand(input) {
|
|
11
|
-
const [cmd, ...rest] = input.split(' ');
|
|
12
|
-
const arg = rest.join(' ');
|
|
13
|
-
|
|
14
|
-
switch (cmd) {
|
|
15
|
-
case '/help':
|
|
16
|
-
store.setState({ showHelp: true });
|
|
17
|
-
break;
|
|
18
|
-
|
|
19
|
-
case '/clear':
|
|
20
|
-
session.conversationHistory = [];
|
|
21
|
-
store.clearMessages();
|
|
22
|
-
store.addMessage({ role: 'system', content: 'Conversation cleared.' });
|
|
23
|
-
break;
|
|
24
|
-
|
|
25
|
-
case '/files':
|
|
26
|
-
case '/ls': {
|
|
27
|
-
const dirPath = arg ? resolvePath(arg) : PROJECT_ROOT;
|
|
28
|
-
store.addMessage({ role: 'system', content: 'Loading file tree...', label: 'Project Files' });
|
|
29
|
-
const result = await executeTool('ListDir', { path: dirPath, recursive: true });
|
|
30
|
-
store.addMessage({ role: 'system', content: result, label: 'Project Files' });
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
case '/cost':
|
|
35
|
-
case '/status': {
|
|
36
|
-
const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
|
|
37
|
-
const parts = [
|
|
38
|
-
`Session: ${elapsed} min`,
|
|
39
|
-
`Turns: ${session.turnCount}`,
|
|
40
|
-
`Tools: ${session.toolCallCount}`,
|
|
41
|
-
`Tokens: ${session.totalTokens.toLocaleString()}`,
|
|
42
|
-
`Cost: $${session.totalCost.toFixed(4)}`,
|
|
43
|
-
];
|
|
44
|
-
if (session.filesModified.size > 0) parts.push(`Files modified: ${session.filesModified.size}`);
|
|
45
|
-
if (session.commandsRun.length > 0) parts.push(`Commands: ${session.commandsRun.length}`);
|
|
46
|
-
store.addMessage({ role: 'system', content: parts.join('\n'), label: 'Session Stats' });
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
case '/undo': {
|
|
51
|
-
if (session.editHistory.length === 0) {
|
|
52
|
-
store.addMessage({ role: 'system', content: 'No edits to undo.' });
|
|
53
|
-
} else {
|
|
54
|
-
const last = session.editHistory[session.editHistory.length - 1];
|
|
55
|
-
fs.writeFileSync(last.path, last.before, 'utf-8');
|
|
56
|
-
session.editHistory.pop();
|
|
57
|
-
store.addMessage({ role: 'system', content: `Undone last edit to ${path.basename(last.path)}` });
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
case '/diff': {
|
|
63
|
-
try {
|
|
64
|
-
const diff = execSync('git diff --stat 2>/dev/null', { encoding: 'utf-8', cwd: PROJECT_ROOT });
|
|
65
|
-
store.addMessage({ role: 'system', content: diff || '(no changes)', label: 'Git Diff' });
|
|
66
|
-
} catch {
|
|
67
|
-
store.addMessage({ role: 'system', content: 'Not a git repository.' });
|
|
68
|
-
}
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
case '/git': {
|
|
73
|
-
if (!arg) {
|
|
74
|
-
store.addMessage({ role: 'system', content: 'Usage: /git <command>' });
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const output = execSync(`git ${arg}`, { encoding: 'utf-8', cwd: PROJECT_ROOT });
|
|
79
|
-
store.addMessage({ role: 'system', content: output || '(no output)', label: `git ${arg}` });
|
|
80
|
-
} catch (err) {
|
|
81
|
-
store.addMessage({ role: 'system', content: err.stderr || err.message });
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
case '/mode': {
|
|
87
|
-
if (!arg) {
|
|
88
|
-
const current = getMode();
|
|
89
|
-
const modes = ['default', 'max', 'lite'];
|
|
90
|
-
const modeDescriptions = {
|
|
91
|
-
default: 'Single agent pass with auto code review',
|
|
92
|
-
max: 'Multi-strategy editing, best-of-N thinking, multi-perspective review, auto context pruning',
|
|
93
|
-
lite: 'Fast mode — skips validation and review steps',
|
|
94
|
-
};
|
|
95
|
-
const lines = modes.map(m =>
|
|
96
|
-
`${m === current ? '→ ' : ' '}${m.padEnd(10)} ${modeDescriptions[m]}`
|
|
97
|
-
);
|
|
98
|
-
store.addMessage({ role: 'system', content: `Current mode: ${current}\n\n${lines.join('\n')}`, label: 'Mode' });
|
|
99
|
-
} else {
|
|
100
|
-
const success = setMode(arg.trim());
|
|
101
|
-
if (success) {
|
|
102
|
-
store.addMessage({ role: 'system', content: `Mode set to: ${arg.trim()}`, label: 'Mode' });
|
|
103
|
-
} else {
|
|
104
|
-
store.addMessage({ role: 'system', content: `Invalid mode: ${arg}. Use default, max, or lite.` });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
case '/compact': {
|
|
111
|
-
const pruneId = store.addMessage({ role: 'system', content: 'Compacting conversation...', label: 'Context Pruner' });
|
|
112
|
-
try {
|
|
113
|
-
const result = await executeTool('ContextPruner', {}, (partial) => {
|
|
114
|
-
store.updateMessage(pruneId, { content: partial, label: 'Context Pruner' });
|
|
115
|
-
});
|
|
116
|
-
store.updateMessage(pruneId, { content: result, label: 'Context Pruner' });
|
|
117
|
-
} catch (err) {
|
|
118
|
-
store.updateMessage(pruneId, { content: `Compaction failed: ${err.message}` });
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
case '/quit':
|
|
124
|
-
return { action: 'quit' };
|
|
125
|
-
|
|
126
|
-
default:
|
|
127
|
-
store.addMessage({ role: 'system', content: `Unknown command: ${cmd}. Type /help for available commands.` });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = { handleSlashCommand };
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
|
|
5
|
-
export default function AssistantMessage({ content, isStreaming }) {
|
|
6
|
-
const { indent, isNarrow, width } = useLayout();
|
|
7
|
-
const codeIndent = isNarrow ? 1 : 2;
|
|
8
|
-
const separatorWidth = Math.min(width - indent - codeIndent, isNarrow ? 40 : 60);
|
|
9
|
-
if (!content) return null;
|
|
10
|
-
|
|
11
|
-
const lines = content.split('\n');
|
|
12
|
-
const rendered = [];
|
|
13
|
-
let inCodeBlock = false;
|
|
14
|
-
let codeLines = [];
|
|
15
|
-
let codeLang = '';
|
|
16
|
-
|
|
17
|
-
for (let i = 0; i < lines.length; i++) {
|
|
18
|
-
const line = lines[i];
|
|
19
|
-
|
|
20
|
-
if (line.startsWith('```') && !inCodeBlock) {
|
|
21
|
-
inCodeBlock = true;
|
|
22
|
-
codeLang = line.slice(3).trim() || 'code';
|
|
23
|
-
codeLines = [];
|
|
24
|
-
} else if (line.startsWith('```') && inCodeBlock) {
|
|
25
|
-
inCodeBlock = false;
|
|
26
|
-
rendered.push(
|
|
27
|
-
<box key={`code-${i}`} style={{ flexDirection: 'column', paddingLeft: codeIndent, marginTop: 0 }}>
|
|
28
|
-
<text fg={colors.dim} content={`── ${codeLang} ──`} />
|
|
29
|
-
{codeLines.map((cl, j) => (
|
|
30
|
-
<text key={j}>
|
|
31
|
-
<span fg={colors.dim}>{String(j + 1).padStart(isNarrow ? 2 : 3) + ' │ '}</span>
|
|
32
|
-
<span fg={colors.text}>{cl}</span>
|
|
33
|
-
</text>
|
|
34
|
-
))}
|
|
35
|
-
<text fg={colors.dim} content={'─'.repeat(Math.max(separatorWidth, 10))} />
|
|
36
|
-
</box>
|
|
37
|
-
);
|
|
38
|
-
} else if (inCodeBlock) {
|
|
39
|
-
codeLines.push(line);
|
|
40
|
-
} else {
|
|
41
|
-
const processed = line.replace(/`([^`]+)`/g, '«$1»');
|
|
42
|
-
if (processed.includes('«')) {
|
|
43
|
-
const parts = processed.split(/«|»/);
|
|
44
|
-
rendered.push(
|
|
45
|
-
<text key={`line-${i}`}>
|
|
46
|
-
{parts.map((part, j) =>
|
|
47
|
-
j % 2 === 0
|
|
48
|
-
? <span key={j} fg={colors.text}>{part}</span>
|
|
49
|
-
: <span key={j} fg={colors.cyan}>{part}</span>
|
|
50
|
-
)}
|
|
51
|
-
</text>
|
|
52
|
-
);
|
|
53
|
-
} else {
|
|
54
|
-
rendered.push(
|
|
55
|
-
<text key={`line-${i}`}>
|
|
56
|
-
<span fg={colors.text}>{line}</span>
|
|
57
|
-
</text>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (inCodeBlock && codeLines.length > 0) {
|
|
64
|
-
rendered.push(
|
|
65
|
-
<box key="code-tail" style={{ flexDirection: 'column', paddingLeft: codeIndent }}>
|
|
66
|
-
<text fg={colors.dim} content={`── ${codeLang} ──`} />
|
|
67
|
-
{codeLines.map((cl, j) => (
|
|
68
|
-
<text key={j}>
|
|
69
|
-
<span fg={colors.dim}>{String(j + 1).padStart(isNarrow ? 2 : 3) + ' │ '}</span>
|
|
70
|
-
<span fg={colors.text}>{cl}</span>
|
|
71
|
-
</text>
|
|
72
|
-
))}
|
|
73
|
-
</box>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<box style={{ flexDirection: 'column', paddingLeft: indent }}>
|
|
79
|
-
{rendered}
|
|
80
|
-
{isStreaming ? <text fg={colors.accent} content="▊" /> : null}
|
|
81
|
-
</box>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
import Welcome from './Welcome.jsx';
|
|
5
|
-
import UserMessage from './UserMessage.jsx';
|
|
6
|
-
import AssistantMessage from './AssistantMessage.jsx';
|
|
7
|
-
import ThinkBlock from './ThinkBlock.jsx';
|
|
8
|
-
import ToolCallItem from './ToolCallItem.jsx';
|
|
9
|
-
import DiffView from './DiffView.jsx';
|
|
10
|
-
import SystemMessage from './SystemMessage.jsx';
|
|
11
|
-
import Spinner from './Spinner.jsx';
|
|
12
|
-
import { toggleMessageExpanded } from '../store.js';
|
|
13
|
-
|
|
14
|
-
function MessageItem({ message }) {
|
|
15
|
-
const { width } = useLayout();
|
|
16
|
-
switch (message.role) {
|
|
17
|
-
case 'user':
|
|
18
|
-
return <UserMessage content={message.content} />;
|
|
19
|
-
case 'assistant':
|
|
20
|
-
return (
|
|
21
|
-
<box style={{ flexDirection: 'column', marginTop: 1 }}>
|
|
22
|
-
<text fg={colors.primary} attributes={TextAttributes.BOLD} style={{ paddingLeft: 1 }} content="Apex" />
|
|
23
|
-
<AssistantMessage content={message.content} />
|
|
24
|
-
</box>
|
|
25
|
-
);
|
|
26
|
-
case 'thinking':
|
|
27
|
-
return (
|
|
28
|
-
<ThinkBlock
|
|
29
|
-
content={message.content}
|
|
30
|
-
expanded={message.expanded}
|
|
31
|
-
onToggle={() => toggleMessageExpanded(message.id)}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
case 'tool':
|
|
35
|
-
return <ToolCallItem message={message} />;
|
|
36
|
-
case 'diff':
|
|
37
|
-
return <DiffView filename={message.filename} content={message.content} />;
|
|
38
|
-
case 'system':
|
|
39
|
-
return <SystemMessage message={message} />;
|
|
40
|
-
case 'divider':
|
|
41
|
-
return <text fg={colors.dim} style={{ paddingLeft: 1 }} content={'─'.repeat(Math.max(width - 2, 10))} />;
|
|
42
|
-
default:
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export default function ChatArea({ messages, streamingContent, streamingThinking, isProcessing }) {
|
|
48
|
-
const { indent } = useLayout();
|
|
49
|
-
return (
|
|
50
|
-
<scrollbox
|
|
51
|
-
style={{ flexGrow: 1 }}
|
|
52
|
-
focused
|
|
53
|
-
stickyScroll
|
|
54
|
-
stickyStart="bottom"
|
|
55
|
-
scrollY
|
|
56
|
-
>
|
|
57
|
-
<box style={{ flexDirection: 'column' }}>
|
|
58
|
-
<Welcome />
|
|
59
|
-
{messages.map(msg => (
|
|
60
|
-
<MessageItem key={msg.id} message={msg} />
|
|
61
|
-
))}
|
|
62
|
-
{streamingThinking ? (
|
|
63
|
-
<box style={{ paddingLeft: 2, marginTop: 0 }}>
|
|
64
|
-
<text fg={colors.dim} attributes={TextAttributes.ITALIC}>
|
|
65
|
-
<span fg={colors.dim}>{'▸ Thinking: '}</span>
|
|
66
|
-
<span fg={colors.dim}>{streamingThinking.slice(-100)}</span>
|
|
67
|
-
</text>
|
|
68
|
-
</box>
|
|
69
|
-
) : null}
|
|
70
|
-
{streamingContent ? (
|
|
71
|
-
<box style={{ flexDirection: 'column', marginTop: 0 }}>
|
|
72
|
-
<AssistantMessage content={streamingContent} isStreaming />
|
|
73
|
-
</box>
|
|
74
|
-
) : null}
|
|
75
|
-
{isProcessing && !streamingContent && !streamingThinking ? (
|
|
76
|
-
<box style={{ paddingLeft: indent, marginTop: 1 }}>
|
|
77
|
-
<Spinner label="Reasoning..." />
|
|
78
|
-
</box>
|
|
79
|
-
) : null}
|
|
80
|
-
<box style={{ height: 1 }} />
|
|
81
|
-
</box>
|
|
82
|
-
</scrollbox>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
export default function DiffView({ filename, content }) {
|
|
8
|
-
const { indent } = useLayout();
|
|
9
|
-
if (!content) return null;
|
|
10
|
-
const lines = content.split('\n');
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<box style={{ flexDirection: 'column', paddingLeft: indent }}>
|
|
14
|
-
<text fg={colors.text} attributes={TextAttributes.BOLD} content={path.basename(filename || '')} />
|
|
15
|
-
{lines.map((line, i) => {
|
|
16
|
-
if (line.startsWith('+')) {
|
|
17
|
-
return <text key={i} fg={colors.green} content={line} />;
|
|
18
|
-
}
|
|
19
|
-
if (line.startsWith('-')) {
|
|
20
|
-
return <text key={i} fg={colors.red} content={line} />;
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
})}
|
|
24
|
-
</box>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { colors } from '../theme.js';
|
|
2
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
3
|
-
|
|
4
|
-
export default function Divider() {
|
|
5
|
-
const { width } = useLayout();
|
|
6
|
-
const cols = Math.min(width, 120);
|
|
7
|
-
return <text fg={colors.dim} content={'─'.repeat(cols)} />;
|
|
8
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { TextAttributes } from '@opentui/core';
|
|
3
|
-
import { colors } from '../theme.js';
|
|
4
|
-
import { PROJECT_ROOT, getMode } from '../config.js';
|
|
5
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
|
-
|
|
10
|
-
export default function Header() {
|
|
11
|
-
const [branch, setBranch] = useState('');
|
|
12
|
-
const { isNarrow } = useLayout();
|
|
13
|
-
const cwd = path.basename(PROJECT_ROOT);
|
|
14
|
-
const mode = getMode();
|
|
15
|
-
const modeColors = { default: colors.dim, max: colors.accent, lite: colors.muted };
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
try {
|
|
19
|
-
const b = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', {
|
|
20
|
-
encoding: 'utf-8',
|
|
21
|
-
cwd: PROJECT_ROOT,
|
|
22
|
-
}).trim();
|
|
23
|
-
setBranch(b);
|
|
24
|
-
} catch {}
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<box style={{ flexDirection: 'row', paddingLeft: 1, paddingRight: 1 }}>
|
|
29
|
-
<text>
|
|
30
|
-
<span fg={colors.primary} attributes={TextAttributes.BOLD}>⚡ Apex</span>
|
|
31
|
-
<span fg={colors.dim}>{' '}</span>
|
|
32
|
-
<span fg={modeColors[mode] || colors.dim}>[{mode}]</span>
|
|
33
|
-
<span fg={colors.dim}>{' '}</span>
|
|
34
|
-
<span fg={colors.muted}>{isNarrow && cwd.length > 12 ? cwd.slice(0, 12) + '…' : cwd}</span>
|
|
35
|
-
{branch && !isNarrow ? (
|
|
36
|
-
<>
|
|
37
|
-
<span fg={colors.dim}>{' on '}</span>
|
|
38
|
-
<span fg={colors.text}>{branch}</span>
|
|
39
|
-
</>
|
|
40
|
-
) : null}
|
|
41
|
-
</text>
|
|
42
|
-
</box>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { useKeyboard } from '@opentui/react';
|
|
3
|
-
import { colors } from '../theme.js';
|
|
4
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
5
|
-
|
|
6
|
-
const COMMANDS = [
|
|
7
|
-
{ cmd: '/help', desc: 'Show this menu' },
|
|
8
|
-
{ cmd: '/mode', desc: 'Show/set mode (default, max, lite)' },
|
|
9
|
-
{ cmd: '/compact', desc: 'Compact conversation context' },
|
|
10
|
-
{ cmd: '/files', desc: 'Show project file tree' },
|
|
11
|
-
{ cmd: '/clear', desc: 'Clear conversation' },
|
|
12
|
-
{ cmd: '/cost', desc: 'Show session stats' },
|
|
13
|
-
{ cmd: '/undo', desc: 'Undo last edit' },
|
|
14
|
-
{ cmd: '/diff', desc: 'Show git diff' },
|
|
15
|
-
{ cmd: '/git <cmd>', desc: 'Run a git command' },
|
|
16
|
-
{ cmd: '/quit', desc: 'Exit' },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const TOOLS = [
|
|
20
|
-
'Read', 'Write', 'Edit', 'Patch', 'Bash', 'Grep', 'Glob', 'ListDir', 'UndoEdit', 'Task', 'CodeReview',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const SUBAGENTS = [
|
|
24
|
-
'FilePickerMax', 'Thinker', 'ThinkerBestOfN*', 'EditorMultiPrompt*', 'CodeReviewMulti*', 'Commander', 'ContextPruner',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
export default function HelpModal({ onClose, onCommand }) {
|
|
28
|
-
const { isNarrow } = useLayout();
|
|
29
|
-
|
|
30
|
-
useKeyboard((key) => {
|
|
31
|
-
if (key.name === 'escape' || key.name === 'q') {
|
|
32
|
-
onClose();
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<box
|
|
38
|
-
zIndex={100}
|
|
39
|
-
border
|
|
40
|
-
borderColor={colors.primary}
|
|
41
|
-
backgroundColor="#0d0d1a"
|
|
42
|
-
title=" Help "
|
|
43
|
-
titleAlignment="center"
|
|
44
|
-
style={{
|
|
45
|
-
position: 'absolute',
|
|
46
|
-
top: 2,
|
|
47
|
-
left: isNarrow ? 1 : 4,
|
|
48
|
-
bottom: 2,
|
|
49
|
-
right: isNarrow ? 1 : 4,
|
|
50
|
-
padding: 1,
|
|
51
|
-
flexDirection: 'column',
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
<text fg={colors.white} attributes={TextAttributes.BOLD} content="Commands" />
|
|
55
|
-
<box style={{ flexDirection: 'column', marginTop: 0 }}>
|
|
56
|
-
{COMMANDS.map(({ cmd, desc }) => (
|
|
57
|
-
<box
|
|
58
|
-
key={cmd}
|
|
59
|
-
style={{ flexDirection: 'row' }}
|
|
60
|
-
onMouseDown={() => {
|
|
61
|
-
const slashCmd = cmd.split(' ')[0];
|
|
62
|
-
if (onCommand && !cmd.includes('<')) onCommand(slashCmd);
|
|
63
|
-
onClose();
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<text>
|
|
67
|
-
<span fg={colors.accent}>{cmd.padEnd(isNarrow ? 10 : 14)}</span>
|
|
68
|
-
<span fg={colors.text}>{desc}</span>
|
|
69
|
-
</text>
|
|
70
|
-
</box>
|
|
71
|
-
))}
|
|
72
|
-
</box>
|
|
73
|
-
<text fg={colors.white} attributes={TextAttributes.BOLD} style={{ marginTop: 1 }} content="Tools" />
|
|
74
|
-
<text fg={colors.dim} content={TOOLS.join(', ')} />
|
|
75
|
-
<text fg={colors.white} attributes={TextAttributes.BOLD} style={{ marginTop: 1 }} content="Sub-Agents" />
|
|
76
|
-
<text fg={colors.dim} content={SUBAGENTS.join(', ')} />
|
|
77
|
-
<text fg={colors.dim} content=" * = MAX mode only" />
|
|
78
|
-
<text fg={colors.dim} style={{ marginTop: 1 }} content="Press ESC or q to close" />
|
|
79
|
-
</box>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { useRef } from 'react';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
|
|
5
|
-
export default function InputBar({ disabled, onSubmit }) {
|
|
6
|
-
const inputRef = useRef(null);
|
|
7
|
-
const { isNarrow } = useLayout();
|
|
8
|
-
|
|
9
|
-
const handleSubmit = (value) => {
|
|
10
|
-
const trimmed = value.trim();
|
|
11
|
-
if (!trimmed) return;
|
|
12
|
-
if (inputRef.current) inputRef.current.value = '';
|
|
13
|
-
onSubmit(trimmed);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<box style={{ flexDirection: 'column' }}>
|
|
18
|
-
<text fg={colors.dim} style={{ paddingLeft: isNarrow ? 1 : 2 }} content={isNarrow ? '^C exit · /help' : 'Ctrl+C to exit · /help for commands'} />
|
|
19
|
-
<box style={{ flexDirection: 'row', paddingLeft: 1 }}>
|
|
20
|
-
<text fg={colors.primary} content="❯ " />
|
|
21
|
-
<input
|
|
22
|
-
ref={inputRef}
|
|
23
|
-
focused={!disabled}
|
|
24
|
-
placeholder={disabled ? 'Processing...' : 'Type a message...'}
|
|
25
|
-
onSubmit={handleSubmit}
|
|
26
|
-
fg={colors.text}
|
|
27
|
-
style={{ flexGrow: 1 }}
|
|
28
|
-
/>
|
|
29
|
-
</box>
|
|
30
|
-
</box>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
|
|
4
|
-
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
|
-
|
|
6
|
-
export default function Spinner({ label }) {
|
|
7
|
-
const [frame, setFrame] = useState(0);
|
|
8
|
-
const timerRef = useRef(null);
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
timerRef.current = setInterval(() => {
|
|
12
|
-
setFrame(f => (f + 1) % FRAMES.length);
|
|
13
|
-
}, 80);
|
|
14
|
-
return () => clearInterval(timerRef.current);
|
|
15
|
-
}, []);
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<text>
|
|
19
|
-
<span fg={colors.accent}>{FRAMES[frame]}</span>
|
|
20
|
-
{label ? <span fg={colors.dim}>{' ' + label}</span> : null}
|
|
21
|
-
</text>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { colors } from '../theme.js';
|
|
2
|
-
import { session } from '../config.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
|
|
5
|
-
export default function StatusBar({ isProcessing }) {
|
|
6
|
-
const { isNarrow } = useLayout();
|
|
7
|
-
const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<box style={{ flexDirection: 'row', paddingLeft: isNarrow ? 1 : 2, paddingRight: isNarrow ? 1 : 2 }}>
|
|
11
|
-
<text>
|
|
12
|
-
<span fg={colors.dim}>{elapsed}min</span>
|
|
13
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
14
|
-
<span fg={colors.dim}>{session.turnCount} turns</span>
|
|
15
|
-
{!isNarrow ? (
|
|
16
|
-
<>
|
|
17
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
18
|
-
<span fg={colors.dim}>{session.toolCallCount} tools</span>
|
|
19
|
-
</>
|
|
20
|
-
) : null}
|
|
21
|
-
{!isNarrow ? (
|
|
22
|
-
<>
|
|
23
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
24
|
-
<span fg={colors.dim}>{session.totalTokens.toLocaleString()} tok</span>
|
|
25
|
-
</>
|
|
26
|
-
) : null}
|
|
27
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
28
|
-
<span fg={colors.dim}>{'$' + session.totalCost.toFixed(4)}</span>
|
|
29
|
-
{session.filesModified.size > 0 && !isNarrow ? (
|
|
30
|
-
<>
|
|
31
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
32
|
-
<span fg={colors.yellow}>{session.filesModified.size} files modified</span>
|
|
33
|
-
</>
|
|
34
|
-
) : null}
|
|
35
|
-
{isProcessing ? (
|
|
36
|
-
<>
|
|
37
|
-
<span fg={colors.dim}>{' · '}</span>
|
|
38
|
-
<span fg={colors.accent}>{isNarrow ? '...' : 'processing'}</span>
|
|
39
|
-
</>
|
|
40
|
-
) : null}
|
|
41
|
-
</text>
|
|
42
|
-
</box>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
import { toggleMessageExpanded } from '../store.js';
|
|
5
|
-
|
|
6
|
-
export default function SystemMessage({ message }) {
|
|
7
|
-
const { isNarrow, smallIndent } = useLayout();
|
|
8
|
-
const { id, content = '', label, expanded } = message;
|
|
9
|
-
if (!content) return null;
|
|
10
|
-
const lines = content.split('\n');
|
|
11
|
-
const maxPreview = isNarrow ? 3 : 6;
|
|
12
|
-
const displayLines = expanded ? lines : lines.slice(0, maxPreview);
|
|
13
|
-
const isTruncated = !expanded && lines.length > maxPreview;
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<box
|
|
17
|
-
style={{ flexDirection: 'column', paddingLeft: smallIndent, marginTop: 1 }}
|
|
18
|
-
onMouseDown={() => toggleMessageExpanded(id)}
|
|
19
|
-
>
|
|
20
|
-
{label ? (
|
|
21
|
-
<text fg={colors.accent} attributes={TextAttributes.BOLD} content={label} />
|
|
22
|
-
) : null}
|
|
23
|
-
{displayLines.map((line, i) => (
|
|
24
|
-
<text key={i} fg={colors.muted} content={line} />
|
|
25
|
-
))}
|
|
26
|
-
{isTruncated ? (
|
|
27
|
-
<text fg={colors.dim} content={isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`} />
|
|
28
|
-
) : null}
|
|
29
|
-
</box>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from '@opentui/core';
|
|
2
|
-
import { colors } from '../theme.js';
|
|
3
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
4
|
-
|
|
5
|
-
export default function ThinkBlock({ content, expanded, onToggle }) {
|
|
6
|
-
const { isNarrow, smallIndent } = useLayout();
|
|
7
|
-
if (!content) return null;
|
|
8
|
-
const lines = content.split('\n');
|
|
9
|
-
const maxPreview = isNarrow ? 2 : 4;
|
|
10
|
-
const displayLines = expanded ? lines : lines.slice(0, maxPreview);
|
|
11
|
-
const isTruncated = !expanded && lines.length > maxPreview;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<box
|
|
15
|
-
style={{ flexDirection: 'column', paddingLeft: smallIndent, marginTop: 0 }}
|
|
16
|
-
onMouseDown={onToggle}
|
|
17
|
-
>
|
|
18
|
-
<text fg={colors.dim} attributes={TextAttributes.ITALIC} content="▸ Thinking" />
|
|
19
|
-
{displayLines.map((line, i) => (
|
|
20
|
-
line.trim() ? (
|
|
21
|
-
<text key={i} fg={colors.dim} attributes={TextAttributes.ITALIC} style={{ paddingLeft: smallIndent }} content={line} />
|
|
22
|
-
) : null
|
|
23
|
-
))}
|
|
24
|
-
{isTruncated ? (
|
|
25
|
-
<text fg={colors.dim} attributes={TextAttributes.ITALIC} style={{ paddingLeft: smallIndent }} content={isNarrow ? `+${lines.length - maxPreview} more (tap)` : `... +${lines.length - maxPreview} more lines (click to expand)`} />
|
|
26
|
-
) : null}
|
|
27
|
-
</box>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { colors } from '../theme.js';
|
|
2
|
-
import { useLayout } from '../hooks/useLayout.js';
|
|
3
|
-
import { toggleMessageExpanded } from '../store.js';
|
|
4
|
-
import Spinner from './Spinner.jsx';
|
|
5
|
-
|
|
6
|
-
function formatElapsed(ms) {
|
|
7
|
-
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function truncate(str, len) {
|
|
11
|
-
return str.length > len ? str.slice(0, len - 3) + '...' : str;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default function ToolCallItem({ message }) {
|
|
15
|
-
const { indent, isNarrow } = useLayout();
|
|
16
|
-
const truncLen = isNarrow ? 30 : 50;
|
|
17
|
-
const { id, name, detail, status, success, elapsed, output, expanded } = message;
|
|
18
|
-
const isRunning = status === 'running' || status === 'pending';
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<box style={{ flexDirection: 'column', paddingLeft: indent }} onMouseDown={() => toggleMessageExpanded(id)}>
|
|
22
|
-
<box style={{ flexDirection: 'row' }}>
|
|
23
|
-
{isRunning ? (
|
|
24
|
-
<Spinner label={`[${name}] ${truncate(detail || '...', truncLen)}`} />
|
|
25
|
-
) : (
|
|
26
|
-
<text>
|
|
27
|
-
<span fg={success ? colors.green : colors.red}>{success ? '✓' : '✗'}</span>
|
|
28
|
-
<span fg={colors.dim}>{' ['}</span>
|
|
29
|
-
<span fg={colors.accent}>{name}</span>
|
|
30
|
-
<span fg={colors.dim}>{'] '}</span>
|
|
31
|
-
<span fg={colors.dim}>{truncate(detail || '', truncLen)}</span>
|
|
32
|
-
{elapsed != null ? <span fg={colors.dim}>{' ' + formatElapsed(elapsed)}</span> : null}
|
|
33
|
-
</text>
|
|
34
|
-
)}
|
|
35
|
-
</box>
|
|
36
|
-
{expanded && output ? (
|
|
37
|
-
<box style={{ paddingLeft: indent, marginTop: 0 }}>
|
|
38
|
-
<text fg={colors.dim} content={truncate(output, 500)} wrapMode="char" />
|
|
39
|
-
</box>
|
|
40
|
-
) : null}
|
|
41
|
-
</box>
|
|
42
|
-
);
|
|
43
|
-
}
|