@townco/ui 0.1.15 → 0.1.16

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.
Files changed (38) hide show
  1. package/dist/core/hooks/index.d.ts +1 -0
  2. package/dist/core/hooks/index.js +1 -0
  3. package/dist/core/hooks/use-chat-messages.d.ts +50 -11
  4. package/dist/core/hooks/use-chat-session.d.ts +5 -5
  5. package/dist/core/hooks/use-tool-calls.d.ts +52 -0
  6. package/dist/core/hooks/use-tool-calls.js +61 -0
  7. package/dist/core/schemas/chat.d.ts +166 -83
  8. package/dist/core/schemas/chat.js +27 -27
  9. package/dist/core/schemas/index.d.ts +1 -0
  10. package/dist/core/schemas/index.js +1 -0
  11. package/dist/core/schemas/tool-call.d.ts +174 -0
  12. package/dist/core/schemas/tool-call.js +130 -0
  13. package/dist/core/store/chat-store.d.ts +28 -28
  14. package/dist/core/store/chat-store.js +123 -59
  15. package/dist/gui/components/ChatLayout.js +11 -10
  16. package/dist/gui/components/MessageContent.js +4 -1
  17. package/dist/gui/components/ToolCall.d.ts +8 -0
  18. package/dist/gui/components/ToolCall.js +100 -0
  19. package/dist/gui/components/ToolCallList.d.ts +9 -0
  20. package/dist/gui/components/ToolCallList.js +22 -0
  21. package/dist/gui/components/index.d.ts +2 -0
  22. package/dist/gui/components/index.js +2 -0
  23. package/dist/gui/components/resizable.d.ts +7 -0
  24. package/dist/gui/components/resizable.js +7 -0
  25. package/dist/sdk/schemas/session.d.ts +390 -220
  26. package/dist/sdk/schemas/session.js +74 -29
  27. package/dist/sdk/transports/http.js +705 -472
  28. package/dist/sdk/transports/stdio.js +187 -32
  29. package/dist/tui/components/ChatView.js +19 -51
  30. package/dist/tui/components/MessageList.d.ts +2 -4
  31. package/dist/tui/components/MessageList.js +13 -37
  32. package/dist/tui/components/ToolCall.d.ts +9 -0
  33. package/dist/tui/components/ToolCall.js +41 -0
  34. package/dist/tui/components/ToolCallList.d.ts +8 -0
  35. package/dist/tui/components/ToolCallList.js +17 -0
  36. package/dist/tui/components/index.d.ts +2 -0
  37. package/dist/tui/components/index.js +2 -0
  38. package/package.json +4 -2
@@ -0,0 +1,100 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Wrench } from "lucide-react";
3
+ import { useState } from "react";
4
+ import ReactJson from "react-json-view";
5
+ /**
6
+ * Tool call status badge styles
7
+ */
8
+ const statusStyles = {
9
+ pending: "bg-transparent text-muted-foreground",
10
+ in_progress: "bg-transparent text-muted-foreground",
11
+ completed: "bg-transparent text-muted-foreground",
12
+ failed: "bg-transparent text-muted-foreground",
13
+ };
14
+ /**
15
+ * Tool call kind icons (using emoji for simplicity)
16
+ */
17
+ const kindIcons = {
18
+ read: "\u{1F4C4}",
19
+ edit: "\u{270F}\u{FE0F}",
20
+ delete: "\u{1F5D1}\u{FE0F}",
21
+ move: "\u{1F4E6}",
22
+ search: "\u{1F50D}",
23
+ execute: "\u{2699}\u{FE0F}",
24
+ think: "\u{1F4AD}",
25
+ fetch: "\u{1F310}",
26
+ switch_mode: "\u{1F501}",
27
+ other: "\u{1F527}",
28
+ };
29
+ /**
30
+ * ToolCall component - displays a single tool call with collapsible details
31
+ */
32
+ export function ToolCall({ toolCall }) {
33
+ const [isExpanded, setIsExpanded] = useState(false);
34
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsxs("span", { className: `inline-flex items-center gap-1.5 px-2 py-1 text-xs font-medium rounded ${statusStyles[toolCall.status]}`, children: [_jsx(Wrench, { className: "h-3 w-3" }), _jsx("span", { children: toolCall.title }), toolCall.status === "completed" && _jsx("span", { children: "\u2713" })] }), _jsx("span", { className: "text-gray-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity", children: isExpanded ? "▲" : "▼" })] }), isExpanded && (_jsxs("div", { className: "mt-2 space-y-3 text-sm border border-gray-200 rounded-md p-3 bg-white shadow-sm max-w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1.5", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-xs text-gray-700 bg-gray-50 px-2 py-1 rounded", children: [loc.path, loc.line !== null &&
35
+ loc.line !== undefined &&
36
+ `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1.5", children: "Input" }), _jsx("div", { className: "bg-gray-50 p-2 rounded border border-gray-200", children: _jsx(ReactJson, { src: toolCall.rawInput, name: false, collapsed: 1, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: { fontSize: "11px", backgroundColor: "transparent" }, theme: "rjv-default" }) })] })), toolCall.content && toolCall.content.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1.5", children: "Output" }), _jsx("div", { className: "space-y-2", children: toolCall.content.map((block, idx) => {
37
+ // Generate a stable key based on content
38
+ const getBlockKey = () => {
39
+ if (block.type === "diff" && "path" in block) {
40
+ return `diff-${block.path}-${idx}`;
41
+ }
42
+ if (block.type === "terminal" && "terminalId" in block) {
43
+ return `terminal-${block.terminalId}`;
44
+ }
45
+ if (block.type === "text" && "text" in block) {
46
+ return `text-${block.text.substring(0, 20)}-${idx}`;
47
+ }
48
+ if (block.type === "content" && "content" in block) {
49
+ const innerContent = block.content;
50
+ return `content-${innerContent.text?.substring(0, 20)}-${idx}`;
51
+ }
52
+ return `block-${idx}`;
53
+ };
54
+ // Helper to render text content (with JSON parsing if applicable)
55
+ const renderTextContent = (text, key) => {
56
+ // Try to parse as JSON
57
+ try {
58
+ const parsed = JSON.parse(text);
59
+ // If it's an object or array, render with ReactJson
60
+ if (typeof parsed === "object" && parsed !== null) {
61
+ return (_jsx("div", { className: "bg-gray-50 p-2 rounded border border-gray-200", children: _jsx(ReactJson, { src: parsed, name: false, collapsed: 1, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: {
62
+ fontSize: "11px",
63
+ backgroundColor: "transparent",
64
+ }, theme: "rjv-default" }) }, key));
65
+ }
66
+ }
67
+ catch {
68
+ // Not valid JSON, render as plain text
69
+ }
70
+ // Render as plain text
71
+ return (_jsx("pre", { className: "bg-gray-50 p-2.5 rounded border border-gray-200 text-xs overflow-x-auto font-mono text-gray-800", children: text }, key));
72
+ };
73
+ // Handle nested content blocks (ACP format)
74
+ if (block.type === "content" && "content" in block) {
75
+ const innerContent = block.content;
76
+ if (innerContent.type === "text" && innerContent.text) {
77
+ return renderTextContent(innerContent.text, getBlockKey());
78
+ }
79
+ }
80
+ // Handle direct text blocks
81
+ if (block.type === "text" && "text" in block) {
82
+ return renderTextContent(block.text, getBlockKey());
83
+ }
84
+ // Handle diff blocks
85
+ if (block.type === "diff" &&
86
+ "path" in block &&
87
+ "oldText" in block &&
88
+ "newText" in block) {
89
+ return (_jsxs("div", { className: "border rounded", children: [_jsxs("div", { className: "bg-gray-100 px-2 py-1 text-xs font-mono", children: [block.path, "line" in block &&
90
+ block.line !== null &&
91
+ block.line !== undefined &&
92
+ `:${block.line}`] }), _jsxs("div", { className: "p-2 font-mono text-xs", children: [_jsxs("div", { className: "text-red-600", children: ["- ", block.oldText] }), _jsxs("div", { className: "text-green-600", children: ["+ ", block.newText] })] })] }, getBlockKey()));
93
+ }
94
+ // Handle terminal blocks
95
+ if (block.type === "terminal" && "terminalId" in block) {
96
+ return (_jsxs("div", { className: "bg-black text-green-400 p-2 rounded text-xs font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
97
+ }
98
+ return null;
99
+ }) })] })), toolCall.error && (_jsxs("div", { className: "bg-red-50 border border-red-200 rounded p-2.5 text-red-700", children: [_jsx("div", { className: "text-xs font-semibold text-red-600 uppercase tracking-wide mb-1.5", children: "Error" }), _jsx("div", { className: "text-xs", children: toolCall.error })] })), toolCall.tokenUsage && (_jsxs("div", { className: "bg-gray-50 border border-gray-200 rounded p-2.5", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2", children: "Token Usage" }), _jsxs("div", { className: "grid grid-cols-3 gap-3 text-xs text-gray-700", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-gray-500 text-[10px] uppercase tracking-wide mb-0.5", children: "Input" }), _jsx("div", { className: "font-medium", children: toolCall.tokenUsage.inputTokens.toLocaleString() })] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-gray-500 text-[10px] uppercase tracking-wide mb-0.5", children: "Output" }), _jsx("div", { className: "font-medium", children: toolCall.tokenUsage.outputTokens.toLocaleString() })] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-gray-500 text-[10px] uppercase tracking-wide mb-0.5", children: "Total" }), _jsx("div", { className: "font-medium", children: toolCall.tokenUsage.totalTokens.toLocaleString() })] }))] })] })), toolCall.startedAt && (_jsxs("div", { className: "text-xs text-gray-500", children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString(), toolCall.completedAt && (_jsxs(_Fragment, { children: [" ", "| Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }));
100
+ }
@@ -0,0 +1,9 @@
1
+ import type { ToolCall as ToolCallType } from "../../core/schemas/tool-call.js";
2
+ export interface ToolCallListProps {
3
+ toolCalls: ToolCallType[];
4
+ groupBy?: "status" | "chronological";
5
+ }
6
+ /**
7
+ * ToolCallList component - renders a list of tool calls, optionally grouped
8
+ */
9
+ export declare function ToolCallList({ toolCalls, groupBy, }: ToolCallListProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ToolCall } from "./ToolCall.js";
3
+ /**
4
+ * ToolCallList component - renders a list of tool calls, optionally grouped
5
+ */
6
+ export function ToolCallList({ toolCalls, groupBy = "chronological", }) {
7
+ if (!toolCalls || toolCalls.length === 0) {
8
+ return null;
9
+ }
10
+ // Group by status if requested
11
+ if (groupBy === "status") {
12
+ const grouped = {
13
+ in_progress: toolCalls.filter((tc) => tc.status === "in_progress"),
14
+ pending: toolCalls.filter((tc) => tc.status === "pending"),
15
+ completed: toolCalls.filter((tc) => tc.status === "completed"),
16
+ failed: toolCalls.filter((tc) => tc.status === "failed"),
17
+ };
18
+ return (_jsxs("div", { className: "space-y-4", children: [grouped.in_progress.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-2", children: "In Progress" }), grouped.in_progress.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.pending.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-2", children: "Pending" }), grouped.pending.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.completed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-2", children: "Completed" }), grouped.completed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.failed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-2", children: "Failed" }), grouped.failed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] }))] }));
19
+ }
20
+ // Default: chronological order
21
+ return (_jsx("div", { className: "space-y-2", children: toolCalls.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id))) }));
22
+ }
@@ -29,3 +29,5 @@ export { Textarea, type TextareaProps, textareaVariants } from "./Textarea.js";
29
29
  export { ThinkingBlock, type ThinkingBlockProps, } from "./ThinkingBlock.js";
30
30
  export { TodoList, type TodoListProps } from "./TodoList.js";
31
31
  export { type TodoItem, TodoListItem, type TodoListItemProps, } from "./TodoListItem.js";
32
+ export { ToolCall, type ToolCallProps } from "./ToolCall.js";
33
+ export { ToolCallList, type ToolCallListProps } from "./ToolCallList.js";
@@ -36,3 +36,5 @@ export { Textarea, textareaVariants } from "./Textarea.js";
36
36
  export { ThinkingBlock, } from "./ThinkingBlock.js";
37
37
  export { TodoList } from "./TodoList.js";
38
38
  export { TodoListItem, } from "./TodoListItem.js";
39
+ export { ToolCall } from "./ToolCall.js";
40
+ export { ToolCallList } from "./ToolCallList.js";
@@ -0,0 +1,7 @@
1
+ import * as ResizablePrimitive from "react-resizable-panels";
2
+ declare const ResizablePanelGroup: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => import("react/jsx-runtime").JSX.Element;
3
+ declare const ResizablePanel: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.Panel>) => import("react/jsx-runtime").JSX.Element;
4
+ declare const ResizableHandle: ({ withHandle, className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
5
+ withHandle?: boolean;
6
+ }) => import("react/jsx-runtime").JSX.Element;
7
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as ResizablePrimitive from "react-resizable-panels";
3
+ import { cn } from "../lib/utils.js";
4
+ const ResizablePanelGroup = ({ className, ...props }) => (_jsx(ResizablePrimitive.PanelGroup, { className: cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className), ...props }));
5
+ const ResizablePanel = ({ className, ...props }) => (_jsx(ResizablePrimitive.Panel, { className: cn(className), ...props }));
6
+ const ResizableHandle = ({ withHandle, className, ...props }) => (_jsx(ResizablePrimitive.PanelResizeHandle, { className: cn("relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", className), ...props, children: withHandle && (_jsx("div", { className: "z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-2.5 w-2.5", children: [_jsx("title", { children: "Resize Handle" }), _jsx("circle", { cx: "9", cy: "12", r: "1" }), _jsx("circle", { cx: "9", cy: "5", r: "1" }), _jsx("circle", { cx: "9", cy: "19", r: "1" }), _jsx("circle", { cx: "15", cy: "12", r: "1" }), _jsx("circle", { cx: "15", cy: "5", r: "1" }), _jsx("circle", { cx: "15", cy: "19", r: "1" })] }) })) }));
7
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };