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 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
- const result = marked.parse(content);
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
- setScrollOffset((prev) => Math.min(prev + 1, maxScrollOffset));
221
+ scrollUp();
180
222
  }
181
223
  if (key.downArrow) {
182
- setScrollOffset((prev) => Math.max(prev - 1, 0));
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 ↑↓ scroll" }), _jsxs(Text, { dimColor: true, children: [userCount, " ", userCount === 1 ? "message" : "messages"] })] })] }));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "casabot",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "CasAbot — Skill-driven multi-agent orchestrator system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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(markedTerminal());
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
- const result = marked.parse(content);
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
- setScrollOffset((prev) => Math.min(prev + 1, maxScrollOffset));
394
+ scrollUp();
340
395
  }
341
396
  if (key.downArrow) {
342
- setScrollOffset((prev) => Math.max(prev - 1, 0));
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 ↑↓ scroll"}</Text>
464
+ <Text dimColor>{"Ctrl+C exit ↑↓/wheel scroll"}</Text>
410
465
  <Text dimColor>
411
466
  {userCount} {userCount === 1 ? "message" : "messages"}
412
467
  </Text>