klyro 0.1.28 → 0.1.29
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/tui/app.js +45 -17
- package/package.json +1 -1
package/dist/tui/app.js
CHANGED
|
@@ -80,7 +80,7 @@ export function App(props) {
|
|
|
80
80
|
const [plan, setPlan] = useState([]);
|
|
81
81
|
const [status, setStatus] = useState({ model: props.initialModel, step: 0, maxSteps: props.maxSteps, usageInput: 0, usageOutput: 0, repairs: 0, status: 'idle', ...props.initialStatus });
|
|
82
82
|
const [elapsed, setElapsed] = useState(0);
|
|
83
|
-
const [
|
|
83
|
+
const [queuedInputs, setQueuedInputs] = useState([]);
|
|
84
84
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
|
85
85
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
86
86
|
const streamingIdRef = useRef(null);
|
|
@@ -97,17 +97,19 @@ export function App(props) {
|
|
|
97
97
|
const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
|
|
98
98
|
const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
|
|
99
99
|
useEffect(() => bridge.subscribe((p) => setAwaitingApproval(p !== null)), [bridge]);
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (queuedInputs.length > 0 && status.status !== 'running' && !awaitingApproval) {
|
|
102
|
+
const toSend = queuedInputs[0];
|
|
103
|
+
setQueuedInputs((prev) => prev.slice(1));
|
|
104
|
+
setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
|
|
105
|
+
streamingIdRef.current = null;
|
|
106
|
+
const cmd = parseSlash(toSend.trim());
|
|
107
|
+
if (cmd.kind === 'prompt')
|
|
108
|
+
void props.onPrompt(cmd.text);
|
|
109
|
+
else
|
|
110
|
+
void props.onSlash(cmd);
|
|
111
|
+
}
|
|
112
|
+
}, [queuedInputs, status.status, awaitingApproval]);
|
|
111
113
|
useEffect(() => { if (status.status !== 'running')
|
|
112
114
|
return; const start = Date.now() - elapsed; const t = setInterval(() => setElapsed(Date.now() - start), 1000); return () => clearInterval(t); }, [status.status, elapsed]);
|
|
113
115
|
useEffect(() => { if (isAtBottom)
|
|
@@ -141,6 +143,10 @@ export function App(props) {
|
|
|
141
143
|
const scrollUp = (n = 3) => setScrollOffset((p) => Math.max(0, p - n));
|
|
142
144
|
const scrollDown = (n = 3) => setScrollOffset((p) => Math.min(maxOffset, p + n));
|
|
143
145
|
useInput((inputStr, key) => {
|
|
146
|
+
if (key.escape && queuedInputs.length > 0) {
|
|
147
|
+
setQueuedInputs((prev) => prev.slice(1));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
144
150
|
if (key.pageUp || (key.ctrl && inputStr === 'u')) {
|
|
145
151
|
scrollUp(5);
|
|
146
152
|
return;
|
|
@@ -175,9 +181,10 @@ export function App(props) {
|
|
|
175
181
|
const v = input.trim();
|
|
176
182
|
if (!v)
|
|
177
183
|
return;
|
|
178
|
-
|
|
184
|
+
if (queuedInputs.length >= 3)
|
|
185
|
+
return; // max 3 queued per §6.6
|
|
186
|
+
setQueuedInputs((prev) => [...prev, v]);
|
|
179
187
|
setInput('');
|
|
180
|
-
setTranscript((prev) => [...prev, { id: nextId('queued'), kind: 'text', text: `queued: ${v.slice(0, 80)}`, role: 'assistant' }]);
|
|
181
188
|
return;
|
|
182
189
|
}
|
|
183
190
|
if (key.backspace || key.delete) {
|
|
@@ -252,7 +259,28 @@ export function App(props) {
|
|
|
252
259
|
return `${gr.verb} ${gr.items.length} items`;
|
|
253
260
|
})();
|
|
254
261
|
const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
|
|
255
|
-
|
|
262
|
+
const marker = isExpanded ? '▼' : '✓';
|
|
263
|
+
const markerColor = gr.status === 'error' ? tokens.ansi.err : gr.status === 'running' ? tokens.ansi.warn : tokens.ansi.ok;
|
|
264
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: markerColor, children: [marker, " ", verbLine] }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", right] })] }), isExpanded ? gr.items.map((it) => {
|
|
265
|
+
let friendly = '';
|
|
266
|
+
try {
|
|
267
|
+
const a = JSON.parse(it.args);
|
|
268
|
+
const p = a.path ?? a.pattern ?? a.command ?? '';
|
|
269
|
+
const short = p ? String(p).split('/').pop()?.slice(0, 40) ?? p : '';
|
|
270
|
+
if (it.name === 'read_file' && short)
|
|
271
|
+
friendly = `${short} ${it.result ? String(it.result).split('\n').length + ' lines' : ''}`.trim();
|
|
272
|
+
else if (it.name === 'shell_exec' && p)
|
|
273
|
+
friendly = `$ ${String(p).slice(0, 40)}`;
|
|
274
|
+
else if (short)
|
|
275
|
+
friendly = short;
|
|
276
|
+
else
|
|
277
|
+
friendly = it.args.slice(0, 40);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
friendly = it.args.slice(0, 40);
|
|
281
|
+
}
|
|
282
|
+
return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly }), it.isError ? _jsx(Text, { color: tokens.ansi.err, children: " \u2717" }) : null] }, it.id));
|
|
283
|
+
}) : null] }, gr.id));
|
|
256
284
|
}
|
|
257
285
|
const it = item;
|
|
258
286
|
if (it.kind === 'text' && it.role === 'user') {
|
|
@@ -266,11 +294,11 @@ export function App(props) {
|
|
|
266
294
|
if (it.kind === 'error')
|
|
267
295
|
return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
|
|
268
296
|
if (it.kind === 'policy')
|
|
269
|
-
return
|
|
297
|
+
return null;
|
|
270
298
|
if (it.kind === 'file_changed')
|
|
271
299
|
return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", it.path, " ", it.op] }) }, it.id);
|
|
272
300
|
if (it.kind === 'diff')
|
|
273
301
|
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: tokens.ansi.soft, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 0, children: [_jsx(Text, { color: tokens.ansi.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { wrap: "wrap", color: l.kind === 'add' ? tokens.ansi.ok : l.kind === 'remove' ? tokens.ansi.err : tokens.ansi.dim, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] }, it.id));
|
|
274
302
|
return null;
|
|
275
|
-
}), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, plan.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { bold: true, children: [g('todoPlan'), " Plan ", plan.filter((p) => p.status === 'done').length, "/", plan.length] })] }), plan.slice(0, 8).map((p) => (_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.dim, children: [p.status === 'done' ? g('todoDone') : p.status === 'in_progress' ? g('todoActive') : g('todoPending'), " ", p.title] })] }, p.id)))] })) : null] }), isFullscreen ? (_jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => (_jsx(Text, { color: i === thumbPos ? tokens.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: rule }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children: placeholder }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: rule })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: tokens.ansi.dim, children: hints }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
|
|
303
|
+
}), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, plan.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { bold: true, children: [g('todoPlan'), " Plan ", plan.filter((p) => p.status === 'done').length, "/", plan.length] })] }), plan.slice(0, 8).map((p) => (_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.dim, children: [p.status === 'done' ? g('todoDone') : p.status === 'in_progress' ? g('todoActive') : g('todoPending'), " ", p.title] })] }, p.id)))] })) : null, queuedInputs.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: queuedInputs.map((q, i) => (_jsxs(Text, { color: tokens.ansi.dim, children: ["queued: ", q.slice(0, 60), i === 0 ? ' esc to drop' : ''] }, i))) })) : null] }), isFullscreen ? (_jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => (_jsx(Text, { color: i === thumbPos ? tokens.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: rule }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children: placeholder }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: rule })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: tokens.ansi.dim, children: hints }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
|
|
276
304
|
}
|