@townco/ui 0.1.41 → 0.1.43

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.
@@ -6,7 +6,7 @@ import { useChatMessages, useChatSession, useToolCalls, } from "../../core/hooks
6
6
  import { useChatStore } from "../../core/store/chat-store.js";
7
7
  import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
8
8
  import { cn } from "../lib/utils.js";
9
- import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, TodoTabContent, } from "./index.js";
9
+ import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, ContextUsageButton, FilesTabContent, IconButton, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, ThemeToggle, TodoTabContent, } from "./index.js";
10
10
  const logger = createLogger("gui");
11
11
  // Helper component to provide openFiles callback
12
12
  function OpenFilesButton({ children, }) {
@@ -25,12 +25,12 @@ function AsideTabs() {
25
25
  // Mobile header component that uses ChatHeader context
26
26
  function MobileHeader({ agentName, showHeader, }) {
27
27
  const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
28
- return (_jsxs("div", { className: "flex lg:hidden items-center gap-2 flex-1", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx("button", { type: "button", className: "flex items-center justify-center shrink-0 cursor-pointer", "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
28
+ return (_jsxs("div", { className: "flex lg:hidden items-center flex-1", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle menu", onClick: () => setIsExpanded(!isExpanded), children: _jsx(ChevronUp, { className: cn("size-4 text-muted-foreground transition-transform duration-200", isExpanded ? "" : "rotate-180") }) })] }));
29
29
  }
30
30
  // Header component that uses ChatLayout context (must be inside ChatLayout.Root)
31
31
  function AppChatHeader({ agentName, todos, sources, showHeader, }) {
32
32
  const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
33
- return (_jsxs(ChatHeader.Root, { className: cn("border-b border-border bg-card relative lg:p-0", "[border-bottom-width:0.5px]"), children: [_jsxs("div", { className: "hidden lg:flex items-center gap-2 w-full h-16 py-5 pl-6 pr-4", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx("button", { type: "button", className: "flex items-center justify-center shrink-0 cursor-pointer", "aria-label": "Toggle sidebar", onClick: () => {
33
+ return (_jsxs(ChatHeader.Root, { className: cn("border-b border-border bg-card relative lg:p-0", "[border-bottom-width:0.5px]"), children: [_jsxs("div", { className: "hidden lg:flex items-center w-full h-16 py-5 pl-6 pr-4", children: [showHeader && (_jsx("div", { className: "flex items-center gap-2 flex-1", children: _jsx("h1", { className: "text-heading-4 text-foreground", children: agentName }) })), !showHeader && _jsx("div", { className: "flex-1" }), _jsx(ThemeToggle, {}), _jsx(IconButton, { "aria-label": "Toggle sidebar", onClick: () => {
34
34
  setPanelSize(panelSize === "hidden" ? "small" : "hidden");
35
35
  }, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName, showHeader: showHeader }), _jsx(ChatHeader.ExpandablePanel, { className: cn("pt-6 pb-8 px-6", "border-b border-border bg-card", "shadow-[0_4px_16px_0_rgba(0,0,0,0.04)]", "[border-bottom-width:0.5px]"), children: _jsxs(Tabs, { defaultValue: "todo", className: "w-full", children: [_jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources"], variant: "default" }), _jsx(TabsContent, { value: "todo", className: "mt-4", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "mt-4", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "mt-4", children: _jsx(SourcesTabContent, { sources: sources }) })] }) })] }));
36
36
  }
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ import { type ButtonProps } from "./Button.js";
3
+ export interface IconButtonProps extends ButtonProps {
4
+ }
5
+ export declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Button } from "./Button.js";
5
+ export const IconButton = React.forwardRef(({ className, ...props }, ref) => {
6
+ return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", className: cn("rounded-full", className), ...props }));
7
+ });
8
+ IconButton.displayName = "IconButton";
@@ -30,7 +30,7 @@ export const PanelTabsHeader = React.forwardRef(({ showIcons = true, visibleTabs
30
30
  const gap = variant === "compact" ? "gap-[4px]" : "gap-3";
31
31
  return (_jsx(TabsList, { ref: ref, className: cn("w-full justify-start bg-transparent p-0 h-auto", gap, className), ...props, children: tabs.map((tab) => {
32
32
  const Icon = tab.icon;
33
- return (_jsxs(TabsTrigger, { value: tab.id, className: cn("gap-2 px-3 py-1.5 rounded-lg text-paragraph-sm font-medium", "data-[state=active]:bg-zinc-100 data-[state=active]:text-foreground", "data-[state=inactive]:text-muted-foreground"), children: [showIcons && Icon && _jsx(Icon, { className: "size-4" }), tab.label] }, tab.id));
33
+ return (_jsxs(TabsTrigger, { value: tab.id, className: cn("gap-2 px-3 py-1.5 rounded-lg text-paragraph-sm font-medium", "data-[state=active]:bg-muted data-[state=active]:text-foreground", "data-[state=inactive]:text-muted-foreground hover:text-foreground transition-colors"), children: [showIcons && Icon && _jsx(Icon, { className: "size-4" }), tab.label] }, tab.id));
34
34
  }) }));
35
35
  });
36
36
  PanelTabsHeader.displayName = "PanelTabsHeader";
@@ -0,0 +1,14 @@
1
+ type Theme = "dark" | "light" | "system";
2
+ type ThemeProviderProps = {
3
+ children: React.ReactNode;
4
+ defaultTheme?: Theme;
5
+ storageKey?: string;
6
+ };
7
+ type ThemeProviderState = {
8
+ theme: Theme;
9
+ setTheme: (theme: Theme) => void;
10
+ resolvedTheme: "dark" | "light";
11
+ };
12
+ export declare function ThemeProvider({ children, defaultTheme, storageKey, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare const useTheme: () => ThemeProviderState;
14
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ const initialState = {
4
+ theme: "system",
5
+ setTheme: () => null,
6
+ resolvedTheme: "light",
7
+ };
8
+ const ThemeProviderContext = createContext(initialState);
9
+ export function ThemeProvider({ children, defaultTheme = "system", storageKey = "vite-ui-theme", }) {
10
+ const [theme, setTheme] = useState(() => localStorage.getItem(storageKey) || defaultTheme);
11
+ const [resolvedTheme, setResolvedTheme] = useState("light");
12
+ useEffect(() => {
13
+ const root = window.document.documentElement;
14
+ root.classList.remove("light", "dark");
15
+ if (theme === "system") {
16
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
17
+ .matches
18
+ ? "dark"
19
+ : "light";
20
+ root.classList.add(systemTheme);
21
+ setResolvedTheme(systemTheme);
22
+ return;
23
+ }
24
+ root.classList.add(theme);
25
+ setResolvedTheme(theme);
26
+ }, [theme]);
27
+ const value = {
28
+ theme,
29
+ setTheme: (theme) => {
30
+ localStorage.setItem(storageKey, theme);
31
+ setTheme(theme);
32
+ },
33
+ resolvedTheme,
34
+ };
35
+ return (_jsx(ThemeProviderContext.Provider, { value: value, children: children }));
36
+ }
37
+ export const useTheme = () => {
38
+ const context = useContext(ThemeProviderContext);
39
+ if (context === undefined)
40
+ throw new Error("useTheme must be used within a ThemeProvider");
41
+ return context;
42
+ };
@@ -0,0 +1 @@
1
+ export declare function ThemeToggle(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Moon, Sun } from "lucide-react";
3
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./DropdownMenu.js";
4
+ import { IconButton } from "./IconButton.js";
5
+ import { useTheme } from "./ThemeProvider.js";
6
+ export function ThemeToggle() {
7
+ const { setTheme } = useTheme();
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(IconButton, { children: [_jsx(Sun, { className: "size-4 text-muted-foreground rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" }), _jsx(Moon, { className: "absolute size-4 text-muted-foreground rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" }), _jsx("span", { className: "sr-only", children: "Toggle theme" })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => setTheme("light"), children: "Light" }), _jsx(DropdownMenuItem, { onClick: () => setTheme("dark"), children: "Dark" }), _jsx(DropdownMenuItem, { onClick: () => setTheme("system"), children: "System" })] })] }));
9
+ }
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import JsonView from "@uiw/react-json-view";
3
3
  import { CheckSquare, ChevronDown, Cloud, Edit, FileText, Globe, Link, Search, Wrench, } from "lucide-react";
4
4
  import { useState } from "react";
5
+ import { useTheme } from "./ThemeProvider.js";
5
6
  /**
6
7
  * Map of icon names to Lucide components
7
8
  */
@@ -35,20 +36,48 @@ const _kindIcons = {
35
36
  */
36
37
  export function ToolCall({ toolCall }) {
37
38
  const [isExpanded, setIsExpanded] = useState(false);
39
+ const { resolvedTheme } = useTheme();
38
40
  // Determine which icon to show
39
41
  const IconComponent = toolCall.icon && ICON_MAP[toolCall.icon]
40
42
  ? ICON_MAP[toolCall.icon]
41
43
  : Wrench;
42
44
  // Determine display name
43
45
  const displayName = toolCall.prettyName || toolCall.title;
44
- return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsx("button", { type: "button", className: "flex items-center gap-2 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: _jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-zinc-500", children: [_jsx("div", { className: "text-zinc-500", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-zinc-500", children: displayName }), _jsx(ChevronDown, { className: `h-3 w-3 text-zinc-400 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }) }), isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-zinc-200 rounded-lg bg-zinc-50 overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-zinc-200", children: [_jsx("div", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-zinc-700 bg-zinc-200/50 px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null &&
46
+ // JSON View style based on theme
47
+ const jsonStyle = {
48
+ fontSize: "11px",
49
+ backgroundColor: "transparent",
50
+ fontFamily: "inherit",
51
+ "--w-rjv-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
52
+ "--w-rjv-key-string": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
53
+ "--w-rjv-background-color": "transparent",
54
+ "--w-rjv-line-color": resolvedTheme === "dark" ? "#27272a" : "#e4e4e7",
55
+ "--w-rjv-arrow-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
56
+ "--w-rjv-edit-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
57
+ "--w-rjv-info-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
58
+ "--w-rjv-update-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
59
+ "--w-rjv-copied-color": resolvedTheme === "dark" ? "#fafafa" : "#09090b",
60
+ "--w-rjv-copied-success-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
61
+ "--w-rjv-curlybraces-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
62
+ "--w-rjv-colon-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
63
+ "--w-rjv-brackets-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
64
+ "--w-rjv-quotes-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
65
+ "--w-rjv-quotes-string-color": resolvedTheme === "dark" ? "#a1a1aa" : "#71717a",
66
+ "--w-rjv-type-string-color": resolvedTheme === "dark" ? "#22c55e" : "#16a34a",
67
+ "--w-rjv-type-int-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
68
+ "--w-rjv-type-float-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
69
+ "--w-rjv-type-bigint-color": resolvedTheme === "dark" ? "#f59e0b" : "#d97706",
70
+ "--w-rjv-type-boolean-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
71
+ "--w-rjv-type-date-color": resolvedTheme === "dark" ? "#ec4899" : "#db2777",
72
+ "--w-rjv-type-url-color": resolvedTheme === "dark" ? "#3b82f6" : "#2563eb",
73
+ "--w-rjv-type-null-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
74
+ "--w-rjv-type-nan-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
75
+ "--w-rjv-type-undefined-color": resolvedTheme === "dark" ? "#ef4444" : "#dc2626",
76
+ };
77
+ return (_jsxs("div", { className: "flex flex-col my-4", children: [_jsx("button", { type: "button", className: "flex items-center gap-2 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: _jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [_jsx("div", { className: "text-muted-foreground", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: displayName }), _jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }) }), isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Files" }), _jsx("ul", { className: "space-y-1", children: toolCall.locations.map((loc) => (_jsxs("li", { className: "font-mono text-[11px] text-foreground bg-muted px-1.5 py-0.5 rounded w-fit", children: [loc.path, loc.line !== null &&
45
78
  loc.line !== undefined &&
46
- `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-zinc-200", children: [_jsx("div", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-zinc-700", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: {
47
- fontSize: "11px",
48
- backgroundColor: "transparent",
49
- fontFamily: "inherit",
50
- } }) })] })), (toolCall.content && toolCall.content.length > 0) ||
51
- toolCall.error ? (_jsxs("div", { className: "p-3 border-b border-zinc-200 last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-zinc-700", children: [toolCall.content?.map((block, idx) => {
79
+ `:${loc.line}`] }, `${loc.path}:${loc.line ?? ""}`))) })] })), toolCall.rawInput && Object.keys(toolCall.rawInput).length > 0 && (_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Input" }), _jsx("div", { className: "text-[11px] font-mono text-foreground", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) })] })), (toolCall.content && toolCall.content.length > 0) ||
80
+ toolCall.error ? (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Output" }), _jsxs("div", { className: "space-y-2 text-[11px] text-foreground", children: [toolCall.content?.map((block, idx) => {
52
81
  // Generate a stable key based on content
53
82
  const getBlockKey = () => {
54
83
  if (block.type === "diff" && "path" in block) {
@@ -73,18 +102,14 @@ export function ToolCall({ toolCall }) {
73
102
  const parsed = JSON.parse(text);
74
103
  // If it's an object or array, render with JsonView
75
104
  if (typeof parsed === "object" && parsed !== null) {
76
- return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: {
77
- fontSize: "11px",
78
- backgroundColor: "transparent",
79
- fontFamily: "inherit",
80
- } }) }, key));
105
+ return (_jsx("div", { className: "text-[11px]", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: jsonStyle }) }, key));
81
106
  }
82
107
  }
83
108
  catch {
84
109
  // Not valid JSON, render as plain text
85
110
  }
86
111
  // Render as plain text
87
- return (_jsx("pre", { className: "whitespace-pre-wrap font-mono text-[11px] text-zinc-700 overflow-x-auto", children: text }, key));
112
+ return (_jsx("pre", { className: "whitespace-pre-wrap font-mono text-[11px] text-foreground overflow-x-auto", children: text }, key));
88
113
  };
89
114
  // Handle nested content blocks (ACP format)
90
115
  if (block.type === "content" && "content" in block) {
@@ -102,15 +127,15 @@ export function ToolCall({ toolCall }) {
102
127
  "path" in block &&
103
128
  "oldText" in block &&
104
129
  "newText" in block) {
105
- return (_jsxs("div", { className: "border border-zinc-200 rounded bg-white", children: [_jsxs("div", { className: "bg-zinc-50 px-2 py-1 text-[10px] font-mono text-zinc-500 border-b border-zinc-200", children: [block.path, "line" in block &&
130
+ return (_jsxs("div", { className: "border border-border rounded bg-card", children: [_jsxs("div", { className: "bg-muted px-2 py-1 text-[10px] font-mono text-muted-foreground border-b border-border", children: [block.path, "line" in block &&
106
131
  block.line !== null &&
107
132
  block.line !== undefined &&
108
- `:${block.line}`] }), _jsxs("div", { className: "p-2 font-mono text-[11px]", children: [_jsxs("div", { className: "text-red-600", children: ["- ", block.oldText] }), _jsxs("div", { className: "text-green-600", children: ["+ ", block.newText] })] })] }, getBlockKey()));
133
+ `:${block.line}`] }), _jsxs("div", { className: "p-2 font-mono text-[11px]", children: [_jsxs("div", { className: "text-red-500 dark:text-red-400", children: ["- ", block.oldText] }), _jsxs("div", { className: "text-green-500 dark:text-green-400", children: ["+ ", block.newText] })] })] }, getBlockKey()));
109
134
  }
110
135
  // Handle terminal blocks
111
136
  if (block.type === "terminal" && "terminalId" in block) {
112
- return (_jsxs("div", { className: "bg-zinc-900 text-zinc-100 p-2 rounded text-[11px] font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
137
+ return (_jsxs("div", { className: "bg-neutral-900 text-neutral-100 p-2 rounded text-[11px] font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
113
138
  }
114
139
  return null;
115
- }), toolCall.error && (_jsxs("div", { className: "text-red-600 font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-zinc-100/50 border-t border-zinc-200 flex flex-wrap gap-4 text-[10px] text-zinc-500 font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
140
+ }), toolCall.error && (_jsxs("div", { className: "text-destructive font-mono text-[11px] mt-2", children: ["Error: ", toolCall.error] }))] })] })) : null, (toolCall.tokenUsage || toolCall.startedAt) && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border flex flex-wrap gap-4 text-[10px] text-muted-foreground font-sans", children: [toolCall.tokenUsage && (_jsxs("div", { className: "flex gap-3", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Input:" }), toolCall.tokenUsage.inputTokens.toLocaleString()] })), toolCall.tokenUsage.outputTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Output:" }), toolCall.tokenUsage.outputTokens.toLocaleString()] })), toolCall.tokenUsage.totalTokens !== undefined && (_jsxs("div", { children: [_jsx("span", { className: "uppercase tracking-wide font-semibold mr-1", children: "Total:" }), toolCall.tokenUsage.totalTokens.toLocaleString()] }))] })), toolCall.startedAt && (_jsxs("div", { className: "flex gap-3 ml-auto", children: [_jsxs("span", { children: ["Started: ", new Date(toolCall.startedAt).toLocaleTimeString()] }), toolCall.completedAt && (_jsxs("span", { children: ["Completed:", " ", new Date(toolCall.completedAt).toLocaleTimeString(), " (", Math.round((toolCall.completedAt - toolCall.startedAt) / 1000), "s)"] }))] }))] }))] }))] }));
116
141
  }
@@ -15,7 +15,7 @@ export function ToolCallList({ toolCalls, groupBy = "chronological", }) {
15
15
  completed: toolCalls.filter((tc) => tc.status === "completed"),
16
16
  failed: toolCalls.filter((tc) => tc.status === "failed"),
17
17
  };
18
- return (_jsxs("div", { className: "space-y-4", children: [grouped.in_progress.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2 pl-1", 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-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2 pl-1", children: "Pending" }), grouped.pending.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.completed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2 pl-1", children: "Completed" }), grouped.completed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.failed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2 pl-1", children: "Failed" }), grouped.failed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] }))] }));
18
+ return (_jsxs("div", { className: "space-y-4", children: [grouped.in_progress.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-2 pl-1", 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-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-2 pl-1", children: "Pending" }), grouped.pending.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.completed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-2 pl-1", children: "Completed" }), grouped.completed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] })), grouped.failed.length > 0 && (_jsxs("div", { children: [_jsx("h4", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2 pl-1", children: "Failed" }), grouped.failed.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id)))] }))] }));
19
19
  }
20
20
  // Default: chronological order
21
21
  return (_jsx("div", { className: "space-y-2", children: toolCalls.map((tc) => (_jsx(ToolCall, { toolCall: tc }, tc.id))) }));
@@ -22,6 +22,7 @@ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMe
22
22
  export { FileSystemItem, type FileSystemItemProps, } from "./FileSystemItem.js";
23
23
  export { FileSystemView, type FileSystemViewProps, } from "./FileSystemView.js";
24
24
  export { HeightTransition } from "./HeightTransition.js";
25
+ export { IconButton, type IconButtonProps } from "./IconButton.js";
25
26
  export { Input, type InputProps, inputVariants } from "./Input.js";
26
27
  export { Label } from "./Label.js";
27
28
  export { MarkdownRenderer } from "./MarkdownRenderer.js";
@@ -37,6 +38,8 @@ export { type SourceItem, SourceListItem, type SourceListItemProps, } from "./So
37
38
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
38
39
  export { Task, type TaskItem, TaskList, type TaskListProps, type TaskProps, } from "./Task.js";
39
40
  export { Textarea, type TextareaProps, textareaVariants } from "./Textarea.js";
41
+ export { ThemeProvider, useTheme } from "./ThemeProvider.js";
42
+ export { ThemeToggle } from "./ThemeToggle.js";
40
43
  export { ThinkingBlock, type ThinkingBlockProps, } from "./ThinkingBlock.js";
41
44
  export { TodoList, type TodoListProps } from "./TodoList.js";
42
45
  export { type TodoItem, TodoListItem, type TodoListItemProps, } from "./TodoListItem.js";
@@ -28,6 +28,7 @@ export { FileSystemItem, } from "./FileSystemItem.js";
28
28
  export { FileSystemView, } from "./FileSystemView.js";
29
29
  // Utility components
30
30
  export { HeightTransition } from "./HeightTransition.js";
31
+ export { IconButton } from "./IconButton.js";
31
32
  export { Input, inputVariants } from "./Input.js";
32
33
  export { Label } from "./Label.js";
33
34
  export { MarkdownRenderer } from "./MarkdownRenderer.js";
@@ -44,6 +45,8 @@ export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
44
45
  // Task/Todo components
45
46
  export { Task, TaskList, } from "./Task.js";
46
47
  export { Textarea, textareaVariants } from "./Textarea.js";
48
+ export { ThemeProvider, useTheme } from "./ThemeProvider.js";
49
+ export { ThemeToggle } from "./ThemeToggle.js";
47
50
  export { ThinkingBlock, } from "./ThinkingBlock.js";
48
51
  export { TodoList } from "./TodoList.js";
49
52
  export { TodoListItem, } from "./TodoListItem.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@agentclientprotocol/sdk": "^0.5.1",
43
- "@townco/core": "0.0.19",
43
+ "@townco/core": "0.0.21",
44
44
  "@radix-ui/react-dialog": "^1.1.15",
45
45
  "@radix-ui/react-dropdown-menu": "^2.1.16",
46
46
  "@radix-ui/react-label": "^2.1.8",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@tailwindcss/postcss": "^4.1.17",
66
- "@townco/tsconfig": "0.1.38",
66
+ "@townco/tsconfig": "0.1.40",
67
67
  "@types/node": "^24.10.0",
68
68
  "@types/react": "^19.2.2",
69
69
  "ink": "^6.4.0",