casabot 1.1.2 → 1.1.3
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 +51 -9
- package/package.json +1 -1
- package/src/tui/app.tsx +63 -8
package/dist/tui/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { render, Box, Text, useInput, useApp, useStdout } from "ink";
|
|
4
4
|
import TextInput from "ink-text-input";
|
|
5
5
|
import Spinner from "ink-spinner";
|
|
@@ -7,12 +7,13 @@ import Gradient from "ink-gradient";
|
|
|
7
7
|
import { marked } from "marked";
|
|
8
8
|
import { markedTerminal } from "marked-terminal";
|
|
9
9
|
import { runAgent } from "../agent/base.js";
|
|
10
|
-
marked.use(markedTerminal(
|
|
10
|
+
marked.use(markedTerminal({
|
|
11
|
+
showSectionPrefix: false,
|
|
12
|
+
tab: 2,
|
|
13
|
+
}));
|
|
14
|
+
marked.use({ gfm: true });
|
|
11
15
|
function renderMarkdown(content) {
|
|
12
|
-
|
|
13
|
-
if (typeof result === "string")
|
|
14
|
-
return result.trimEnd();
|
|
15
|
-
return content;
|
|
16
|
+
return marked.parse(content, { async: false }).trimEnd();
|
|
16
17
|
}
|
|
17
18
|
function truncateOutput(content, maxLines = 8) {
|
|
18
19
|
const lines = content.split("\n");
|
|
@@ -42,6 +43,40 @@ function estimateMessageLines(message, width) {
|
|
|
42
43
|
}
|
|
43
44
|
return 2;
|
|
44
45
|
}
|
|
46
|
+
function useMouseWheel(onScrollUp, onScrollDown) {
|
|
47
|
+
const { stdout } = useStdout();
|
|
48
|
+
const scrollUpRef = useRef(onScrollUp);
|
|
49
|
+
const scrollDownRef = useRef(onScrollDown);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
scrollUpRef.current = onScrollUp;
|
|
52
|
+
scrollDownRef.current = onScrollDown;
|
|
53
|
+
});
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!process.stdin.isTTY)
|
|
56
|
+
return;
|
|
57
|
+
const ENABLE_MOUSE = "\x1b[?1000h\x1b[?1006h";
|
|
58
|
+
const DISABLE_MOUSE = "\x1b[?1000l\x1b[?1006l";
|
|
59
|
+
stdout.write(ENABLE_MOUSE);
|
|
60
|
+
const sgrRegex = /\x1b\[<(\d+);\d+;\d+M/g;
|
|
61
|
+
const handleData = (data) => {
|
|
62
|
+
const str = data.toString("utf8");
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = sgrRegex.exec(str)) !== null) {
|
|
65
|
+
const button = parseInt(match[1], 10);
|
|
66
|
+
if (button === 64)
|
|
67
|
+
scrollUpRef.current();
|
|
68
|
+
if (button === 65)
|
|
69
|
+
scrollDownRef.current();
|
|
70
|
+
}
|
|
71
|
+
sgrRegex.lastIndex = 0;
|
|
72
|
+
};
|
|
73
|
+
process.stdin.on("data", handleData);
|
|
74
|
+
return () => {
|
|
75
|
+
process.stdin.off("data", handleData);
|
|
76
|
+
stdout.write(DISABLE_MOUSE);
|
|
77
|
+
};
|
|
78
|
+
}, [stdout]);
|
|
79
|
+
}
|
|
45
80
|
function HRule({ width }) {
|
|
46
81
|
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(width - 4, 10)) }) }));
|
|
47
82
|
}
|
|
@@ -171,21 +206,28 @@ function App({ provider, conversation, skills, }) {
|
|
|
171
206
|
}
|
|
172
207
|
setIsProcessing(false);
|
|
173
208
|
}, [isProcessing, provider, conversation, skills]);
|
|
209
|
+
const scrollUp = useCallback(() => {
|
|
210
|
+
setScrollOffset((prev) => Math.min(prev + 1, maxScrollOffset));
|
|
211
|
+
}, [maxScrollOffset]);
|
|
212
|
+
const scrollDown = useCallback(() => {
|
|
213
|
+
setScrollOffset((prev) => Math.max(prev - 1, 0));
|
|
214
|
+
}, []);
|
|
215
|
+
useMouseWheel(scrollUp, scrollDown);
|
|
174
216
|
useInput((ch, key) => {
|
|
175
217
|
if (key.ctrl && ch === "c") {
|
|
176
218
|
exit();
|
|
177
219
|
}
|
|
178
220
|
if (key.upArrow) {
|
|
179
|
-
|
|
221
|
+
scrollUp();
|
|
180
222
|
}
|
|
181
223
|
if (key.downArrow) {
|
|
182
|
-
|
|
224
|
+
scrollDown();
|
|
183
225
|
}
|
|
184
226
|
});
|
|
185
227
|
const userCount = messages.filter((m) => m.role === "user").length;
|
|
186
228
|
return (_jsxs(Box, { flexDirection: "column", width: termSize.columns, height: termSize.rows, borderStyle: "round", borderColor: "gray", children: [_jsx(Header, {}), _jsx(HRule, { width: termSize.columns }), _jsx(Box, { flexDirection: "column", height: messagesHeight, overflowY: "hidden", justifyContent: "flex-end", children: messages.length === 0 && !isProcessing ? (_jsx(WelcomeHint, {})) : (_jsxs(_Fragment, { children: [hiddenAbove > 0 && (_jsx(ScrollIndicator, { direction: "above", count: hiddenAbove })), visibleMessages.map((msg, i) => (_jsx(MessageView, { message: msg }, hiddenAbove + i))), isProcessing && _jsx(ProcessingIndicator, {}), hiddenBelow > 0 && (_jsx(ScrollIndicator, { direction: "below", count: hiddenBelow }))] })) }), _jsx(HRule, { width: termSize.columns }), _jsx(Box, { paddingX: 1, children: _jsxs(Box, { borderStyle: "round", borderColor: isProcessing ? "gray" : "cyan", paddingX: 1, width: "100%", children: [_jsx(Text, { color: "cyan", bold: true, children: "❯ " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: (val) => {
|
|
187
229
|
handleSubmit(val).catch(() => { });
|
|
188
|
-
}, placeholder: "Type your message\u2026", focus: !isProcessing, showCursor: true })] }) }), _jsxs(Box, { paddingX: 2, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: "Ctrl+C exit
|
|
230
|
+
}, placeholder: "Type your message\u2026", focus: !isProcessing, showCursor: true })] }) }), _jsxs(Box, { paddingX: 2, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: "Ctrl+C exit ↑↓/wheel scroll" }), _jsxs(Text, { dimColor: true, children: [userCount, " ", userCount === 1 ? "message" : "messages"] })] })] }));
|
|
189
231
|
}
|
|
190
232
|
export function startTUI(provider, conversation, skills) {
|
|
191
233
|
render(_jsx(App, { provider: provider, conversation: conversation, skills: skills }));
|
package/package.json
CHANGED
package/src/tui/app.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect, useMemo } from "react";
|
|
1
|
+
import React, { useState, useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { render, Box, Text, useInput, useApp, useStdout } from "ink";
|
|
3
3
|
import TextInput from "ink-text-input";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
@@ -9,12 +9,16 @@ import type { ChatProvider } from "../providers/base.js";
|
|
|
9
9
|
import type { ConversationHistory, Message, Skill } from "../config/types.js";
|
|
10
10
|
import { runAgent } from "../agent/base.js";
|
|
11
11
|
|
|
12
|
-
marked.use(
|
|
12
|
+
marked.use(
|
|
13
|
+
markedTerminal({
|
|
14
|
+
showSectionPrefix: false,
|
|
15
|
+
tab: 2,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
marked.use({ gfm: true });
|
|
13
19
|
|
|
14
20
|
function renderMarkdown(content: string): string {
|
|
15
|
-
|
|
16
|
-
if (typeof result === "string") return result.trimEnd();
|
|
17
|
-
return content;
|
|
21
|
+
return (marked.parse(content, { async: false }) as string).trimEnd();
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function truncateOutput(content: string, maxLines = 8): string {
|
|
@@ -53,6 +57,47 @@ function estimateMessageLines(message: Message, width: number): number {
|
|
|
53
57
|
return 2;
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
function useMouseWheel(
|
|
61
|
+
onScrollUp: () => void,
|
|
62
|
+
onScrollDown: () => void,
|
|
63
|
+
): void {
|
|
64
|
+
const { stdout } = useStdout();
|
|
65
|
+
const scrollUpRef = useRef(onScrollUp);
|
|
66
|
+
const scrollDownRef = useRef(onScrollDown);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
scrollUpRef.current = onScrollUp;
|
|
70
|
+
scrollDownRef.current = onScrollDown;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!process.stdin.isTTY) return;
|
|
75
|
+
|
|
76
|
+
const ENABLE_MOUSE = "\x1b[?1000h\x1b[?1006h";
|
|
77
|
+
const DISABLE_MOUSE = "\x1b[?1000l\x1b[?1006l";
|
|
78
|
+
stdout.write(ENABLE_MOUSE);
|
|
79
|
+
|
|
80
|
+
const sgrRegex = /\x1b\[<(\d+);\d+;\d+M/g;
|
|
81
|
+
|
|
82
|
+
const handleData = (data: Buffer): void => {
|
|
83
|
+
const str = data.toString("utf8");
|
|
84
|
+
let match;
|
|
85
|
+
while ((match = sgrRegex.exec(str)) !== null) {
|
|
86
|
+
const button = parseInt(match[1], 10);
|
|
87
|
+
if (button === 64) scrollUpRef.current();
|
|
88
|
+
if (button === 65) scrollDownRef.current();
|
|
89
|
+
}
|
|
90
|
+
sgrRegex.lastIndex = 0;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
process.stdin.on("data", handleData);
|
|
94
|
+
return () => {
|
|
95
|
+
process.stdin.off("data", handleData);
|
|
96
|
+
stdout.write(DISABLE_MOUSE);
|
|
97
|
+
};
|
|
98
|
+
}, [stdout]);
|
|
99
|
+
}
|
|
100
|
+
|
|
56
101
|
function HRule({ width }: { width: number }): React.ReactElement {
|
|
57
102
|
return (
|
|
58
103
|
<Box paddingX={1}>
|
|
@@ -331,15 +376,25 @@ function App({
|
|
|
331
376
|
[isProcessing, provider, conversation, skills],
|
|
332
377
|
);
|
|
333
378
|
|
|
379
|
+
const scrollUp = useCallback(() => {
|
|
380
|
+
setScrollOffset((prev) => Math.min(prev + 1, maxScrollOffset));
|
|
381
|
+
}, [maxScrollOffset]);
|
|
382
|
+
|
|
383
|
+
const scrollDown = useCallback(() => {
|
|
384
|
+
setScrollOffset((prev) => Math.max(prev - 1, 0));
|
|
385
|
+
}, []);
|
|
386
|
+
|
|
387
|
+
useMouseWheel(scrollUp, scrollDown);
|
|
388
|
+
|
|
334
389
|
useInput((ch, key) => {
|
|
335
390
|
if (key.ctrl && ch === "c") {
|
|
336
391
|
exit();
|
|
337
392
|
}
|
|
338
393
|
if (key.upArrow) {
|
|
339
|
-
|
|
394
|
+
scrollUp();
|
|
340
395
|
}
|
|
341
396
|
if (key.downArrow) {
|
|
342
|
-
|
|
397
|
+
scrollDown();
|
|
343
398
|
}
|
|
344
399
|
});
|
|
345
400
|
|
|
@@ -406,7 +461,7 @@ function App({
|
|
|
406
461
|
</Box>
|
|
407
462
|
|
|
408
463
|
<Box paddingX={2} justifyContent="space-between">
|
|
409
|
-
<Text dimColor>{"Ctrl+C exit
|
|
464
|
+
<Text dimColor>{"Ctrl+C exit ↑↓/wheel scroll"}</Text>
|
|
410
465
|
<Text dimColor>
|
|
411
466
|
{userCount} {userCount === 1 ? "message" : "messages"}
|
|
412
467
|
</Text>
|