@townco/ui 0.1.22 → 0.1.24
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/core/hooks/use-chat-input.js +5 -1
- package/dist/core/hooks/use-chat-messages.js +7 -3
- package/dist/core/hooks/use-chat-session.js +5 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/store/chat-store.d.ts +6 -0
- package/dist/core/store/chat-store.js +27 -13
- package/dist/gui/components/Button.d.ts +1 -1
- package/dist/gui/components/Button.js +5 -5
- package/dist/gui/components/ChatPanelTabContent.d.ts +5 -0
- package/dist/gui/components/ChatPanelTabContent.js +5 -0
- package/dist/gui/components/ChatSecondaryPanel.d.ts +1 -1
- package/dist/gui/components/ChatSecondaryPanel.js +8 -3
- package/dist/gui/components/Input.js +1 -1
- package/dist/gui/components/PanelTabsHeader.d.ts +1 -1
- package/dist/gui/components/PanelTabsHeader.js +6 -1
- package/dist/gui/components/SourceListItem.d.ts +15 -0
- package/dist/gui/components/SourceListItem.js +7 -0
- package/dist/gui/components/ToolCall.js +7 -7
- package/dist/gui/components/index.d.ts +2 -1
- package/dist/gui/components/index.js +3 -2
- package/dist/sdk/client/acp-client.js +11 -3
- package/dist/sdk/transports/http.js +1 -1
- package/dist/sdk/transports/stdio.js +1 -1
- package/dist/tui/components/ChatView.d.ts +3 -0
- package/dist/tui/components/ChatView.js +20 -8
- package/dist/tui/components/InputBox.js +22 -3
- package/dist/tui/components/MessageList.js +1 -1
- package/dist/tui/components/SimpleTextInput.d.ts +7 -0
- package/dist/tui/components/SimpleTextInput.js +208 -0
- package/dist/tui/components/StatusBar.js +3 -3
- package/dist/tui/components/index.d.ts +1 -0
- package/dist/tui/components/index.js +1 -0
- package/package.json +3 -3
- package/src/styles/global.css +138 -84
- package/dist/core/lib/logger.d.ts +0 -24
- package/dist/core/lib/logger.js +0 -108
|
@@ -6,18 +6,18 @@ import { MessageList } from "./MessageList.js";
|
|
|
6
6
|
import { StatusBar } from "./StatusBar.js";
|
|
7
7
|
export function ChatView({ client }) {
|
|
8
8
|
const setIsStreaming = useChatStore((state) => state.setIsStreaming);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
9
|
+
const _streamingStartTime = useChatStore((state) => state.streamingStartTime);
|
|
10
|
+
const _totalBilled = useChatStore((state) => state.totalBilled);
|
|
11
|
+
const _currentContext = useChatStore((state) => state.currentContext);
|
|
12
|
+
const _currentModel = useChatStore((state) => state.currentModel);
|
|
13
|
+
const _tokenDisplayMode = useChatStore((state) => state.tokenDisplayMode);
|
|
14
14
|
// Use headless hooks for business logic
|
|
15
|
-
|
|
15
|
+
useChatSession(client); // Subscribe to session changes
|
|
16
16
|
const { messages, isStreaming } = useChatMessages(client);
|
|
17
17
|
useToolCalls(client); // Still need to subscribe to tool call events
|
|
18
18
|
const { value, isSubmitting, attachedFiles, onChange, onSubmit } = useChatInput(client);
|
|
19
19
|
// Check if we're actively receiving content (hide waiting indicator)
|
|
20
|
-
const
|
|
20
|
+
const _hasStreamingContent = messages.some((msg) => msg.isStreaming && msg.content.length > 0);
|
|
21
21
|
// Callbacks for keyboard shortcuts
|
|
22
22
|
const handleEscape = () => {
|
|
23
23
|
if (isStreaming) {
|
|
@@ -25,5 +25,17 @@ export function ChatView({ client }) {
|
|
|
25
25
|
setIsStreaming(false);
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", children: _jsx(MessageList, { messages: messages }) }), _jsx(InputBox, { value: value, isSubmitting: isSubmitting, attachedFiles: attachedFiles, onChange: onChange, onSubmit: onSubmit, onEscape: handleEscape })
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", children: _jsx(MessageList, { messages: messages }) }), _jsx(InputBox, { value: value, isSubmitting: isSubmitting, attachedFiles: attachedFiles, onChange: onChange, onSubmit: onSubmit, onEscape: handleEscape })] }));
|
|
29
|
+
}
|
|
30
|
+
// Export helper to render status content separately
|
|
31
|
+
export function ChatViewStatus({ client }) {
|
|
32
|
+
const streamingStartTime = useChatStore((state) => state.streamingStartTime);
|
|
33
|
+
const totalBilled = useChatStore((state) => state.totalBilled);
|
|
34
|
+
const currentContext = useChatStore((state) => state.currentContext);
|
|
35
|
+
const currentModel = useChatStore((state) => state.currentModel);
|
|
36
|
+
const tokenDisplayMode = useChatStore((state) => state.tokenDisplayMode);
|
|
37
|
+
const { connectionStatus, sessionId } = useChatSession(client);
|
|
38
|
+
const { messages, isStreaming } = useChatMessages(client);
|
|
39
|
+
const hasStreamingContent = messages.some((msg) => msg.isStreaming && msg.content.length > 0);
|
|
40
|
+
return (_jsx(StatusBar, { connectionStatus: connectionStatus, sessionId: sessionId, isStreaming: isStreaming, streamingStartTime: streamingStartTime, hasStreamingContent: hasStreamingContent, totalBilled: totalBilled, currentContext: currentContext, currentModel: currentModel, tokenDisplayMode: tokenDisplayMode }));
|
|
29
41
|
}
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text, useStdout } from "ink";
|
|
3
|
-
import {
|
|
2
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
|
+
import { SimpleTextInput } from "./SimpleTextInput.js";
|
|
4
4
|
export function InputBox({ value, isSubmitting, attachedFiles, onChange, onSubmit, onEscape, }) {
|
|
5
5
|
const { stdout } = useStdout();
|
|
6
6
|
const terminalWidth = stdout?.columns || 80;
|
|
7
|
-
|
|
7
|
+
// Handle special keys for multi-line and escape
|
|
8
|
+
useInput((_input, key) => {
|
|
9
|
+
// Escape key
|
|
10
|
+
if (key.escape && onEscape) {
|
|
11
|
+
onEscape();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// Don't interfere if submitting
|
|
15
|
+
if (isSubmitting)
|
|
16
|
+
return;
|
|
17
|
+
// Shift+Enter or Alt+Enter: insert newline
|
|
18
|
+
if (key.return && (key.shift || key.meta)) {
|
|
19
|
+
onChange(`${value}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// Split value into lines for display
|
|
24
|
+
const lines = value.split("\n");
|
|
25
|
+
const _hasMultipleLines = lines.length > 1;
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "blue", children: "─".repeat(terminalWidth) }), attachedFiles.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Attached files:" }), attachedFiles.map((file) => (_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: [" ", file.name] }), _jsxs(Text, { dimColor: true, children: [" (", formatFileSize(file.size), ")"] })] }, file.path)))] })), _jsx(Box, { paddingY: 1, flexDirection: "column", children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { bold: true, color: "blue", children: "> " }), _jsx(Box, { flexGrow: 1, children: isSubmitting ? (_jsx(Text, { color: "gray", italic: true, children: "Sending..." })) : (_jsx(SimpleTextInput, { value: value, onChange: onChange, onSubmit: onSubmit, placeholder: "Type your message... (\\ or Shift+Enter for newline)" })) })] }) })] }));
|
|
8
27
|
}
|
|
9
28
|
function formatFileSize(bytes) {
|
|
10
29
|
if (bytes < 1024)
|
|
@@ -3,7 +3,7 @@ import { Box, Text } from "ink";
|
|
|
3
3
|
import { GameOfLife } from "./GameOfLife.js";
|
|
4
4
|
import { ToolCall } from "./ToolCall.js";
|
|
5
5
|
export function MessageList({ messages }) {
|
|
6
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: messages.length === 0 ? (_jsx(GameOfLife, {})) : (messages.map((message) => (_jsx(Message, { message: message }, message.id)))) }));
|
|
6
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, height: "100%", children: messages.length === 0 ? (_jsx(GameOfLife, {})) : (messages.map((message) => (_jsx(Message, { message: message }, message.id)))) }));
|
|
7
7
|
}
|
|
8
8
|
function Message({ message }) {
|
|
9
9
|
const roleColor = message.role === "user"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface SimpleTextInputProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (value: string) => void;
|
|
4
|
+
onSubmit: () => void;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function SimpleTextInput({ value, onChange, onSubmit, placeholder, }: SimpleTextInputProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
export function SimpleTextInput({ value, onChange, onSubmit, placeholder = "", }) {
|
|
5
|
+
const [cursorOffset, setCursorOffset] = useState(0);
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
// Handle return/enter
|
|
8
|
+
if (key.return) {
|
|
9
|
+
onSubmit();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
// Handle backspace/delete
|
|
13
|
+
if (key.backspace || key.delete) {
|
|
14
|
+
const cursorPos = value.length + cursorOffset;
|
|
15
|
+
if (cursorPos > 0) {
|
|
16
|
+
const newValue = value.slice(0, cursorPos - 1) + value.slice(cursorPos);
|
|
17
|
+
onChange(newValue);
|
|
18
|
+
if (cursorOffset < 0) {
|
|
19
|
+
setCursorOffset(cursorOffset + 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Handle left arrow
|
|
25
|
+
if (key.leftArrow) {
|
|
26
|
+
const cursorPos = value.length + cursorOffset;
|
|
27
|
+
if (cursorPos > 0) {
|
|
28
|
+
setCursorOffset(cursorOffset - 1);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Handle right arrow
|
|
33
|
+
if (key.rightArrow) {
|
|
34
|
+
const cursorPos = value.length + cursorOffset;
|
|
35
|
+
if (cursorPos < value.length) {
|
|
36
|
+
setCursorOffset(cursorOffset + 1);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Handle up arrow - navigate to previous line
|
|
41
|
+
if (key.upArrow) {
|
|
42
|
+
const cursorPos = value.length + cursorOffset;
|
|
43
|
+
const lines = value.split("\n");
|
|
44
|
+
// Find current line and position within it
|
|
45
|
+
let currentLineIndex = 0;
|
|
46
|
+
let charCount = 0;
|
|
47
|
+
let posInLine = cursorPos;
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
const lineLength = (lines[i]?.length || 0) + (i < lines.length - 1 ? 1 : 0);
|
|
50
|
+
if (charCount + lineLength > cursorPos || i === lines.length - 1) {
|
|
51
|
+
currentLineIndex = i;
|
|
52
|
+
posInLine = cursorPos - charCount;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
charCount += lineLength;
|
|
56
|
+
}
|
|
57
|
+
// Move to previous line if possible
|
|
58
|
+
if (currentLineIndex > 0) {
|
|
59
|
+
const prevLine = lines[currentLineIndex - 1] || "";
|
|
60
|
+
const currentLine = lines[currentLineIndex] || "";
|
|
61
|
+
const prevLineStart = charCount - (currentLine.length + 1);
|
|
62
|
+
const targetPos = Math.min(posInLine, prevLine.length);
|
|
63
|
+
const newCursorPos = prevLineStart + targetPos;
|
|
64
|
+
setCursorOffset(newCursorPos - value.length);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Handle down arrow - navigate to next line
|
|
69
|
+
if (key.downArrow) {
|
|
70
|
+
const cursorPos = value.length + cursorOffset;
|
|
71
|
+
const lines = value.split("\n");
|
|
72
|
+
// Find current line and position within it
|
|
73
|
+
let currentLineIndex = 0;
|
|
74
|
+
let charCount = 0;
|
|
75
|
+
let posInLine = cursorPos;
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const lineLength = (lines[i]?.length || 0) + (i < lines.length - 1 ? 1 : 0);
|
|
78
|
+
if (charCount + lineLength > cursorPos || i === lines.length - 1) {
|
|
79
|
+
currentLineIndex = i;
|
|
80
|
+
posInLine = cursorPos - charCount;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
charCount += lineLength;
|
|
84
|
+
}
|
|
85
|
+
// Move to next line if possible
|
|
86
|
+
if (currentLineIndex < lines.length - 1) {
|
|
87
|
+
const nextLine = lines[currentLineIndex + 1] || "";
|
|
88
|
+
const currentLine = lines[currentLineIndex] || "";
|
|
89
|
+
const nextLineStart = charCount + currentLine.length + 1;
|
|
90
|
+
const targetPos = Math.min(posInLine, nextLine.length);
|
|
91
|
+
const newCursorPos = nextLineStart + targetPos;
|
|
92
|
+
setCursorOffset(newCursorPos - value.length);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Handle Ctrl+A (move to beginning)
|
|
97
|
+
if (key.ctrl && input === "a") {
|
|
98
|
+
setCursorOffset(-value.length);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Handle Ctrl+E (move to end)
|
|
102
|
+
if (key.ctrl && input === "e") {
|
|
103
|
+
setCursorOffset(0);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Handle Ctrl+W (delete word backward)
|
|
107
|
+
if (key.ctrl && input === "w") {
|
|
108
|
+
const cursorPos = value.length + cursorOffset;
|
|
109
|
+
const before = value.slice(0, cursorPos);
|
|
110
|
+
const after = value.slice(cursorPos);
|
|
111
|
+
const match = before.match(/\s*\S*$/);
|
|
112
|
+
if (match) {
|
|
113
|
+
const newBefore = before.slice(0, -match[0].length);
|
|
114
|
+
onChange(newBefore + after);
|
|
115
|
+
setCursorOffset(-after.length);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Handle Ctrl+U (delete to beginning)
|
|
120
|
+
if (key.ctrl && input === "u") {
|
|
121
|
+
const cursorPos = value.length + cursorOffset;
|
|
122
|
+
onChange(value.slice(cursorPos));
|
|
123
|
+
setCursorOffset(-value.slice(cursorPos).length);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Handle Ctrl+K (delete to end)
|
|
127
|
+
if (key.ctrl && input === "k") {
|
|
128
|
+
const cursorPos = value.length + cursorOffset;
|
|
129
|
+
onChange(value.slice(0, cursorPos));
|
|
130
|
+
setCursorOffset(0);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Regular character input
|
|
134
|
+
if (!key.ctrl && !key.meta && input.length > 0) {
|
|
135
|
+
const cursorPos = value.length + cursorOffset;
|
|
136
|
+
const newValue = value.slice(0, cursorPos) + input + value.slice(cursorPos);
|
|
137
|
+
// If user types backslash at the end, automatically add newline
|
|
138
|
+
if (input === "\\" && cursorPos === value.length) {
|
|
139
|
+
onChange(`${newValue.slice(0, -1)}\n`);
|
|
140
|
+
// Reset cursor to end (no offset)
|
|
141
|
+
setCursorOffset(0);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
onChange(newValue);
|
|
145
|
+
// Keep cursor at same relative position
|
|
146
|
+
if (cursorOffset < 0) {
|
|
147
|
+
setCursorOffset(cursorOffset - input.length);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Reset cursor when value changes externally (e.g., after submit)
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (value.length === 0) {
|
|
155
|
+
setCursorOffset(0);
|
|
156
|
+
}
|
|
157
|
+
}, [value]);
|
|
158
|
+
// Display the input with cursor
|
|
159
|
+
const cursorPos = value.length + cursorOffset;
|
|
160
|
+
// Show placeholder if empty
|
|
161
|
+
if (value.length === 0) {
|
|
162
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(Text, { dimColor: true, children: placeholder })] }));
|
|
163
|
+
}
|
|
164
|
+
// Split by newlines for multi-line rendering
|
|
165
|
+
const allLines = value.split("\n");
|
|
166
|
+
// Find which line the cursor is on and position within that line
|
|
167
|
+
let charCount = 0;
|
|
168
|
+
let cursorLineIndex = 0;
|
|
169
|
+
let cursorPosInLine = 0;
|
|
170
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
171
|
+
const line = allLines[i] || "";
|
|
172
|
+
const lineEndPos = charCount + line.length;
|
|
173
|
+
// Check if cursor is within this line (including at the end before newline)
|
|
174
|
+
if (cursorPos <= lineEndPos) {
|
|
175
|
+
cursorLineIndex = i;
|
|
176
|
+
cursorPosInLine = cursorPos - charCount;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
// Move past the newline character for next iteration
|
|
180
|
+
charCount = lineEndPos + 1;
|
|
181
|
+
}
|
|
182
|
+
// Build the full display text with cursor
|
|
183
|
+
let displayText = "";
|
|
184
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
185
|
+
const line = allLines[i] || "";
|
|
186
|
+
if (i === cursorLineIndex) {
|
|
187
|
+
// Add text before cursor
|
|
188
|
+
displayText += line.slice(0, cursorPosInLine);
|
|
189
|
+
// Mark cursor position - we'll add it separately
|
|
190
|
+
displayText += "\0"; // placeholder for cursor
|
|
191
|
+
// Add text after cursor
|
|
192
|
+
displayText += line.slice(cursorPosInLine + 1);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
displayText += line;
|
|
196
|
+
}
|
|
197
|
+
// Add newline if not last line
|
|
198
|
+
// if (i < allLines.length - 1) {
|
|
199
|
+
displayText += "\n";
|
|
200
|
+
// }
|
|
201
|
+
}
|
|
202
|
+
// Split by cursor placeholder
|
|
203
|
+
const parts = displayText.split("\0");
|
|
204
|
+
const beforeCursor = parts[0] || "";
|
|
205
|
+
const afterCursor = parts[1] || "";
|
|
206
|
+
const cursorChar = allLines[cursorLineIndex]?.[cursorPosInLine] || " ";
|
|
207
|
+
return (_jsxs(Text, { children: [beforeCursor, _jsx(Text, { inverse: true, children: cursorChar }), afterCursor] }));
|
|
208
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Text } from "ink";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { calculateTokenPercentage, formatTokenPercentage, } from "../../core/utils/model-context.js";
|
|
5
5
|
// Synonyms of "thinking" in multiple languages
|
|
@@ -113,7 +113,7 @@ export function StatusBar({ connectionStatus, isStreaming, streamingStartTime, h
|
|
|
113
113
|
const contextPercentage = calculateTokenPercentage(contextTokens, currentModel ?? undefined);
|
|
114
114
|
const contextPercentageStr = formatTokenPercentage(contextTokens, currentModel ?? undefined);
|
|
115
115
|
const contextColor = getTokenColor(contextPercentage);
|
|
116
|
-
return (_jsxs(
|
|
116
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "Ctx: " }), _jsx(Text, { color: contextColor, children: contextPercentageStr }), _jsx(Text, { dimColor: true, children: " | In: " }), _jsx(Text, { children: formatTokenCount(inputTokens) }), _jsx(Text, { dimColor: true, children: " | Out: " }), _jsx(Text, { children: formatTokenCount(outputTokens) })] }));
|
|
117
117
|
};
|
|
118
|
-
return (_jsxs(
|
|
118
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { color: statusColor, children: "\u25CF" }), _jsxs(Text, { children: [" ", connectionStatus] }), showWaiting && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(WaitingElapsedTime, { startTime: streamingStartTime })] })), _jsx(Text, { dimColor: true, children: " | " }), renderTokenDisplay()] }));
|
|
119
119
|
}
|
|
@@ -7,6 +7,7 @@ export * from "./InputBox.js";
|
|
|
7
7
|
export * from "./MessageList.js";
|
|
8
8
|
export * from "./MultiSelect.js";
|
|
9
9
|
export * from "./ReadlineInput.js";
|
|
10
|
+
export * from "./SimpleTextInput.js";
|
|
10
11
|
export * from "./SingleSelect.js";
|
|
11
12
|
export * from "./StatusBar.js";
|
|
12
13
|
export * from "./ToolCall.js";
|
|
@@ -7,6 +7,7 @@ export * from "./InputBox.js";
|
|
|
7
7
|
export * from "./MessageList.js";
|
|
8
8
|
export * from "./MultiSelect.js";
|
|
9
9
|
export * from "./ReadlineInput.js";
|
|
10
|
+
export * from "./SimpleTextInput.js";
|
|
10
11
|
export * from "./SingleSelect.js";
|
|
11
12
|
export * from "./StatusBar.js";
|
|
12
13
|
export * from "./ToolCall.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@agentclientprotocol/sdk": "^0.5.1",
|
|
43
|
+
"@townco/core": "0.0.2",
|
|
43
44
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
44
45
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
45
46
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -61,11 +62,10 @@
|
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@tailwindcss/postcss": "^4.1.17",
|
|
64
|
-
"@townco/tsconfig": "0.1.
|
|
65
|
+
"@townco/tsconfig": "0.1.21",
|
|
65
66
|
"@types/node": "^24.10.0",
|
|
66
67
|
"@types/react": "^19.2.2",
|
|
67
68
|
"ink": "^6.4.0",
|
|
68
|
-
"ink-text-input": "^6.0.0",
|
|
69
69
|
"react": "^19.2.0",
|
|
70
70
|
"tailwindcss": "^4.1.17",
|
|
71
71
|
"typescript": "^5.9.3"
|
package/src/styles/global.css
CHANGED
|
@@ -2,92 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
@source "../**/*.{ts,tsx}";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
--card-foreground: 222.2 84% 4.9%;
|
|
10
|
-
--popover: 0 0% 100%;
|
|
11
|
-
--popover-foreground: 222.2 84% 4.9%;
|
|
12
|
-
--primary: 222.2 47.4% 11.2%;
|
|
13
|
-
--primary-foreground: 210 40% 98%;
|
|
14
|
-
--secondary: 210 40% 96.1%;
|
|
15
|
-
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
16
|
-
--muted: 210 40% 96.1%;
|
|
17
|
-
--muted-foreground: 215.4 16.3% 46.9%;
|
|
18
|
-
--accent: 210 40% 96.1%;
|
|
19
|
-
--accent-foreground: 222.2 47.4% 11.2%;
|
|
20
|
-
--destructive: 0 84.2% 60.2%;
|
|
21
|
-
--destructive-foreground: 210 40% 98%;
|
|
22
|
-
--border: 214.3 31.8% 91.4%;
|
|
23
|
-
--input: 214.3 31.8% 91.4%;
|
|
24
|
-
--ring: 222.2 84% 4.9%;
|
|
25
|
-
--radius: 0.5rem;
|
|
5
|
+
@theme {
|
|
6
|
+
/* Semantic Color Tokens */
|
|
7
|
+
--color-background: var(--background);
|
|
8
|
+
--color-foreground: var(--foreground);
|
|
26
9
|
|
|
27
|
-
|
|
28
|
-
--
|
|
29
|
-
--text-secondary: 0 0% 32.16%; /* #525252 */
|
|
30
|
-
--text-tertiary: 0 0% 45.1%; /* #737373 */
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.dark {
|
|
34
|
-
--background: 222.2 84% 4.9%;
|
|
35
|
-
--foreground: 210 40% 98%;
|
|
36
|
-
--card: 222.2 84% 4.9%;
|
|
37
|
-
--card-foreground: 210 40% 98%;
|
|
38
|
-
--popover: 222.2 84% 4.9%;
|
|
39
|
-
--popover-foreground: 210 40% 98%;
|
|
40
|
-
--primary: 210 40% 98%;
|
|
41
|
-
--primary-foreground: 222.2 47.4% 11.2%;
|
|
42
|
-
--secondary: 217.2 32.6% 17.5%;
|
|
43
|
-
--secondary-foreground: 210 40% 98%;
|
|
44
|
-
--muted: 217.2 32.6% 17.5%;
|
|
45
|
-
--muted-foreground: 215 20.2% 65.1%;
|
|
46
|
-
--accent: 217.2 32.6% 17.5%;
|
|
47
|
-
--accent-foreground: 210 40% 98%;
|
|
48
|
-
--destructive: 0 62.8% 30.6%;
|
|
49
|
-
--destructive-foreground: 210 40% 98%;
|
|
50
|
-
--border: 217.2 32.6% 17.5%;
|
|
51
|
-
--input: 217.2 32.6% 17.5%;
|
|
52
|
-
--ring: 212.7 26.8% 83.9%;
|
|
10
|
+
--color-card: var(--card);
|
|
11
|
+
--color-card-foreground: var(--card-foreground);
|
|
53
12
|
|
|
54
|
-
|
|
55
|
-
--
|
|
56
|
-
--text-secondary: 0 0% 70%; /* Muted light text */
|
|
57
|
-
--text-tertiary: 0 0% 55%; /* More muted text */
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@theme {
|
|
61
|
-
/* Colors */
|
|
62
|
-
--color-background: hsl(var(--background));
|
|
63
|
-
--color-foreground: hsl(var(--foreground));
|
|
64
|
-
--color-card: hsl(var(--card));
|
|
65
|
-
--color-card-foreground: hsl(var(--card-foreground));
|
|
66
|
-
--color-popover: hsl(var(--popover));
|
|
67
|
-
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
68
|
-
--color-primary: hsl(var(--primary));
|
|
69
|
-
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
70
|
-
--color-secondary: hsl(var(--secondary));
|
|
71
|
-
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
72
|
-
--color-muted: hsl(var(--muted));
|
|
73
|
-
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
74
|
-
--color-accent: hsl(var(--accent));
|
|
75
|
-
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
76
|
-
--color-destructive: hsl(var(--destructive));
|
|
77
|
-
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
78
|
-
--color-border: hsl(var(--border));
|
|
79
|
-
--color-input: hsl(var(--input));
|
|
80
|
-
--color-ring: hsl(var(--ring));
|
|
13
|
+
--color-popover: var(--popover);
|
|
14
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
81
15
|
|
|
82
|
-
|
|
83
|
-
--color-
|
|
84
|
-
--color-
|
|
85
|
-
--color-text-tertiary: hsl(var(--text-tertiary));
|
|
16
|
+
--color-primary: var(--primary);
|
|
17
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
18
|
+
--color-primary-hover: var(--primary-hover);
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
--
|
|
89
|
-
--
|
|
20
|
+
--color-secondary: var(--secondary);
|
|
21
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
22
|
+
--color-secondary-hover: var(--secondary-hover);
|
|
23
|
+
|
|
24
|
+
--color-muted: var(--muted);
|
|
25
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
26
|
+
|
|
27
|
+
--color-accent: var(--accent);
|
|
28
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
29
|
+
--color-accent-hover: var(--accent-hover);
|
|
30
|
+
|
|
31
|
+
--color-destructive: var(--destructive);
|
|
32
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
33
|
+
--color-destructive-hover: var(--destructive-hover);
|
|
90
34
|
|
|
35
|
+
--color-border: var(--border);
|
|
36
|
+
--color-border-dark: var(--border-dark);
|
|
37
|
+
--color-border-focus: var(--border-focus);
|
|
38
|
+
--color-border-error: var(--border-error);
|
|
39
|
+
--color-input: var(--input);
|
|
40
|
+
--color-input-background: var(--input-background);
|
|
41
|
+
--color-ring: var(--ring);
|
|
42
|
+
|
|
43
|
+
/* Text colors from design system */
|
|
44
|
+
--color-text-primary: var(--text-primary);
|
|
45
|
+
--color-text-secondary: var(--text-secondary);
|
|
46
|
+
--color-text-tertiary: var(--text-tertiary);
|
|
47
|
+
|
|
91
48
|
/* Layout widths - max-width utilities */
|
|
92
49
|
--max-width-chat: 720px;
|
|
93
50
|
--max-width-prose: 477px;
|
|
@@ -106,15 +63,112 @@
|
|
|
106
63
|
|
|
107
64
|
--font-size-label: 0.875rem; /* 14px */
|
|
108
65
|
--line-height-label: 1.5; /* 150% */
|
|
66
|
+
|
|
67
|
+
/* Shadows */
|
|
68
|
+
--shadow-sm: 0 8px 24px -16px rgba(0, 0, 0, 0.04), 0 4px 16px 0 rgba(0, 0, 0, 0.04);
|
|
69
|
+
--shadow-md: 0 8px 24px -16px rgba(0, 0, 0, 0.04), 0 4px 16px 0 rgba(0, 0, 0, 0.04);
|
|
70
|
+
|
|
71
|
+
/* Radius */
|
|
72
|
+
--radius: 0.5rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
Define the CSS variables that reference Tailwind palette colors.
|
|
77
|
+
This allows us to swap them in dark mode while keeping the semantic names.
|
|
78
|
+
*/
|
|
79
|
+
:root {
|
|
80
|
+
--background: var(--color-white);
|
|
81
|
+
--foreground: var(--color-neutral-950);
|
|
82
|
+
|
|
83
|
+
--card: var(--color-white);
|
|
84
|
+
--card-foreground: var(--color-neutral-950);
|
|
85
|
+
|
|
86
|
+
--popover: var(--color-white);
|
|
87
|
+
--popover-foreground: var(--color-neutral-950);
|
|
88
|
+
|
|
89
|
+
--primary: var(--color-neutral-900);
|
|
90
|
+
--primary-foreground: var(--color-neutral-50);
|
|
91
|
+
--primary-hover: var(--color-neutral-700);
|
|
92
|
+
|
|
93
|
+
--secondary: var(--color-neutral-100);
|
|
94
|
+
--secondary-foreground: var(--color-neutral-900);
|
|
95
|
+
--secondary-hover: var(--color-neutral-50);
|
|
96
|
+
|
|
97
|
+
--muted: var(--color-neutral-50);
|
|
98
|
+
--muted-foreground: var(--color-neutral-500);
|
|
99
|
+
|
|
100
|
+
--accent: var(--color-neutral-100);
|
|
101
|
+
--accent-foreground: var(--color-neutral-900);
|
|
102
|
+
--accent-hover: var(--color-neutral-50);
|
|
103
|
+
|
|
104
|
+
--destructive: var(--color-red-500);
|
|
105
|
+
--destructive-foreground: var(--color-neutral-50);
|
|
106
|
+
--destructive-hover: var(--color-red-600);
|
|
107
|
+
|
|
108
|
+
--border: var(--color-neutral-200);
|
|
109
|
+
--border-dark: var(--color-neutral-300);
|
|
110
|
+
--border-focus: var(--color-neutral-400);
|
|
111
|
+
--border-error: var(--color-red-500);
|
|
112
|
+
--input: var(--color-neutral-200);
|
|
113
|
+
--input-background: var(--color-white);
|
|
114
|
+
--ring: var(--color-neutral-900);
|
|
115
|
+
|
|
116
|
+
/* Text colors from design system */
|
|
117
|
+
--text-primary: var(--color-neutral-900);
|
|
118
|
+
--text-secondary: var(--color-neutral-600);
|
|
119
|
+
--text-tertiary: var(--color-neutral-500);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.dark {
|
|
123
|
+
--background: var(--color-neutral-950);
|
|
124
|
+
--foreground: var(--color-neutral-50);
|
|
125
|
+
|
|
126
|
+
--card: var(--color-neutral-950);
|
|
127
|
+
--card-foreground: var(--color-neutral-50);
|
|
128
|
+
|
|
129
|
+
--popover: var(--color-neutral-950);
|
|
130
|
+
--popover-foreground: var(--color-neutral-50);
|
|
131
|
+
|
|
132
|
+
--primary: var(--color-neutral-50);
|
|
133
|
+
--primary-foreground: var(--color-neutral-900);
|
|
134
|
+
--primary-hover: var(--color-neutral-200);
|
|
135
|
+
|
|
136
|
+
--secondary: var(--color-neutral-800);
|
|
137
|
+
--secondary-foreground: var(--color-neutral-50);
|
|
138
|
+
--secondary-hover: var(--color-neutral-700);
|
|
139
|
+
|
|
140
|
+
--muted: var(--color-neutral-800);
|
|
141
|
+
--muted-foreground: var(--color-neutral-400);
|
|
142
|
+
|
|
143
|
+
--accent: var(--color-neutral-800);
|
|
144
|
+
--accent-foreground: var(--color-neutral-50);
|
|
145
|
+
--accent-hover: var(--color-neutral-700);
|
|
146
|
+
|
|
147
|
+
--destructive: var(--color-red-900);
|
|
148
|
+
--destructive-foreground: var(--color-neutral-50);
|
|
149
|
+
--destructive-hover: var(--color-red-800);
|
|
150
|
+
|
|
151
|
+
--border: var(--color-neutral-800);
|
|
152
|
+
--border-dark: var(--color-neutral-700);
|
|
153
|
+
--border-focus: var(--color-neutral-600);
|
|
154
|
+
--border-error: var(--color-red-500);
|
|
155
|
+
--input: var(--color-neutral-800);
|
|
156
|
+
--input-background: var(--color-neutral-950);
|
|
157
|
+
--ring: var(--color-neutral-300);
|
|
158
|
+
|
|
159
|
+
/* Text colors from design system (dark mode) */
|
|
160
|
+
--text-primary: var(--color-neutral-50);
|
|
161
|
+
--text-secondary: var(--color-neutral-400);
|
|
162
|
+
--text-tertiary: var(--color-neutral-500);
|
|
109
163
|
}
|
|
110
164
|
|
|
111
165
|
@layer base {
|
|
112
166
|
* {
|
|
113
|
-
border-color:
|
|
167
|
+
border-color: var(--border);
|
|
114
168
|
}
|
|
115
169
|
body {
|
|
116
|
-
background-color:
|
|
117
|
-
color:
|
|
170
|
+
background-color: var(--background);
|
|
171
|
+
color: var(--foreground);
|
|
118
172
|
}
|
|
119
173
|
button {
|
|
120
174
|
cursor: pointer;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible logger
|
|
3
|
-
* Outputs structured JSON logs to console with color-coding
|
|
4
|
-
*/
|
|
5
|
-
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
6
|
-
export declare class Logger {
|
|
7
|
-
private service;
|
|
8
|
-
private minLevel;
|
|
9
|
-
constructor(service: string, minLevel?: LogLevel);
|
|
10
|
-
private shouldLog;
|
|
11
|
-
private log;
|
|
12
|
-
trace(message: string, metadata?: Record<string, unknown>): void;
|
|
13
|
-
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
14
|
-
info(message: string, metadata?: Record<string, unknown>): void;
|
|
15
|
-
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
16
|
-
error(message: string, metadata?: Record<string, unknown>): void;
|
|
17
|
-
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Create a logger instance for a service
|
|
21
|
-
* @param service - Service name (e.g., "gui", "http-agent", "tui")
|
|
22
|
-
* @param minLevel - Minimum log level to display (default: "debug")
|
|
23
|
-
*/
|
|
24
|
-
export declare function createLogger(service: string, minLevel?: LogLevel): Logger;
|