@townco/ui 0.1.23 → 0.1.25
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 +1 -1
- package/dist/core/hooks/use-chat-messages.js +1 -1
- package/dist/core/hooks/use-chat-session.d.ts +1 -1
- package/dist/core/hooks/use-chat-session.js +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/store/chat-store.d.ts +1 -1
- package/dist/core/store/chat-store.js +7 -7
- 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 +1 -1
- package/dist/sdk/transports/http.js +1 -1
- package/dist/sdk/transports/stdio.js +1 -1
- package/dist/tui/components/ChatView.js +7 -7
- package/dist/tui/components/InputBox.js +2 -3
- package/dist/tui/components/SimpleTextInput.js +1 -1
- package/dist/tui/components/StatusBar.js +1 -1
- package/package.json +3 -2
- package/src/styles/global.css +138 -84
- package/dist/core/lib/logger.d.ts +0 -59
- package/dist/core/lib/logger.js +0 -191
- package/dist/tui/components/LogsPanel.d.ts +0 -5
- package/dist/tui/components/LogsPanel.js +0 -29
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { createLogger } from "@townco/core";
|
|
1
2
|
import { useCallback } from "react";
|
|
2
|
-
import { createLogger } from "../lib/logger.js";
|
|
3
3
|
import { useChatStore } from "../store/chat-store.js";
|
|
4
4
|
import { useChatMessages } from "./use-chat-messages.js";
|
|
5
5
|
const logger = createLogger("use-chat-input", "debug");
|
|
@@ -3,7 +3,7 @@ import type { AcpClient } from "../../sdk/client/index.js";
|
|
|
3
3
|
* Hook for managing chat session lifecycle
|
|
4
4
|
*/
|
|
5
5
|
export declare function useChatSession(client: AcpClient | null): {
|
|
6
|
-
connectionStatus: "
|
|
6
|
+
connectionStatus: "error" | "connecting" | "connected" | "disconnected";
|
|
7
7
|
sessionId: string | null;
|
|
8
8
|
connect: () => Promise<void>;
|
|
9
9
|
startSession: () => Promise<void>;
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { createLogger } from "@townco/core";
|
|
1
2
|
import { create } from "zustand";
|
|
2
|
-
import { createLogger } from "../lib/logger.js";
|
|
3
3
|
import { mergeToolCallUpdate } from "../schemas/tool-call.js";
|
|
4
4
|
const logger = createLogger("chat-store", "debug");
|
|
5
5
|
/**
|
|
@@ -96,14 +96,14 @@ export const useChatStore = create((set) => ({
|
|
|
96
96
|
// Apply the updates (using finalUpdates which has the max tokenUsage)
|
|
97
97
|
const messages = state.messages.map((msg) => msg.id === id ? { ...msg, ...finalUpdates } : msg);
|
|
98
98
|
// Verification logging (only if tokenUsage was updated)
|
|
99
|
-
if (updates.tokenUsage) {
|
|
99
|
+
if (updates.tokenUsage && finalUpdates.tokenUsage) {
|
|
100
100
|
const existingTokenUsage = existingMessage?.tokenUsage;
|
|
101
|
-
const
|
|
102
|
-
const inputDelta = (
|
|
101
|
+
const messageTokens = finalUpdates.tokenUsage;
|
|
102
|
+
const inputDelta = (messageTokens.inputTokens ?? 0) -
|
|
103
103
|
(existingTokenUsage?.inputTokens ?? 0);
|
|
104
|
-
const outputDelta = (
|
|
104
|
+
const outputDelta = (messageTokens.outputTokens ?? 0) -
|
|
105
105
|
(existingTokenUsage?.outputTokens ?? 0);
|
|
106
|
-
const totalDelta = (
|
|
106
|
+
const totalDelta = (messageTokens.totalTokens ?? 0) -
|
|
107
107
|
(existingTokenUsage?.totalTokens ?? 0);
|
|
108
108
|
// Calculate actual sum from updated messages
|
|
109
109
|
const messageTokenBreakdown = messages
|
|
@@ -125,7 +125,7 @@ export const useChatStore = create((set) => ({
|
|
|
125
125
|
messageId: id,
|
|
126
126
|
updates: updates.tokenUsage,
|
|
127
127
|
existing: existingTokenUsage,
|
|
128
|
-
messageMax:
|
|
128
|
+
messageMax: messageTokens,
|
|
129
129
|
delta: { inputDelta, outputDelta, totalDelta },
|
|
130
130
|
totalBilled: newTotalBilled,
|
|
131
131
|
currentContext: newCurrentContext,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VariantProps } from "class-variance-authority";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
|
-
variant?: "default" | "
|
|
4
|
+
variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
5
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
@@ -6,11 +6,11 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer", {
|
|
7
7
|
variants: {
|
|
8
8
|
variant: {
|
|
9
|
-
default: "bg-primary text-primary-foreground hover:bg-primary
|
|
10
|
-
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive
|
|
11
|
-
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
12
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary
|
|
13
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
9
|
+
default: "bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
10
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
|
|
11
|
+
outline: "border border-input bg-background hover:bg-accent-hover hover:text-accent-foreground",
|
|
12
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
|
|
13
|
+
ghost: "hover:bg-accent-hover hover:text-accent-foreground",
|
|
14
14
|
link: "text-primary underline-offset-4 hover:underline",
|
|
15
15
|
},
|
|
16
16
|
size: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { type SourceItem } from "./SourceListItem.js";
|
|
2
3
|
import type { TodoItem } from "./TodoListItem.js";
|
|
3
4
|
/**
|
|
4
5
|
* Shared tab content components for both mobile (ChatHeader) and desktop (Panel) views
|
|
@@ -12,6 +13,10 @@ export interface FilesTabContentProps extends React.HTMLAttributes<HTMLDivElemen
|
|
|
12
13
|
files?: string[];
|
|
13
14
|
}
|
|
14
15
|
export declare const FilesTabContent: React.ForwardRefExoticComponent<FilesTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
export interface SourcesTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
sources?: SourceItem[];
|
|
18
|
+
}
|
|
19
|
+
export declare const SourcesTabContent: React.ForwardRefExoticComponent<SourcesTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
15
20
|
export interface DatabaseTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
21
|
data?: unknown;
|
|
17
22
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { SourceListItem } from "./SourceListItem.js";
|
|
4
5
|
export const TodoTabContent = React.forwardRef(({ todos, className, ...props }, ref) => {
|
|
5
6
|
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: todos.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No todos yet" }) })) : (todos.map((todo) => (_jsx("div", { className: "text-sm", children: todo.text }, todo.id)))) }));
|
|
6
7
|
});
|
|
@@ -9,6 +10,10 @@ export const FilesTabContent = React.forwardRef(({ files = [], className, ...pro
|
|
|
9
10
|
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: files.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No files attached" }) })) : (files.map((file) => (_jsx("div", { className: "text-sm", children: file }, file)))) }));
|
|
10
11
|
});
|
|
11
12
|
FilesTabContent.displayName = "FilesTabContent";
|
|
13
|
+
export const SourcesTabContent = React.forwardRef(({ sources = [], className, ...props }, ref) => {
|
|
14
|
+
return (_jsx("div", { ref: ref, className: cn("space-y-2", className), ...props, children: sources.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-full min-h-[200px]", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No sources available" }) })) : (sources.map((source) => (_jsx(SourceListItem, { source: source }, source.id)))) }));
|
|
15
|
+
});
|
|
16
|
+
SourcesTabContent.displayName = "SourcesTabContent";
|
|
12
17
|
export const DatabaseTabContent = React.forwardRef(({ data, className, ...props }, ref) => {
|
|
13
18
|
return (_jsxs("div", { ref: ref, className: cn("space-y-4", className), ...props, children: [_jsx("h3", { className: "font-semibold text-lg", children: "Database" }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("p", { children: "Database viewer - panel automatically expanded to large size" }), _jsxs("div", { className: "mt-4 p-4 border border-border rounded", children: [_jsx("p", { children: "Your large data table would go here" }), data && typeof data === "object" ? (_jsx("pre", { className: "mt-2 text-xs overflow-auto", children: JSON.stringify(data, null, 2) })) : null] })] })] }));
|
|
14
19
|
});
|
|
@@ -29,6 +29,6 @@ export interface ChatSecondaryPanelProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
29
29
|
/**
|
|
30
30
|
* Which tabs to show
|
|
31
31
|
*/
|
|
32
|
-
visibleTabs?: ("todo" | "files" | "database")[];
|
|
32
|
+
visibleTabs?: ("todo" | "files" | "database" | "sources")[];
|
|
33
33
|
}
|
|
34
34
|
export declare const ChatSecondaryPanel: React.ForwardRefExoticComponent<ChatSecondaryPanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CheckSquare, Database, FileText } from "lucide-react";
|
|
2
|
+
import { CheckSquare, Database, FileText, Link2 } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
|
-
import { FilesTabContent, TodoTabContent } from "./ChatPanelTabContent.js";
|
|
6
|
+
import { FilesTabContent, SourcesTabContent, TodoTabContent, } from "./ChatPanelTabContent.js";
|
|
7
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
|
|
8
8
|
import { TodoList } from "./TodoList.js";
|
|
9
9
|
export const ChatSecondaryPanel = React.forwardRef(({ client, todos, variant = "animated", showIcons = false, visibleTabs = ["todo", "files", "database"], className, ...props }, ref) => {
|
|
@@ -49,6 +49,11 @@ export const ChatSecondaryPanel = React.forwardRef(({ client, todos, variant = "
|
|
|
49
49
|
label: "Database",
|
|
50
50
|
icon: Database,
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
id: "sources",
|
|
54
|
+
label: "Sources",
|
|
55
|
+
icon: Link2,
|
|
56
|
+
},
|
|
52
57
|
];
|
|
53
58
|
const tabs = allTabs.filter((tab) => visibleTabs.includes(tab.id));
|
|
54
59
|
return (_jsx("div", { ref: ref, className: cn("select-none", className), ...props, children: _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, className: "w-full", children: [variant === "pills" ? (
|
|
@@ -61,6 +66,6 @@ export const ChatSecondaryPanel = React.forwardRef(({ client, todos, variant = "
|
|
|
61
66
|
_jsxs("div", { className: "relative mb-4 border-border", children: [_jsx(TabsList, { className: "bg-transparent p-0 h-auto rounded-none w-full border-none", children: tabs.map((tab) => (_jsx(TabsTrigger, { value: tab.id, className: "px-3 py-1 text-sm font-[var(--font-family)] font-medium rounded-none text-foreground opacity-60 data-[state=active]:opacity-100 data-[state=active]:bg-transparent data-[state=active]:shadow-none", children: tab.label }, tab.id))) }), _jsx("div", { ref: containerRef, className: "absolute top-0 left-0 w-full overflow-hidden z-10 pointer-events-none", style: {
|
|
62
67
|
clipPath: "inset(0 100% 0 0% round 999px)",
|
|
63
68
|
transition: "clip-path 0.25s ease-out",
|
|
64
|
-
}, children: _jsx(TabsList, { className: "bg-secondary p-0 h-auto rounded-none w-full border-none", children: tabs.map((tab) => (_jsx(TabsTrigger, { value: tab.id, ref: activeTab === tab.id ? activeTabElementRef : null, className: "px-3 py-1 text-sm font-[var(--font-family)] font-medium rounded-none text-primary bg-transparent data-[state=active]:shadow-none shadow-none", tabIndex: -1, children: tab.label }, tab.id))) }) })] })), _jsx(TabsContent, { value: "todo", className: variant === "pills" ? "mt-0" : "", children: variant === "pills" ? (_jsx(TodoTabContent, { todos: todosToDisplay })) : (_jsx(TodoList, { todos: todosToDisplay })) }), _jsx(TabsContent, { value: "files", className: variant === "pills" ? "mt-0" : "", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "database", className: variant === "pills" ? "mt-0" : "", children: _jsx("div", { className: "text-sm text-foreground opacity-60 italic", children: "Database tab coming soon..." }) })] }) }));
|
|
69
|
+
}, children: _jsx(TabsList, { className: "bg-secondary p-0 h-auto rounded-none w-full border-none", children: tabs.map((tab) => (_jsx(TabsTrigger, { value: tab.id, ref: activeTab === tab.id ? activeTabElementRef : null, className: "px-3 py-1 text-sm font-[var(--font-family)] font-medium rounded-none text-primary bg-transparent data-[state=active]:shadow-none shadow-none", tabIndex: -1, children: tab.label }, tab.id))) }) })] })), _jsx(TabsContent, { value: "todo", className: variant === "pills" ? "mt-0" : "", children: variant === "pills" ? (_jsx(TodoTabContent, { todos: todosToDisplay })) : (_jsx(TodoList, { todos: todosToDisplay })) }), _jsx(TabsContent, { value: "files", className: variant === "pills" ? "mt-0" : "", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "database", className: variant === "pills" ? "mt-0" : "", children: _jsx("div", { className: "text-sm text-foreground opacity-60 italic", children: "Database tab coming soon..." }) }), _jsx(TabsContent, { value: "sources", className: variant === "pills" ? "mt-0" : "", children: _jsx(SourcesTabContent, {}) })] }) }));
|
|
65
70
|
});
|
|
66
71
|
ChatSecondaryPanel.displayName = "ChatSecondaryPanel";
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
-
const inputVariants = cva("flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", {
|
|
5
|
+
const inputVariants = cva("flex h-10 w-full rounded-md border bg-input-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", {
|
|
6
6
|
variants: {
|
|
7
7
|
variant: {
|
|
8
8
|
default: "border-input focus-visible:ring-ring",
|
|
@@ -8,7 +8,7 @@ export interface PanelTabsHeaderProps extends Omit<React.ComponentPropsWithoutRe
|
|
|
8
8
|
/**
|
|
9
9
|
* Which tabs to show
|
|
10
10
|
*/
|
|
11
|
-
visibleTabs?: ("todo" | "files" | "database")[];
|
|
11
|
+
visibleTabs?: ("todo" | "files" | "database" | "sources")[];
|
|
12
12
|
/**
|
|
13
13
|
* Styling variant
|
|
14
14
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CheckSquare, Database, FileText } from "lucide-react";
|
|
2
|
+
import { CheckSquare, Database, FileText, Link2 } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { TabsList, TabsTrigger } from "./Tabs.js";
|
|
@@ -20,6 +20,11 @@ export const PanelTabsHeader = React.forwardRef(({ showIcons = true, visibleTabs
|
|
|
20
20
|
label: "Database",
|
|
21
21
|
icon: Database,
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
id: "sources",
|
|
25
|
+
label: "Sources",
|
|
26
|
+
icon: Link2,
|
|
27
|
+
},
|
|
23
28
|
];
|
|
24
29
|
const tabs = allTabs.filter((tab) => visibleTabs.includes(tab.id));
|
|
25
30
|
const gap = variant === "compact" ? "gap-[4px]" : "gap-3";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface SourceItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
url: string;
|
|
6
|
+
snippet: string;
|
|
7
|
+
sourceName: string;
|
|
8
|
+
favicon?: string;
|
|
9
|
+
timestamp?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SourceListItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
12
|
+
source: SourceItem;
|
|
13
|
+
isSelected?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare const SourceListItem: React.ForwardRefExoticComponent<SourceListItemProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
export const SourceListItem = React.forwardRef(({ source, isSelected, className, ...props }, ref) => {
|
|
5
|
+
return (_jsxs("button", { ref: ref, type: "button", className: cn("flex w-full text-left gap-2 items-start p-3 rounded-lg transition-colors cursor-pointer border border-transparent", "hover:bg-accent-hover hover:border-border/50", isSelected && "bg-accent-hover border-border/50", className), onClick: () => window.open(source.url, "_blank"), ...props, children: [_jsx("div", { className: "flex gap-2 items-center py-[2px] shrink-0", children: _jsx("div", { className: "relative rounded-[3px] shrink-0 size-4 overflow-hidden bg-muted", children: source.favicon ? (_jsx("img", { alt: source.sourceName, className: "size-full object-cover", src: source.favicon })) : (_jsx("div", { className: "size-full bg-muted" })) }) }), _jsxs("div", { className: "flex flex-1 flex-col gap-1 min-w-0", children: [_jsxs("div", { className: "text-xs leading-normal text-foreground", children: [_jsx("span", { className: "font-medium", children: source.sourceName }), _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", source.title] })] }), _jsx("p", { className: "text-xs leading-relaxed text-muted-foreground line-clamp-3", children: source.snippet })] })] }));
|
|
6
|
+
});
|
|
7
|
+
SourceListItem.displayName = "SourceListItem";
|
|
@@ -14,7 +14,7 @@ const statusStyles = {
|
|
|
14
14
|
/**
|
|
15
15
|
* Tool call kind icons (using emoji for simplicity)
|
|
16
16
|
*/
|
|
17
|
-
const
|
|
17
|
+
const _kindIcons = {
|
|
18
18
|
read: "\u{1F4C4}",
|
|
19
19
|
edit: "\u{270F}\u{FE0F}",
|
|
20
20
|
delete: "\u{1F5D1}\u{FE0F}",
|
|
@@ -31,9 +31,9 @@ const kindIcons = {
|
|
|
31
31
|
*/
|
|
32
32
|
export function ToolCall({ toolCall }) {
|
|
33
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-
|
|
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-muted-foreground 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-border rounded-md p-3 bg-card shadow-sm max-w-full", children: [toolCall.locations && toolCall.locations.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-semibold text-muted-foreground 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-foreground bg-muted px-2 py-1 rounded", children: [loc.path, loc.line !== null &&
|
|
35
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-
|
|
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-muted-foreground uppercase tracking-wide mb-1.5", children: "Input" }), _jsx("div", { className: "bg-muted p-2 rounded border border-border", children: _jsx(JsonView, { value: toolCall.rawInput, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: { fontSize: "11px", backgroundColor: "transparent" } }) })] })), toolCall.content && toolCall.content.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-1.5", children: "Output" }), _jsx("div", { className: "space-y-2", children: toolCall.content.map((block, idx) => {
|
|
37
37
|
// Generate a stable key based on content
|
|
38
38
|
const getBlockKey = () => {
|
|
39
39
|
if (block.type === "diff" && "path" in block) {
|
|
@@ -58,7 +58,7 @@ export function ToolCall({ toolCall }) {
|
|
|
58
58
|
const parsed = JSON.parse(text);
|
|
59
59
|
// If it's an object or array, render with JsonView
|
|
60
60
|
if (typeof parsed === "object" && parsed !== null) {
|
|
61
|
-
return (_jsx("div", { className: "bg-
|
|
61
|
+
return (_jsx("div", { className: "bg-muted p-2 rounded border border-border", children: _jsx(JsonView, { value: parsed, collapsed: false, displayDataTypes: false, displayObjectSize: false, enableClipboard: true, style: {
|
|
62
62
|
fontSize: "11px",
|
|
63
63
|
backgroundColor: "transparent",
|
|
64
64
|
} }) }, key));
|
|
@@ -68,7 +68,7 @@ export function ToolCall({ toolCall }) {
|
|
|
68
68
|
// Not valid JSON, render as plain text
|
|
69
69
|
}
|
|
70
70
|
// Render as plain text
|
|
71
|
-
return (_jsx("pre", { className: "bg-
|
|
71
|
+
return (_jsx("pre", { className: "bg-muted p-2.5 rounded border border-border text-xs overflow-x-auto font-mono text-foreground", children: text }, key));
|
|
72
72
|
};
|
|
73
73
|
// Handle nested content blocks (ACP format)
|
|
74
74
|
if (block.type === "content" && "content" in block) {
|
|
@@ -86,7 +86,7 @@ export function ToolCall({ toolCall }) {
|
|
|
86
86
|
"path" in block &&
|
|
87
87
|
"oldText" in block &&
|
|
88
88
|
"newText" in block) {
|
|
89
|
-
return (_jsxs("div", { className: "border rounded", children: [_jsxs("div", { className: "bg-
|
|
89
|
+
return (_jsxs("div", { className: "border rounded", children: [_jsxs("div", { className: "bg-muted px-2 py-1 text-xs font-mono", children: [block.path, "line" in block &&
|
|
90
90
|
block.line !== null &&
|
|
91
91
|
block.line !== undefined &&
|
|
92
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()));
|
|
@@ -96,5 +96,5 @@ export function ToolCall({ toolCall }) {
|
|
|
96
96
|
return (_jsxs("div", { className: "bg-black text-green-400 p-2 rounded text-xs font-mono", children: ["Terminal: ", block.terminalId] }, getBlockKey()));
|
|
97
97
|
}
|
|
98
98
|
return null;
|
|
99
|
-
}) })] })), toolCall.error && (_jsxs("div", { className: "bg-
|
|
99
|
+
}) })] })), toolCall.error && (_jsxs("div", { className: "bg-destructive/10 border border-destructive/20 rounded p-2.5 text-destructive", children: [_jsx("div", { className: "text-xs font-semibold text-destructive uppercase tracking-wide mb-1.5", children: "Error" }), _jsx("div", { className: "text-xs", children: toolCall.error })] })), toolCall.tokenUsage && (_jsxs("div", { className: "bg-muted border border-border rounded p-2.5", children: [_jsx("div", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: "Token Usage" }), _jsxs("div", { className: "grid grid-cols-3 gap-3 text-xs text-foreground", children: [toolCall.tokenUsage.inputTokens !== undefined && (_jsxs("div", { children: [_jsx("div", { className: "text-muted-foreground 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-muted-foreground 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-muted-foreground 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-muted-foreground", 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
100
|
}
|
|
@@ -6,7 +6,7 @@ export type { ConnectionStatus } from "./ChatHeader.js";
|
|
|
6
6
|
export * as ChatHeader from "./ChatHeader.js";
|
|
7
7
|
export { Actions as ChatInputActions, Attachment as ChatInputAttachment, type ChatInputActionsProps, type ChatInputAttachmentProps, type ChatInputCommandMenuProps, type ChatInputFieldProps, type ChatInputRootProps, type ChatInputSubmitProps, type ChatInputToolbarProps, type ChatInputVoiceInputProps, CommandMenu as ChatInputCommandMenu, type CommandMenuItem, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
|
|
8
8
|
export * as ChatLayout from "./ChatLayout.js";
|
|
9
|
-
export { DatabaseTabContent, type DatabaseTabContentProps, FilesTabContent, type FilesTabContentProps, TodoTabContent, type TodoTabContentProps, } from "./ChatPanelTabContent.js";
|
|
9
|
+
export { DatabaseTabContent, type DatabaseTabContentProps, FilesTabContent, type FilesTabContentProps, SourcesTabContent, type SourcesTabContentProps, TodoTabContent, type TodoTabContentProps, } from "./ChatPanelTabContent.js";
|
|
10
10
|
export { ChatSecondaryPanel, type ChatSecondaryPanelProps, } from "./ChatSecondaryPanel.js";
|
|
11
11
|
export * as ChatSidebar from "./ChatSidebar.js";
|
|
12
12
|
export { ChatStatus, type ChatStatusProps } from "./ChatStatus.js";
|
|
@@ -25,6 +25,7 @@ export { Reasoning, type ReasoningProps } from "./Reasoning.js";
|
|
|
25
25
|
export { Response, type ResponseProps } from "./Response.js";
|
|
26
26
|
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
|
|
27
27
|
export { Toaster } from "./Sonner.js";
|
|
28
|
+
export { type SourceItem, SourceListItem, type SourceListItemProps, } from "./SourceListItem.js";
|
|
28
29
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
|
|
29
30
|
export { Task, type TaskItem, TaskList, type TaskListProps, type TaskProps, } from "./Task.js";
|
|
30
31
|
export { Textarea, type TextareaProps, textareaVariants } from "./Textarea.js";
|
|
@@ -8,7 +8,7 @@ export * as ChatHeader from "./ChatHeader.js";
|
|
|
8
8
|
export { Actions as ChatInputActions, Attachment as ChatInputAttachment, CommandMenu as ChatInputCommandMenu, Field as ChatInputField, Root as ChatInputRoot, Submit as ChatInputSubmit, Toolbar as ChatInputToolbar, VoiceInput as ChatInputVoiceInput, } from "./ChatInput.js";
|
|
9
9
|
// Chat layout components
|
|
10
10
|
export * as ChatLayout from "./ChatLayout.js";
|
|
11
|
-
export { DatabaseTabContent, FilesTabContent, TodoTabContent, } from "./ChatPanelTabContent.js";
|
|
11
|
+
export { DatabaseTabContent, FilesTabContent, SourcesTabContent, TodoTabContent, } from "./ChatPanelTabContent.js";
|
|
12
12
|
export { ChatSecondaryPanel, } from "./ChatSecondaryPanel.js";
|
|
13
13
|
export * as ChatSidebar from "./ChatSidebar.js";
|
|
14
14
|
export { ChatStatus } from "./ChatStatus.js";
|
|
@@ -29,8 +29,9 @@ export { PanelTabsHeader, } from "./PanelTabsHeader.js";
|
|
|
29
29
|
export { Reasoning } from "./Reasoning.js";
|
|
30
30
|
export { Response } from "./Response.js";
|
|
31
31
|
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, } from "./Select.js";
|
|
32
|
-
// Toast components
|
|
33
32
|
export { Toaster } from "./Sonner.js";
|
|
33
|
+
// Toast components
|
|
34
|
+
export { SourceListItem, } from "./SourceListItem.js";
|
|
34
35
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs.js";
|
|
35
36
|
// Task/Todo components
|
|
36
37
|
export { Task, TaskList, } from "./Task.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLogger } from "
|
|
1
|
+
import { createLogger } from "@townco/core";
|
|
2
2
|
import { HttpTransport } from "../transports/http.js";
|
|
3
3
|
import { StdioTransport } from "../transports/stdio.js";
|
|
4
4
|
import { WebSocketTransport } from "../transports/websocket.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { Readable, Writable } from "node:stream";
|
|
3
3
|
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
|
|
4
|
-
import { createLogger } from "
|
|
4
|
+
import { createLogger } from "@townco/core";
|
|
5
5
|
const logger = createLogger("stdio-transport");
|
|
6
6
|
/**
|
|
7
7
|
* Stdio transport implementation using Agent Client Protocol SDK
|
|
@@ -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) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
|
-
import { useState } from "react";
|
|
4
3
|
import { SimpleTextInput } from "./SimpleTextInput.js";
|
|
5
4
|
export function InputBox({ value, isSubmitting, attachedFiles, onChange, onSubmit, onEscape, }) {
|
|
6
5
|
const { stdout } = useStdout();
|
|
@@ -17,13 +16,13 @@ export function InputBox({ value, isSubmitting, attachedFiles, onChange, onSubmi
|
|
|
17
16
|
return;
|
|
18
17
|
// Shift+Enter or Alt+Enter: insert newline
|
|
19
18
|
if (key.return && (key.shift || key.meta)) {
|
|
20
|
-
onChange(value
|
|
19
|
+
onChange(`${value}\n`);
|
|
21
20
|
return;
|
|
22
21
|
}
|
|
23
22
|
});
|
|
24
23
|
// Split value into lines for display
|
|
25
24
|
const lines = value.split("\n");
|
|
26
|
-
const
|
|
25
|
+
const _hasMultipleLines = lines.length > 1;
|
|
27
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)" })) })] }) })] }));
|
|
28
27
|
}
|
|
29
28
|
function formatFileSize(bytes) {
|
|
@@ -136,7 +136,7 @@ export function SimpleTextInput({ value, onChange, onSubmit, placeholder = "", }
|
|
|
136
136
|
const newValue = value.slice(0, cursorPos) + input + value.slice(cursorPos);
|
|
137
137
|
// If user types backslash at the end, automatically add newline
|
|
138
138
|
if (input === "\\" && cursorPos === value.length) {
|
|
139
|
-
onChange(newValue.slice(0, -1)
|
|
139
|
+
onChange(`${newValue.slice(0, -1)}\n`);
|
|
140
140
|
// Reset cursor to end (no offset)
|
|
141
141
|
setCursorOffset(0);
|
|
142
142
|
}
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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.3",
|
|
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,7 +62,7 @@
|
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@tailwindcss/postcss": "^4.1.17",
|
|
64
|
-
"@townco/tsconfig": "0.1.
|
|
65
|
+
"@townco/tsconfig": "0.1.22",
|
|
65
66
|
"@types/node": "^24.10.0",
|
|
66
67
|
"@types/react": "^19.2.2",
|
|
67
68
|
"ink": "^6.4.0",
|
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,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible logger
|
|
3
|
-
* Outputs structured JSON logs to console with color-coding
|
|
4
|
-
* Also captures logs to a global store for in-app viewing
|
|
5
|
-
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
6
|
-
*/
|
|
7
|
-
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
8
|
-
export interface LogEntry {
|
|
9
|
-
id: string;
|
|
10
|
-
timestamp: string;
|
|
11
|
-
level: LogLevel;
|
|
12
|
-
service: string;
|
|
13
|
-
message: string;
|
|
14
|
-
metadata?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Get all captured logs
|
|
18
|
-
*/
|
|
19
|
-
export declare function getCapturedLogs(): LogEntry[];
|
|
20
|
-
/**
|
|
21
|
-
* Clear all captured logs
|
|
22
|
-
*/
|
|
23
|
-
export declare function clearCapturedLogs(): void;
|
|
24
|
-
/**
|
|
25
|
-
* Subscribe to log updates
|
|
26
|
-
*/
|
|
27
|
-
type LogSubscriber = (entry: LogEntry) => void;
|
|
28
|
-
export declare function subscribeToLogs(callback: LogSubscriber): () => void;
|
|
29
|
-
/**
|
|
30
|
-
* Configure global logs directory for file writing
|
|
31
|
-
* Must be called before creating any loggers (typically at TUI startup)
|
|
32
|
-
*/
|
|
33
|
-
export declare function configureLogsDir(logsDir: string): void;
|
|
34
|
-
export declare class Logger {
|
|
35
|
-
private service;
|
|
36
|
-
private minLevel;
|
|
37
|
-
private logFilePath?;
|
|
38
|
-
private logsDir?;
|
|
39
|
-
private writeQueue;
|
|
40
|
-
private isWriting;
|
|
41
|
-
constructor(service: string, minLevel?: LogLevel);
|
|
42
|
-
private setupFileLogging;
|
|
43
|
-
private writeToFile;
|
|
44
|
-
private shouldLog;
|
|
45
|
-
private log;
|
|
46
|
-
trace(message: string, metadata?: Record<string, unknown>): void;
|
|
47
|
-
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
48
|
-
info(message: string, metadata?: Record<string, unknown>): void;
|
|
49
|
-
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
50
|
-
error(message: string, metadata?: Record<string, unknown>): void;
|
|
51
|
-
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Create a logger instance for a service
|
|
55
|
-
* @param service - Service name (e.g., "gui", "http-agent", "tui")
|
|
56
|
-
* @param minLevel - Minimum log level to display (default: "debug")
|
|
57
|
-
*/
|
|
58
|
-
export declare function createLogger(service: string, minLevel?: LogLevel): Logger;
|
|
59
|
-
export {};
|
package/dist/core/lib/logger.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible logger
|
|
3
|
-
* Outputs structured JSON logs to console with color-coding
|
|
4
|
-
* Also captures logs to a global store for in-app viewing
|
|
5
|
-
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
6
|
-
*/
|
|
7
|
-
// Check if running in Node.js
|
|
8
|
-
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
9
|
-
// Global logs directory configuration (set once at app startup for TUI)
|
|
10
|
-
let globalLogsDir;
|
|
11
|
-
// Global log store
|
|
12
|
-
const globalLogStore = [];
|
|
13
|
-
let logIdCounter = 0;
|
|
14
|
-
/**
|
|
15
|
-
* Get all captured logs
|
|
16
|
-
*/
|
|
17
|
-
export function getCapturedLogs() {
|
|
18
|
-
return [...globalLogStore];
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Clear all captured logs
|
|
22
|
-
*/
|
|
23
|
-
export function clearCapturedLogs() {
|
|
24
|
-
globalLogStore.length = 0;
|
|
25
|
-
}
|
|
26
|
-
const logSubscribers = new Set();
|
|
27
|
-
export function subscribeToLogs(callback) {
|
|
28
|
-
logSubscribers.add(callback);
|
|
29
|
-
return () => logSubscribers.delete(callback);
|
|
30
|
-
}
|
|
31
|
-
function notifyLogSubscribers(entry) {
|
|
32
|
-
for (const callback of logSubscribers) {
|
|
33
|
-
callback(entry);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Configure global logs directory for file writing
|
|
38
|
-
* Must be called before creating any loggers (typically at TUI startup)
|
|
39
|
-
*/
|
|
40
|
-
export function configureLogsDir(logsDir) {
|
|
41
|
-
globalLogsDir = logsDir;
|
|
42
|
-
}
|
|
43
|
-
const LOG_LEVELS = {
|
|
44
|
-
trace: 0,
|
|
45
|
-
debug: 1,
|
|
46
|
-
info: 2,
|
|
47
|
-
warn: 3,
|
|
48
|
-
error: 4,
|
|
49
|
-
fatal: 5,
|
|
50
|
-
};
|
|
51
|
-
const _LOG_COLORS = {
|
|
52
|
-
trace: "#6B7280", // gray
|
|
53
|
-
debug: "#3B82F6", // blue
|
|
54
|
-
info: "#10B981", // green
|
|
55
|
-
warn: "#F59E0B", // orange
|
|
56
|
-
error: "#EF4444", // red
|
|
57
|
-
fatal: "#DC2626", // dark red
|
|
58
|
-
};
|
|
59
|
-
const _LOG_STYLES = {
|
|
60
|
-
trace: "color: #6B7280",
|
|
61
|
-
debug: "color: #3B82F6; font-weight: bold",
|
|
62
|
-
info: "color: #10B981; font-weight: bold",
|
|
63
|
-
warn: "color: #F59E0B; font-weight: bold",
|
|
64
|
-
error: "color: #EF4444; font-weight: bold",
|
|
65
|
-
fatal: "color: #DC2626; font-weight: bold; background: #FEE2E2",
|
|
66
|
-
};
|
|
67
|
-
export class Logger {
|
|
68
|
-
service;
|
|
69
|
-
minLevel;
|
|
70
|
-
logFilePath;
|
|
71
|
-
logsDir;
|
|
72
|
-
writeQueue = [];
|
|
73
|
-
isWriting = false;
|
|
74
|
-
constructor(service, minLevel = "debug") {
|
|
75
|
-
this.service = service;
|
|
76
|
-
this.minLevel = minLevel;
|
|
77
|
-
// In production, suppress trace and debug logs
|
|
78
|
-
if (typeof process !== "undefined" &&
|
|
79
|
-
process.env?.NODE_ENV === "production") {
|
|
80
|
-
this.minLevel = "info";
|
|
81
|
-
}
|
|
82
|
-
// Note: File logging setup is done lazily in log() method
|
|
83
|
-
// This allows loggers created before configureLogsDir() to still write to files
|
|
84
|
-
}
|
|
85
|
-
setupFileLogging() {
|
|
86
|
-
if (!isNode || !globalLogsDir)
|
|
87
|
-
return;
|
|
88
|
-
try {
|
|
89
|
-
// Dynamic import for Node.js modules
|
|
90
|
-
const path = require("node:path");
|
|
91
|
-
const fs = require("node:fs");
|
|
92
|
-
this.logsDir = globalLogsDir;
|
|
93
|
-
this.logFilePath = path.join(this.logsDir, `${this.service}.log`);
|
|
94
|
-
// Create logs directory if it doesn't exist
|
|
95
|
-
if (!fs.existsSync(this.logsDir)) {
|
|
96
|
-
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (_error) {
|
|
100
|
-
// Silently fail if we can't set up file logging
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async writeToFile(content) {
|
|
104
|
-
if (!this.logFilePath || !isNode)
|
|
105
|
-
return;
|
|
106
|
-
this.writeQueue.push(content);
|
|
107
|
-
if (this.isWriting) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
this.isWriting = true;
|
|
111
|
-
while (this.writeQueue.length > 0) {
|
|
112
|
-
const batch = this.writeQueue.splice(0, this.writeQueue.length);
|
|
113
|
-
const data = `${batch.join("\n")}\n`;
|
|
114
|
-
try {
|
|
115
|
-
// Dynamic import for Node.js modules
|
|
116
|
-
const fs = require("node:fs");
|
|
117
|
-
await fs.promises.appendFile(this.logFilePath, data, "utf-8");
|
|
118
|
-
}
|
|
119
|
-
catch (_error) {
|
|
120
|
-
// Silently fail
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
this.isWriting = false;
|
|
124
|
-
}
|
|
125
|
-
shouldLog(level) {
|
|
126
|
-
return LOG_LEVELS[level] >= LOG_LEVELS[this.minLevel];
|
|
127
|
-
}
|
|
128
|
-
log(level, message, metadata) {
|
|
129
|
-
if (!this.shouldLog(level)) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const entry = {
|
|
133
|
-
id: `log_${++logIdCounter}`,
|
|
134
|
-
timestamp: new Date().toISOString(),
|
|
135
|
-
level,
|
|
136
|
-
service: this.service,
|
|
137
|
-
message,
|
|
138
|
-
...(metadata && { metadata }),
|
|
139
|
-
};
|
|
140
|
-
// Store in global log store
|
|
141
|
-
globalLogStore.push(entry);
|
|
142
|
-
// Notify subscribers
|
|
143
|
-
notifyLogSubscribers(entry);
|
|
144
|
-
// Write to file in Node.js (for logs tab to read)
|
|
145
|
-
// Lazily set up file logging if globalLogsDir was configured after this logger was created
|
|
146
|
-
if (isNode && !this.logFilePath && globalLogsDir) {
|
|
147
|
-
this.setupFileLogging();
|
|
148
|
-
}
|
|
149
|
-
if (isNode && this.logFilePath) {
|
|
150
|
-
// Write as JSON without the id field (to match expected format)
|
|
151
|
-
const fileEntry = {
|
|
152
|
-
timestamp: entry.timestamp,
|
|
153
|
-
level: entry.level,
|
|
154
|
-
service: entry.service,
|
|
155
|
-
message: entry.message,
|
|
156
|
-
...(entry.metadata && { metadata: entry.metadata }),
|
|
157
|
-
};
|
|
158
|
-
this.writeToFile(JSON.stringify(fileEntry)).catch(() => {
|
|
159
|
-
// Silently fail
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
// No console output - logs are only captured and displayed in UI
|
|
163
|
-
// This prevents logs from polluting stdout/stderr in TUI mode
|
|
164
|
-
}
|
|
165
|
-
trace(message, metadata) {
|
|
166
|
-
this.log("trace", message, metadata);
|
|
167
|
-
}
|
|
168
|
-
debug(message, metadata) {
|
|
169
|
-
this.log("debug", message, metadata);
|
|
170
|
-
}
|
|
171
|
-
info(message, metadata) {
|
|
172
|
-
this.log("info", message, metadata);
|
|
173
|
-
}
|
|
174
|
-
warn(message, metadata) {
|
|
175
|
-
this.log("warn", message, metadata);
|
|
176
|
-
}
|
|
177
|
-
error(message, metadata) {
|
|
178
|
-
this.log("error", message, metadata);
|
|
179
|
-
}
|
|
180
|
-
fatal(message, metadata) {
|
|
181
|
-
this.log("fatal", message, metadata);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Create a logger instance for a service
|
|
186
|
-
* @param service - Service name (e.g., "gui", "http-agent", "tui")
|
|
187
|
-
* @param minLevel - Minimum log level to display (default: "debug")
|
|
188
|
-
*/
|
|
189
|
-
export function createLogger(service, minLevel = "debug") {
|
|
190
|
-
return new Logger(service, minLevel);
|
|
191
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { subscribeToLogs } from "../../core/lib/logger.js";
|
|
5
|
-
// Color mapping for log levels
|
|
6
|
-
const LOG_LEVEL_COLORS = {
|
|
7
|
-
trace: "gray",
|
|
8
|
-
debug: "blue",
|
|
9
|
-
info: "green",
|
|
10
|
-
warn: "yellow",
|
|
11
|
-
error: "red",
|
|
12
|
-
fatal: "red",
|
|
13
|
-
};
|
|
14
|
-
export function LogsPanel({ logs: initialLogs }) {
|
|
15
|
-
const [logs, setLogs] = useState(initialLogs);
|
|
16
|
-
// Subscribe to new logs
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const unsubscribe = subscribeToLogs((entry) => {
|
|
19
|
-
setLogs((prev) => [...prev, entry]);
|
|
20
|
-
});
|
|
21
|
-
return unsubscribe;
|
|
22
|
-
}, []);
|
|
23
|
-
if (logs.length === 0) {
|
|
24
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(Text, { dimColor: true, children: "No logs yet..." }) }));
|
|
25
|
-
}
|
|
26
|
-
// Show last 100 logs
|
|
27
|
-
const displayLogs = logs.slice(-100);
|
|
28
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: displayLogs.map((log) => (_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { dimColor: true, children: new Date(log.timestamp).toLocaleTimeString() }), _jsxs(Text, { color: LOG_LEVEL_COLORS[log.level], bold: true, children: ["[", log.level.toUpperCase(), "]"] }), _jsxs(Text, { dimColor: true, children: ["[", log.service, "]"] }), _jsx(Text, { children: log.message }), log.metadata && (_jsx(Text, { dimColor: true, children: JSON.stringify(log.metadata) }))] }, log.id))) }));
|
|
29
|
-
}
|