@townco/ui 0.1.33 → 0.1.34
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-session.d.ts +1 -1
- package/dist/core/schemas/chat.d.ts +1 -1
- package/dist/gui/components/Button.js +1 -1
- package/dist/gui/components/ChatEmptyState.js +1 -1
- package/dist/gui/components/ChatHeader.js +2 -2
- package/dist/gui/components/ChatInput.js +1 -1
- package/dist/gui/components/ChatInputCommandMenu.js +1 -1
- package/dist/gui/components/ChatLayout.js +1 -1
- package/dist/gui/components/ChatPanelTabContent.d.ts +3 -0
- package/dist/gui/components/ChatPanelTabContent.js +24 -5
- package/dist/gui/components/ChatSecondaryPanel.js +3 -3
- package/dist/gui/components/ChatView.js +28 -9
- package/dist/gui/components/Dialog.js +2 -2
- package/dist/gui/components/DropdownMenu.js +7 -7
- package/dist/gui/components/FileSystemItem.d.ts +17 -0
- package/dist/gui/components/FileSystemItem.js +81 -0
- package/dist/gui/components/FileSystemView.d.ts +14 -0
- package/dist/gui/components/FileSystemView.js +46 -0
- package/dist/gui/components/Input.js +1 -1
- package/dist/gui/components/Label.js +1 -1
- package/dist/gui/components/MarkdownRenderer.js +5 -5
- package/dist/gui/components/Message.d.ts +1 -1
- package/dist/gui/components/MessageContent.js +8 -8
- package/dist/gui/components/PanelTabsHeader.js +1 -1
- package/dist/gui/components/Reasoning.js +2 -2
- package/dist/gui/components/Response.js +13 -11
- package/dist/gui/components/Select.js +3 -3
- package/dist/gui/components/SourceListItem.js +1 -1
- package/dist/gui/components/Tabs.js +1 -1
- package/dist/gui/components/Task.js +2 -2
- package/dist/gui/components/Textarea.js +1 -1
- package/dist/gui/components/ThinkingBlock.js +2 -2
- package/dist/gui/components/TodoList.js +1 -1
- package/dist/gui/components/ToolCall.js +67 -70
- package/dist/gui/components/ToolCallList.js +1 -1
- package/dist/gui/components/index.d.ts +4 -0
- package/dist/gui/components/index.js +4 -0
- package/dist/gui/data/mockFileSystemData.d.ts +21 -0
- package/dist/gui/data/mockFileSystemData.js +127 -0
- package/dist/gui/types/filesystem.d.ts +27 -0
- package/dist/gui/types/filesystem.js +5 -0
- package/dist/sdk/schemas/session.d.ts +12 -12
- package/package.json +3 -3
- package/src/styles/global.css +108 -0
- 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
|
@@ -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, initialSessionId?: string | null): {
|
|
6
|
-
connectionStatus: "
|
|
6
|
+
connectionStatus: "error" | "connecting" | "connected" | "disconnected";
|
|
7
7
|
sessionId: string | null;
|
|
8
8
|
connect: () => Promise<void>;
|
|
9
9
|
loadSession: (sessionIdToLoad: string) => Promise<void>;
|
|
@@ -193,8 +193,8 @@ export type ChatSessionState = z.infer<typeof ChatSessionState>;
|
|
|
193
193
|
*/
|
|
194
194
|
export declare const ConnectionStatus: z.ZodEnum<{
|
|
195
195
|
error: "error";
|
|
196
|
-
disconnected: "disconnected";
|
|
197
196
|
connecting: "connecting";
|
|
198
197
|
connected: "connected";
|
|
198
|
+
disconnected: "disconnected";
|
|
199
199
|
}>;
|
|
200
200
|
export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
|
|
@@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
|
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
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", {
|
|
6
|
+
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-paragraph-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
9
|
default: "bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
@@ -17,6 +17,6 @@ export const ChatEmptyState = React.forwardRef(({ title, description, guideUrl,
|
|
|
17
17
|
for (let i = 0; i < suggestedPrompts.length; i += 2) {
|
|
18
18
|
promptRows.push(suggestedPrompts.slice(i, i + 2));
|
|
19
19
|
}
|
|
20
|
-
return (_jsxs("div", { ref: ref, className: cn("flex flex-col items-start gap-6", className), ...props, children: [_jsx("h3", { className: "text-heading-
|
|
20
|
+
return (_jsxs("div", { ref: ref, className: cn("flex flex-col items-start gap-6", className), ...props, children: [_jsx("h3", { className: "text-heading-4 text-text-primary", children: title }), _jsx("p", { className: "text-subheading text-text-secondary max-w-prose", children: description }), (guideUrl || onGuideClick) && (_jsxs("button", { type: "button", onClick: handleGuideClick, className: "flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-accent transition-colors", children: [_jsx("span", { className: "text-paragraph-sm font-medium leading-normal text-text-primary", children: guideText }), _jsx(ChevronRight, { className: "size-4 text-text-primary" })] })), suggestedPrompts.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3 w-full max-w-prompt-container", children: [_jsx("p", { className: "text-label text-text-tertiary", children: "Suggested Prompts" }), _jsx("div", { className: "flex flex-col gap-2.5", children: promptRows.map((row) => (_jsx("div", { className: "flex gap-2.5 items-center", children: row.map((prompt) => (_jsx("button", { type: "button", onClick: () => handlePromptClick(prompt), className: "flex-1 flex items-start gap-2 p-3 bg-secondary hover:bg-secondary/80 rounded-2xl transition-colors min-w-0", children: _jsx("span", { className: "text-paragraph font-normal leading-normal text-text-tertiary truncate", children: prompt }) }, prompt))) }, row.join("-")))) })] }))] }));
|
|
21
21
|
});
|
|
22
22
|
ChatEmptyState.displayName = "ChatEmptyState";
|
|
@@ -27,7 +27,7 @@ const ChatHeaderRoot = React.forwardRef(({ defaultExpanded = false, expanded: ex
|
|
|
27
27
|
});
|
|
28
28
|
ChatHeaderRoot.displayName = "ChatHeader.Root";
|
|
29
29
|
const ChatHeaderTitle = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
30
|
-
return (_jsx("h1", { ref: ref, className: cn("m-0 text-
|
|
30
|
+
return (_jsx("h1", { ref: ref, className: cn("m-0 text-subheading font-semibold", className), ...props, children: children }));
|
|
31
31
|
});
|
|
32
32
|
ChatHeaderTitle.displayName = "ChatHeader.Title";
|
|
33
33
|
const ChatHeaderActions = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
@@ -61,7 +61,7 @@ const getDefaultStatusText = (status) => {
|
|
|
61
61
|
const ChatHeaderStatusIndicator = React.forwardRef(({ status, statusText, className, ...props }, ref) => {
|
|
62
62
|
const text = statusText ?? getDefaultStatusText(status);
|
|
63
63
|
const colorClass = getStatusColor(status);
|
|
64
|
-
return (_jsxs("div", { ref: ref, className: cn("flex items-center gap-2", className), ...props, children: [_jsx("div", { className: cn("h-2 w-2 rounded-full", colorClass) }), _jsx("span", { className: "text-sm text-muted-foreground", children: text })] }));
|
|
64
|
+
return (_jsxs("div", { ref: ref, className: cn("flex items-center gap-2", className), ...props, children: [_jsx("div", { className: cn("h-2 w-2 rounded-full", colorClass) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: text })] }));
|
|
65
65
|
});
|
|
66
66
|
ChatHeaderStatusIndicator.displayName = "ChatHeader.StatusIndicator";
|
|
67
67
|
const ChatHeaderToggle = React.forwardRef(({ icon, className, children, onClick, ...props }, ref) => {
|
|
@@ -201,7 +201,7 @@ const ChatInputField = React.forwardRef(({ asChild = false, className, onKeyDown
|
|
|
201
201
|
if (asChild && React.isValidElement(children)) {
|
|
202
202
|
return React.cloneElement(children, fieldProps);
|
|
203
203
|
}
|
|
204
|
-
return (_jsx("textarea", { ...fieldProps, className: cn("w-full resize-none rounded-none border-none p-4 shadow-none", "outline-none ring-0 field-sizing-content max-h-[6lh]", "bg-transparent dark:bg-transparent focus-visible:ring-0", "text-sm placeholder:text-muted-foreground", "disabled:cursor-not-allowed disabled:opacity-50", className) }));
|
|
204
|
+
return (_jsx("textarea", { ...fieldProps, className: cn("w-full resize-none rounded-none border-none p-4 shadow-none", "outline-none ring-0 field-sizing-content max-h-[6lh]", "bg-transparent dark:bg-transparent focus-visible:ring-0", "text-paragraph-sm placeholder:text-muted-foreground", "disabled:cursor-not-allowed disabled:opacity-50", className) }));
|
|
205
205
|
});
|
|
206
206
|
ChatInputField.displayName = "ChatInput.Field";
|
|
207
207
|
const ChatInputSubmit = React.forwardRef(({ asChild = false, className, disabled: disabledProp, children, ...props }, ref) => {
|
|
@@ -57,6 +57,6 @@ export const ChatInputCommandMenu = React.forwardRef(({ commands = [], showComma
|
|
|
57
57
|
if (!showCommandMenu || filteredCommands.length === 0) {
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
|
-
return (_jsxs("div", { ref: ref, className: cn("absolute bottom-full left-0 z-50 mb-2 w-full max-w-md", "rounded-md border border-border bg-card p-2 shadow-lg", className), ...props, children: [_jsx("div", { className: "text-
|
|
60
|
+
return (_jsxs("div", { ref: ref, className: cn("absolute bottom-full left-0 z-50 mb-2 w-full max-w-md", "rounded-md border border-border bg-card p-2 shadow-lg", className), ...props, children: [_jsx("div", { className: "text-caption font-semibold text-muted-foreground px-2 py-1", children: "Commands" }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: filteredCommands.map((command, index) => (_jsxs("button", { type: "button", onClick: () => command.onSelect(), className: cn("w-full rounded-sm px-2 py-2 text-left text-paragraph-sm transition-colors", "flex items-start gap-2", "hover:bg-muted", index === selectedMenuIndex && "bg-muted"), children: [command.icon && (_jsx("span", { className: "shrink-0 mt-0.5", children: command.icon })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: command.label }), command.description && (_jsx("div", { className: "text-caption text-muted-foreground truncate", children: command.description }))] })] }, command.id))) })] }));
|
|
61
61
|
});
|
|
62
62
|
ChatInputCommandMenu.displayName = "ChatInputCommandMenu";
|
|
@@ -72,7 +72,7 @@ const ChatLayoutMessages = React.forwardRef(({ className, children, onScrollChan
|
|
|
72
72
|
React.useEffect(() => {
|
|
73
73
|
checkScrollPosition();
|
|
74
74
|
}, [checkScrollPosition]);
|
|
75
|
-
return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsx("div", { ref: scrollContainerRef, className: cn("h-full overflow-y-auto", className), onScroll: handleScroll, ...props, children: _jsx("div", { className: "mx-auto max-w-chat
|
|
75
|
+
return (_jsxs("div", { className: "relative flex-1 overflow-hidden", children: [_jsx("div", { ref: scrollContainerRef, className: cn("h-full overflow-y-auto flex flex-col", className), onScroll: handleScroll, ...props, children: _jsx("div", { className: "mx-auto max-w-chat flex-1 w-full flex flex-col", children: children }) }), showScrollButton && (_jsx("button", { type: "button", onClick: scrollToBottom, className: cn("absolute bottom-4 left-1/2 -translate-x-1/2 z-10", "flex items-center justify-center p-2 rounded-full", "bg-card border border-border shadow-lg", "text-foreground", "hover:bg-accent hover:text-accent-foreground", "transition-all duration-200 ease-in-out", "animate-in fade-in slide-in-from-bottom-2"), "aria-label": "Scroll to bottom", children: _jsx(ArrowDown, { className: "size-4" }) }))] }));
|
|
76
76
|
});
|
|
77
77
|
ChatLayoutMessages.displayName = "ChatLayout.Messages";
|
|
78
78
|
const ChatLayoutFooter = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import type { FileSystemProvider } from "../types/filesystem.js";
|
|
2
3
|
import { type SourceItem } from "./SourceListItem.js";
|
|
3
4
|
import type { TodoItem } from "./TodoListItem.js";
|
|
4
5
|
/**
|
|
@@ -11,6 +12,8 @@ export interface TodoTabContentProps extends React.HTMLAttributes<HTMLDivElement
|
|
|
11
12
|
export declare const TodoTabContent: React.ForwardRefExoticComponent<TodoTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
12
13
|
export interface FilesTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
14
|
files?: string[];
|
|
15
|
+
provider?: FileSystemProvider;
|
|
16
|
+
onFileSelect?: (filePath: string) => void;
|
|
14
17
|
}
|
|
15
18
|
export declare const FilesTabContent: React.ForwardRefExoticComponent<FilesTabContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
19
|
export interface SourcesTabContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -1,20 +1,39 @@
|
|
|
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 { FileSystemView } from "./FileSystemView.js";
|
|
4
5
|
import { SourceListItem } from "./SourceListItem.js";
|
|
5
6
|
export const TodoTabContent = React.forwardRef(({ todos, className, ...props }, ref) => {
|
|
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)))) }));
|
|
7
|
+
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-paragraph-sm text-muted-foreground", children: "No todos yet" }) })) : (todos.map((todo) => (_jsx("div", { className: "text-paragraph-sm", children: todo.text }, todo.id)))) }));
|
|
7
8
|
});
|
|
8
9
|
TodoTabContent.displayName = "TodoTabContent";
|
|
9
|
-
export const FilesTabContent = React.forwardRef(({ files = [], className, ...props }, ref) => {
|
|
10
|
-
|
|
10
|
+
export const FilesTabContent = React.forwardRef(({ files = [], provider, onFileSelect, className, ...props }, ref) => {
|
|
11
|
+
// Stub handlers for the overflow menu actions
|
|
12
|
+
// Replace these with real implementations when connecting to actual filesystem
|
|
13
|
+
const handleDownload = React.useCallback((item) => {
|
|
14
|
+
console.log("Download:", item.name);
|
|
15
|
+
// TODO: Implement download logic
|
|
16
|
+
}, []);
|
|
17
|
+
const handleRename = React.useCallback((item) => {
|
|
18
|
+
console.log("Rename:", item.name);
|
|
19
|
+
// TODO: Implement rename logic
|
|
20
|
+
}, []);
|
|
21
|
+
const handleDelete = React.useCallback((item) => {
|
|
22
|
+
console.log("Delete:", item.name);
|
|
23
|
+
// TODO: Implement delete logic
|
|
24
|
+
}, []);
|
|
25
|
+
return (_jsx("div", { ref: ref, className: cn("h-full", className), ...props, children: _jsx(FileSystemView, { ...(provider && { provider }), onItemSelect: (item) => {
|
|
26
|
+
if (item.type === "file" && onFileSelect) {
|
|
27
|
+
onFileSelect(item.path || item.name);
|
|
28
|
+
}
|
|
29
|
+
}, onDownload: handleDownload, onRename: handleRename, onDelete: handleDelete, className: "h-full" }) }));
|
|
11
30
|
});
|
|
12
31
|
FilesTabContent.displayName = "FilesTabContent";
|
|
13
32
|
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)))) }));
|
|
33
|
+
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-paragraph-sm text-muted-foreground", children: "No sources available" }) })) : (sources.map((source) => (_jsx(SourceListItem, { source: source }, source.id)))) }));
|
|
15
34
|
});
|
|
16
35
|
SourcesTabContent.displayName = "SourcesTabContent";
|
|
17
36
|
export const DatabaseTabContent = React.forwardRef(({ data, className, ...props }, ref) => {
|
|
18
|
-
return (_jsxs("div", { ref: ref, className: cn("space-y-4", className), ...props, children: [_jsx("h3", { className: "font-semibold text-
|
|
37
|
+
return (_jsxs("div", { ref: ref, className: cn("space-y-4", className), ...props, children: [_jsx("h3", { className: "font-semibold text-subheading", children: "Database" }), _jsxs("div", { className: "text-paragraph-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-caption overflow-auto", children: JSON.stringify(data, null, 2) })) : null] })] })] }));
|
|
19
38
|
});
|
|
20
39
|
DatabaseTabContent.displayName = "DatabaseTabContent";
|
|
@@ -60,12 +60,12 @@ export const ChatSecondaryPanel = React.forwardRef(({ client, todos, variant = "
|
|
|
60
60
|
// Pills variant - Simple background highlight (Figma design)
|
|
61
61
|
_jsx(TabsList, { className: cn("w-full justify-start bg-transparent p-0 h-auto", "gap-1"), children: tabs.map((tab) => {
|
|
62
62
|
const Icon = tab.icon;
|
|
63
|
-
return (_jsxs(TabsTrigger, { value: tab.id, className: cn("gap-2 px-3 py-1.5 rounded-lg text-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));
|
|
63
|
+
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));
|
|
64
64
|
}) })) : (
|
|
65
65
|
// Animated variant - Clip-path animation (original style)
|
|
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-
|
|
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-paragraph-sm 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: {
|
|
67
67
|
clipPath: "inset(0 100% 0 0% round 999px)",
|
|
68
68
|
transition: "clip-path 0.25s ease-out",
|
|
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-
|
|
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-paragraph-sm 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-paragraph-sm text-foreground opacity-60 italic", children: "Database tab coming soon..." }) }), _jsx(TabsContent, { value: "sources", className: variant === "pills" ? "mt-0" : "", children: _jsx(SourcesTabContent, {}) })] }) }));
|
|
70
70
|
});
|
|
71
71
|
ChatSecondaryPanel.displayName = "ChatSecondaryPanel";
|
|
@@ -8,21 +8,21 @@ import { cn } from "../lib/utils.js";
|
|
|
8
8
|
import { ChatEmptyState, ChatHeader, ChatInputActions, ChatInputAttachment, ChatInputCommandMenu, ChatInputField, ChatInputRoot, ChatInputSubmit, ChatInputToolbar, ChatInputVoiceInput, ChatLayout, FilesTabContent, Message, MessageContent, PanelTabsHeader, SourcesTabContent, Tabs, TabsContent, TodoTabContent, } from "./index.js";
|
|
9
9
|
const logger = createLogger("gui");
|
|
10
10
|
// Mobile header component that uses ChatHeader context
|
|
11
|
-
function MobileHeader({ agentName }) {
|
|
11
|
+
function MobileHeader({ agentName, showHeader, }) {
|
|
12
12
|
const { isExpanded, setIsExpanded } = ChatHeader.useChatHeaderContext();
|
|
13
|
-
return (_jsxs("div", { className: "flex lg:hidden items-center gap-2 flex-1", children: [
|
|
13
|
+
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") }) })] }));
|
|
14
14
|
}
|
|
15
15
|
// Header component that uses ChatLayout context (must be inside ChatLayout.Root)
|
|
16
16
|
function AppChatHeader({ agentName, todos, sources, showHeader, }) {
|
|
17
17
|
const { panelSize, setPanelSize } = ChatLayout.useChatLayoutContext();
|
|
18
|
-
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 && (
|
|
18
|
+
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: () => {
|
|
19
19
|
setPanelSize(panelSize === "hidden" ? "small" : "hidden");
|
|
20
|
-
}, children: _jsx(PanelRight, { className: "size-4 text-muted-foreground" }) })] }), _jsx(MobileHeader, { agentName: agentName }), _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 }) })] }) })] }));
|
|
20
|
+
}, 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 }) })] }) })] }));
|
|
21
21
|
}
|
|
22
22
|
export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
23
23
|
// Use shared hooks from @townco/ui/core - MUST be called before any early returns
|
|
24
24
|
const { connectionStatus, connect, sessionId } = useChatSession(client, initialSessionId);
|
|
25
|
-
const { messages } = useChatMessages(client);
|
|
25
|
+
const { messages, sendMessage } = useChatMessages(client);
|
|
26
26
|
useToolCalls(client); // Still need to subscribe to tool call events
|
|
27
27
|
const error = useChatStore((state) => state.error);
|
|
28
28
|
const [agentName, setAgentName] = useState("Agent");
|
|
@@ -141,13 +141,32 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
141
141
|
},
|
|
142
142
|
},
|
|
143
143
|
];
|
|
144
|
-
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0 }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, {
|
|
145
|
-
"
|
|
144
|
+
return (_jsxs(ChatLayout.Root, { defaultPanelSize: "hidden", defaultActiveTab: "todo", children: [_jsxs(ChatLayout.Main, { children: [_jsx(AppChatHeader, { agentName: agentName, todos: todos, sources: sources, showHeader: messages.length > 0 }), connectionStatus === "error" && error && (_jsx("div", { className: "border-b border-destructive/20 bg-destructive/10 px-6 py-4", children: _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "mb-1 text-paragraph-sm font-semibold text-destructive", children: "Connection Error" }), _jsx("p", { className: "whitespace-pre-line text-paragraph-sm text-foreground", children: error })] }), _jsx("button", { type: "button", onClick: connect, className: "rounded-lg bg-destructive px-4 py-2 text-paragraph-sm font-medium text-destructive-foreground transition-colors hover:bg-destructive-hover", children: "Retry" })] }) })), _jsxs(ChatLayout.Body, { children: [_jsx(ChatLayout.Messages, { children: messages.length === 0 ? (_jsx("div", { className: "flex flex-1 items-center px-4", children: _jsx(ChatEmptyState, { title: agentName, description: "This agent can help you with your tasks. Start a conversation by typing a message below.", suggestedPrompts: [
|
|
145
|
+
"Search the web for the latest news on top tech company earnings, produce a summary for each company, and then a macro trend analysis of the tech industry. Use your todo list",
|
|
146
146
|
"Explain how this works",
|
|
147
147
|
"Create a new feature",
|
|
148
148
|
"Review my changes",
|
|
149
149
|
], onPromptClick: (prompt) => {
|
|
150
|
-
|
|
150
|
+
sendMessage(prompt);
|
|
151
151
|
logger.info("Prompt clicked", { prompt });
|
|
152
|
-
} }) })) : (_jsx("div", { className: "flex flex-col
|
|
152
|
+
} }) })) : (_jsx("div", { className: "flex flex-col px-4", children: messages.map((message, index) => {
|
|
153
|
+
// Calculate dynamic spacing based on message sequence
|
|
154
|
+
const isFirst = index === 0;
|
|
155
|
+
const previousMessage = isFirst ? null : messages[index - 1];
|
|
156
|
+
let spacingClass = "mt-2";
|
|
157
|
+
if (isFirst) {
|
|
158
|
+
spacingClass = "mt-2";
|
|
159
|
+
}
|
|
160
|
+
else if (message.role === "user") {
|
|
161
|
+
// User message usually starts a new turn
|
|
162
|
+
spacingClass =
|
|
163
|
+
previousMessage?.role === "user" ? "mt-4" : "mt-4";
|
|
164
|
+
}
|
|
165
|
+
else if (message.role === "assistant") {
|
|
166
|
+
// Assistant message is usually a response
|
|
167
|
+
spacingClass =
|
|
168
|
+
previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
|
|
169
|
+
}
|
|
170
|
+
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
171
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: "Type a message or / for commands...", autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {})] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsxs(Tabs, { defaultValue: "todo", className: "flex flex-col h-full", children: [_jsx("div", { className: cn("border-b border-border bg-card", "px-6 py-2 h-16", "flex items-center", "[border-bottom-width:0.5px]"), children: _jsx(PanelTabsHeader, { showIcons: true, visibleTabs: ["todo", "files", "sources"], variant: "compact" }) }), _jsx(TabsContent, { value: "todo", className: "flex-1 p-4 mt-0", children: _jsx(TodoTabContent, { todos: todos }) }), _jsx(TabsContent, { value: "files", className: "flex-1 p-4 mt-0", children: _jsx(FilesTabContent, {}) }), _jsx(TabsContent, { value: "sources", className: "flex-1 p-4 mt-0", children: _jsx(SourcesTabContent, { sources: sources }) })] }) }))] }));
|
|
153
172
|
}
|
|
@@ -15,8 +15,8 @@ const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("
|
|
|
15
15
|
DialogHeader.displayName = "DialogHeader";
|
|
16
16
|
const DialogFooter = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }));
|
|
17
17
|
DialogFooter.displayName = "DialogFooter";
|
|
18
|
-
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn("text-
|
|
18
|
+
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn("text-subheading font-semibold leading-none tracking-tight", className), ...props })));
|
|
19
19
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
20
|
-
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-sm text-muted-foreground", className), ...props })));
|
|
20
|
+
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-paragraph-sm text-muted-foreground", className), ...props })));
|
|
21
21
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
22
22
|
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
|
|
@@ -15,7 +15,7 @@ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
|
15
15
|
/* -------------------------------------------------------------------------------------------------
|
|
16
16
|
* DropdownMenuSubTrigger
|
|
17
17
|
* -----------------------------------------------------------------------------------------------*/
|
|
18
|
-
const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.SubTrigger, { ref: ref, className: cn("flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none", "focus:bg-muted data-[state=open]:bg-muted", inset && "pl-8", className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto h-4 w-4" })] })));
|
|
18
|
+
const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.SubTrigger, { ref: ref, className: cn("flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-paragraph-sm outline-none", "focus:bg-muted data-[state=open]:bg-muted", inset && "pl-8", className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto h-4 w-4" })] })));
|
|
19
19
|
DropdownMenuSubTrigger.displayName =
|
|
20
20
|
DropdownMenuPrimitive.SubTrigger.displayName;
|
|
21
21
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -27,28 +27,28 @@ DropdownMenuSubContent.displayName =
|
|
|
27
27
|
/* -------------------------------------------------------------------------------------------------
|
|
28
28
|
* DropdownMenuContent
|
|
29
29
|
* -----------------------------------------------------------------------------------------------*/
|
|
30
|
-
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-
|
|
30
|
+
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover p-0.5 shadow-md", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[side=bottom]:slide-in-from-top-2", "data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2", "data-[side=top]:slide-in-from-bottom-2", className), ...props }) })));
|
|
31
31
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
32
32
|
/* -------------------------------------------------------------------------------------------------
|
|
33
33
|
* DropdownMenuItem
|
|
34
34
|
* -----------------------------------------------------------------------------------------------*/
|
|
35
|
-
const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Item, { ref: ref, className: cn("relative flex cursor-
|
|
35
|
+
const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Item, { ref: ref, className: cn("relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-paragraph-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", inset && "pl-8", className), ...props })));
|
|
36
36
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
37
37
|
/* -------------------------------------------------------------------------------------------------
|
|
38
38
|
* DropdownMenuCheckboxItem
|
|
39
39
|
* -----------------------------------------------------------------------------------------------*/
|
|
40
|
-
const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), children] })));
|
|
40
|
+
const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.CheckboxItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-paragraph-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), children] })));
|
|
41
41
|
DropdownMenuCheckboxItem.displayName =
|
|
42
42
|
DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
43
43
|
/* -------------------------------------------------------------------------------------------------
|
|
44
44
|
* DropdownMenuRadioItem
|
|
45
45
|
* -----------------------------------------------------------------------------------------------*/
|
|
46
|
-
const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.RadioItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Circle, { className: "h-2 w-2 fill-current" }) }) }), children] })));
|
|
46
|
+
const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DropdownMenuPrimitive.RadioItem, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-paragraph-sm outline-none transition-colors", "focus:bg-muted focus:text-foreground", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Circle, { className: "h-2 w-2 fill-current" }) }) }), children] })));
|
|
47
47
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
48
48
|
/* -------------------------------------------------------------------------------------------------
|
|
49
49
|
* DropdownMenuLabel
|
|
50
50
|
* -----------------------------------------------------------------------------------------------*/
|
|
51
|
-
const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className), ...props })));
|
|
51
|
+
const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Label, { ref: ref, className: cn("px-2 py-1.5 text-paragraph-sm font-semibold", inset && "pl-8", className), ...props })));
|
|
52
52
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
53
53
|
/* -------------------------------------------------------------------------------------------------
|
|
54
54
|
* DropdownMenuSeparator
|
|
@@ -59,7 +59,7 @@ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
|
59
59
|
* DropdownMenuShortcut
|
|
60
60
|
* -----------------------------------------------------------------------------------------------*/
|
|
61
61
|
const DropdownMenuShortcut = ({ className, ...props }) => {
|
|
62
|
-
return (_jsx("span", { className: cn("ml-auto text-
|
|
62
|
+
return (_jsx("span", { className: cn("ml-auto text-caption tracking-widest opacity-60", className), ...props }));
|
|
63
63
|
};
|
|
64
64
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
65
65
|
/* -------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystemItem component - represents a single file or folder in the tree
|
|
3
|
+
* Matches the Figma design specifications with all states
|
|
4
|
+
* States: Default, Hover, Focus, Selected (Targeted in Figma)
|
|
5
|
+
*/
|
|
6
|
+
import type { FileSystemItem as FileSystemItemType } from "../types/filesystem.js";
|
|
7
|
+
export interface FileSystemItemProps {
|
|
8
|
+
item: FileSystemItemType;
|
|
9
|
+
level?: number;
|
|
10
|
+
onSelect?: (item: FileSystemItemType) => void;
|
|
11
|
+
selectedId?: string;
|
|
12
|
+
isDropTarget?: boolean;
|
|
13
|
+
onDownload?: (item: FileSystemItemType) => void;
|
|
14
|
+
onRename?: (item: FileSystemItemType) => void;
|
|
15
|
+
onDelete?: (item: FileSystemItemType) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function FileSystemItem({ item, level, onSelect, selectedId, isDropTarget, onDownload, onRename, onDelete, }: FileSystemItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* FileSystemItem component - represents a single file or folder in the tree
|
|
4
|
+
* Matches the Figma design specifications with all states
|
|
5
|
+
* States: Default, Hover, Focus, Selected (Targeted in Figma)
|
|
6
|
+
*/
|
|
7
|
+
import { ChevronDown, FileCode, Folder, FolderOpen, MoreVertical, } from "lucide-react";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { cn } from "../lib/utils.js";
|
|
10
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./DropdownMenu.js";
|
|
11
|
+
export function FileSystemItem({ item, level = 0, onSelect, selectedId, isDropTarget = false, onDownload, onRename, onDelete, }) {
|
|
12
|
+
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
13
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
14
|
+
const isSelected = selectedId === item.id;
|
|
15
|
+
const handleToggle = () => {
|
|
16
|
+
if (item.type === "folder") {
|
|
17
|
+
setIsExpanded(!isExpanded);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const handleClick = () => {
|
|
21
|
+
onSelect?.(item);
|
|
22
|
+
if (item.type === "folder") {
|
|
23
|
+
handleToggle();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const handleKeyDown = (e) => {
|
|
27
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
handleClick();
|
|
30
|
+
}
|
|
31
|
+
else if (e.key === "ArrowRight" &&
|
|
32
|
+
item.type === "folder" &&
|
|
33
|
+
!isExpanded) {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
setIsExpanded(true);
|
|
36
|
+
}
|
|
37
|
+
else if (e.key === "ArrowLeft" && item.type === "folder" && isExpanded) {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
setIsExpanded(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
// Calculate padding based on level
|
|
43
|
+
const getPaddingClass = () => {
|
|
44
|
+
if (level === 0)
|
|
45
|
+
return "p-2";
|
|
46
|
+
if (level === 1)
|
|
47
|
+
return "pl-8 pr-2 py-2";
|
|
48
|
+
return "pl-16 pr-2 py-2"; // level 2+
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs("div", { className: "flex flex-col w-full", children: [_jsxs("div", { role: "button", tabIndex: 0, "aria-expanded": item.type === "folder" ? isExpanded : undefined, className: cn(
|
|
51
|
+
// Base styles with semantic typography
|
|
52
|
+
"group flex items-center gap-2 rounded-md cursor-pointer transition-colors text-paragraph-sm", getPaddingClass(),
|
|
53
|
+
// State styles using semantic colors
|
|
54
|
+
// Default: transparent background
|
|
55
|
+
// Hover: accent-hover background
|
|
56
|
+
"hover:bg-accent-hover",
|
|
57
|
+
// Focus: ring with border-dark color (3px ring matching Figma)
|
|
58
|
+
"focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-border-dark",
|
|
59
|
+
// Selected: just accent background (no border)
|
|
60
|
+
isSelected && "bg-accent",
|
|
61
|
+
// Drop target state (for future drag & drop) - dashed border
|
|
62
|
+
isDropTarget && [
|
|
63
|
+
"bg-accent",
|
|
64
|
+
"border border-dashed border-border-dark",
|
|
65
|
+
]), onClick: handleClick, onKeyDown: handleKeyDown, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), children: [_jsx("div", { className: "shrink-0 size-4 flex items-center justify-center text-foreground", children: item.type === "folder" ? (isExpanded ? (_jsx(FolderOpen, { className: "size-4" })) : (_jsx(Folder, { className: "size-4" }))) : (_jsx(FileCode, { className: "size-4" })) }), _jsx("p", { className: "flex-1 text-foreground whitespace-nowrap overflow-hidden text-ellipsis", children: item.name }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("shrink-0 size-4 transition-opacity text-muted-foreground hover:text-foreground",
|
|
66
|
+
// Hidden by default, shown on group hover
|
|
67
|
+
"opacity-0 group-hover:opacity-100",
|
|
68
|
+
// Always hidden when focused (to show clean state)
|
|
69
|
+
isFocused && "opacity-0"), onClick: (e) => {
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
}, "aria-label": "More options", type: "button", tabIndex: -1, children: _jsx(MoreVertical, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", side: "bottom", sideOffset: 5, alignOffset: 0, collisionPadding: 8, className: "w-40 z-[100]", onClick: (e) => e.stopPropagation(), children: [onDownload && (_jsx(DropdownMenuItem, { onClick: (e) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
onDownload(item);
|
|
74
|
+
}, children: "Download" })), onRename && (_jsx(DropdownMenuItem, { onClick: (e) => {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
onRename(item);
|
|
77
|
+
}, children: "Rename" })), (onDownload || onRename) && onDelete && _jsx(DropdownMenuSeparator, {}), onDelete && (_jsx(DropdownMenuItem, { className: "text-destructive focus:text-destructive focus:bg-muted", onClick: (e) => {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
onDelete(item);
|
|
80
|
+
}, children: _jsx("span", { className: "text-paragraph-sm", children: "Delete" }) }))] })] }), item.type === "folder" && (_jsx("div", { className: "shrink-0 size-4 flex items-center justify-center text-muted-foreground", children: _jsx(ChevronDown, { className: cn("size-4 transition-transform", !isExpanded && "-rotate-90") }) }))] }), item.type === "folder" && isExpanded && item.children && (_jsx("div", { className: "flex flex-col", children: item.children.map((child) => (_jsx(FileSystemItem, { item: child, level: level + 1, ...(onSelect && { onSelect }), ...(selectedId && { selectedId }), ...(onDownload && { onDownload }), ...(onRename && { onRename }), ...(onDelete && { onDelete }) }, child.id))) }))] }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystemView component - main file tree/manager view
|
|
3
|
+
* Based on shadcn file manager design and Figma specifications
|
|
4
|
+
*/
|
|
5
|
+
import type { FileSystemItem as FileSystemItemType, FileSystemProvider } from "../types/filesystem.js";
|
|
6
|
+
export interface FileSystemViewProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
provider?: FileSystemProvider;
|
|
9
|
+
onItemSelect?: (item: FileSystemItemType) => void;
|
|
10
|
+
onDownload?: (item: FileSystemItemType) => void;
|
|
11
|
+
onRename?: (item: FileSystemItemType) => void;
|
|
12
|
+
onDelete?: (item: FileSystemItemType) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function FileSystemView({ className, provider, onItemSelect, onDownload, onRename, onDelete, }: FileSystemViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* FileSystemView component - main file tree/manager view
|
|
4
|
+
* Based on shadcn file manager design and Figma specifications
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { MockFileSystemProvider } from "../data/mockFileSystemData.js";
|
|
8
|
+
import { cn } from "../lib/utils.js";
|
|
9
|
+
import { FileSystemItem } from "./FileSystemItem.js";
|
|
10
|
+
// Create a default provider instance outside the component to avoid infinite loops
|
|
11
|
+
const defaultProvider = new MockFileSystemProvider();
|
|
12
|
+
export function FileSystemView({ className, provider = defaultProvider, onItemSelect, onDownload, onRename, onDelete, }) {
|
|
13
|
+
const [items, setItems] = React.useState([]);
|
|
14
|
+
const [selectedId, setSelectedId] = React.useState();
|
|
15
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
16
|
+
const [error, setError] = React.useState();
|
|
17
|
+
// Load root items on mount
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
const loadItems = async () => {
|
|
20
|
+
try {
|
|
21
|
+
setIsLoading(true);
|
|
22
|
+
const rootItems = await provider.getRootItems();
|
|
23
|
+
setItems(rootItems);
|
|
24
|
+
setError(undefined);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
setError(err instanceof Error ? err.message : "Failed to load items");
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
loadItems();
|
|
34
|
+
}, [provider]);
|
|
35
|
+
const handleItemSelect = (item) => {
|
|
36
|
+
setSelectedId(item.id);
|
|
37
|
+
onItemSelect?.(item);
|
|
38
|
+
};
|
|
39
|
+
if (isLoading) {
|
|
40
|
+
return (_jsx("div", { className: cn("p-4", className), children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Loading..." }) }));
|
|
41
|
+
}
|
|
42
|
+
if (error) {
|
|
43
|
+
return (_jsx("div", { className: cn("p-4", className), children: _jsxs("p", { className: "text-sm text-destructive", children: ["Error: ", error] }) }));
|
|
44
|
+
}
|
|
45
|
+
return (_jsx("div", { className: cn("flex flex-col px-4 py-3", className), children: items.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No items found" })) : (items.map((item) => (_jsx(FileSystemItem, { item: item, onSelect: handleItemSelect, ...(selectedId && { selectedId }), ...(onDownload && { onDownload }), ...(onRename && { onRename }), ...(onDelete && { onDelete }) }, item.id)))) }));
|
|
46
|
+
}
|
|
@@ -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-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", {
|
|
5
|
+
const inputVariants = cva("flex h-10 w-full rounded-md border bg-input-background px-3 py-2 text-paragraph-sm ring-offset-background file:border-0 file:bg-transparent file:text-paragraph-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",
|
|
@@ -2,6 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
-
const Label = React.forwardRef(({ className, ...props }, ref) => (_jsx(LabelPrimitive.Root, { ref: ref, className: cn("text-
|
|
5
|
+
const Label = React.forwardRef(({ className, ...props }, ref) => (_jsx(LabelPrimitive.Root, { ref: ref, className: cn("text-label font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", className), ...props })));
|
|
6
6
|
Label.displayName = LabelPrimitive.Root.displayName;
|
|
7
7
|
export { Label };
|
|
@@ -25,15 +25,15 @@ export function MarkdownRenderer({ content, className, }) {
|
|
|
25
25
|
code: ({ node, ...props }) => {
|
|
26
26
|
const inline = !props.className?.includes("language-");
|
|
27
27
|
if (inline) {
|
|
28
|
-
return (_jsx("code", { className: "px-1.5 py-0.5 bg-card border border-border rounded text-
|
|
28
|
+
return (_jsx("code", { className: "px-1.5 py-0.5 bg-card border border-border rounded text-code text-foreground", ...props }));
|
|
29
29
|
}
|
|
30
|
-
return (_jsx("code", { className: "block p-4 bg-card border border-border rounded-md overflow-x-auto text-
|
|
30
|
+
return (_jsx("code", { className: "block p-4 bg-card border border-border rounded-md overflow-x-auto text-code text-foreground", ...props }));
|
|
31
31
|
},
|
|
32
32
|
pre: ({ node, ...props }) => _jsx("pre", { className: "my-4", ...props }),
|
|
33
33
|
// Heading styling
|
|
34
|
-
h1: ({ node, ...props }) => (_jsx("h1", { className: "text-
|
|
35
|
-
h2: ({ node, ...props }) => (_jsx("h2", { className: "text-
|
|
36
|
-
h3: ({ node, ...props }) => (_jsx("h3", { className: "text-
|
|
34
|
+
h1: ({ node, ...props }) => (_jsx("h1", { className: "text-heading-3 mt-6 mb-4 text-foreground", ...props })),
|
|
35
|
+
h2: ({ node, ...props }) => (_jsx("h2", { className: "text-subheading mt-5 mb-3 text-foreground", ...props })),
|
|
36
|
+
h3: ({ node, ...props }) => (_jsx("h3", { className: "text-subheading mt-4 mb-2 text-foreground", ...props })),
|
|
37
37
|
// List styling
|
|
38
38
|
ul: ({ node, ...props }) => {
|
|
39
39
|
// Check if this is a task list by looking for checkbox inputs in children
|
|
@@ -7,7 +7,7 @@ import type { DisplayMessage } from "./MessageList.js";
|
|
|
7
7
|
*/
|
|
8
8
|
declare const messageVariants: (props?: ({
|
|
9
9
|
role?: "user" | "assistant" | "system" | null | undefined;
|
|
10
|
-
layout?: "default" | "
|
|
10
|
+
layout?: "default" | "compact" | "full" | null | undefined;
|
|
11
11
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
12
12
|
export interface MessageProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof messageVariants> {
|
|
13
13
|
/**
|