@townco/ui 0.1.68 → 0.1.70
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-messages.d.ts +6 -1
- package/dist/core/hooks/use-chat-session.d.ts +1 -1
- package/dist/core/hooks/use-tool-calls.d.ts +6 -1
- package/dist/core/schemas/chat.d.ts +10 -0
- package/dist/core/schemas/tool-call.d.ts +13 -8
- package/dist/core/schemas/tool-call.js +8 -0
- package/dist/core/utils/tool-call-state.d.ts +30 -0
- package/dist/core/utils/tool-call-state.js +73 -0
- package/dist/core/utils/tool-summary.d.ts +13 -0
- package/dist/core/utils/tool-summary.js +172 -0
- package/dist/core/utils/tool-verbiage.d.ts +28 -0
- package/dist/core/utils/tool-verbiage.js +185 -0
- package/dist/gui/components/AppSidebar.d.ts +22 -0
- package/dist/gui/components/AppSidebar.js +22 -0
- package/dist/gui/components/ChatLayout.d.ts +5 -0
- package/dist/gui/components/ChatLayout.js +130 -138
- package/dist/gui/components/ChatView.js +42 -118
- package/dist/gui/components/HookNotification.d.ts +9 -0
- package/dist/gui/components/HookNotification.js +50 -0
- package/dist/gui/components/MessageContent.js +151 -39
- package/dist/gui/components/SessionHistory.d.ts +10 -0
- package/dist/gui/components/SessionHistory.js +101 -0
- package/dist/gui/components/SessionHistoryItem.d.ts +11 -0
- package/dist/gui/components/SessionHistoryItem.js +24 -0
- package/dist/gui/components/Sheet.d.ts +25 -0
- package/dist/gui/components/Sheet.js +36 -0
- package/dist/gui/components/Sidebar.d.ts +65 -0
- package/dist/gui/components/Sidebar.js +231 -0
- package/dist/gui/components/SidebarToggle.d.ts +3 -0
- package/dist/gui/components/SidebarToggle.js +9 -0
- package/dist/gui/components/SubAgentDetails.js +13 -2
- package/dist/gui/components/ToolCallList.js +3 -3
- package/dist/gui/components/ToolOperation.d.ts +11 -0
- package/dist/gui/components/ToolOperation.js +329 -0
- package/dist/gui/components/WorkProgress.d.ts +20 -0
- package/dist/gui/components/WorkProgress.js +79 -0
- package/dist/gui/components/index.d.ts +8 -1
- package/dist/gui/components/index.js +9 -1
- package/dist/gui/hooks/index.d.ts +1 -0
- package/dist/gui/hooks/index.js +1 -0
- package/dist/gui/hooks/use-mobile.d.ts +1 -0
- package/dist/gui/hooks/use-mobile.js +15 -0
- package/dist/gui/index.d.ts +1 -0
- package/dist/gui/index.js +2 -0
- package/dist/gui/lib/motion.d.ts +55 -0
- package/dist/gui/lib/motion.js +217 -0
- package/dist/sdk/schemas/message.d.ts +2 -2
- package/dist/sdk/schemas/session.d.ts +5 -0
- package/dist/sdk/transports/types.d.ts +5 -0
- package/package.json +8 -7
- package/src/styles/global.css +128 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AlertCircle, Archive, CheckCircle2, ChevronDown, Scissors, } from "lucide-react";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Get display information for a hook type
|
|
6
|
+
*/
|
|
7
|
+
function getHookDisplayInfo(hookType, _callback) {
|
|
8
|
+
if (hookType === "context_size") {
|
|
9
|
+
return {
|
|
10
|
+
icon: Archive,
|
|
11
|
+
title: "Context Compacted",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (hookType === "tool_response") {
|
|
15
|
+
return {
|
|
16
|
+
icon: Scissors,
|
|
17
|
+
title: "Tool Response Compacted",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Fallback for unknown hook types
|
|
21
|
+
return {
|
|
22
|
+
icon: Archive,
|
|
23
|
+
title: `Hook Executed`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Format a number with thousand separators
|
|
28
|
+
*/
|
|
29
|
+
function formatNumber(num) {
|
|
30
|
+
return num.toLocaleString();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* HookNotification component - displays a hook notification inline with messages
|
|
34
|
+
* Only shows completed or error states (not intermediate "triggered" state)
|
|
35
|
+
*/
|
|
36
|
+
export function HookNotification({ notification }) {
|
|
37
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
38
|
+
const { icon: IconComponent, title } = getHookDisplayInfo(notification.hookType, notification.callback);
|
|
39
|
+
const isCompleted = notification.status === "completed";
|
|
40
|
+
const isError = notification.status === "error";
|
|
41
|
+
// Build subtitle showing key info
|
|
42
|
+
let subtitle = "";
|
|
43
|
+
if (isCompleted && notification.metadata?.tokensSaved !== undefined) {
|
|
44
|
+
subtitle = `${formatNumber(notification.metadata.tokensSaved)} tokens saved`;
|
|
45
|
+
}
|
|
46
|
+
else if (isError && notification.error) {
|
|
47
|
+
subtitle = notification.error;
|
|
48
|
+
}
|
|
49
|
+
return (_jsxs("div", { className: "flex flex-col my-3", children: [_jsxs("button", { type: "button", className: "flex flex-col items-start gap-0.5 cursor-pointer bg-transparent border-none p-0 text-left group w-fit", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [_jsx("div", { className: isError ? "text-destructive" : "text-muted-foreground", children: _jsx(IconComponent, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-paragraph-sm text-muted-foreground", children: title }), isCompleted && _jsx(CheckCircle2, { className: "h-3 w-3 text-green-500" }), isError && _jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }), _jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground/70 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })] }), subtitle && (_jsx("span", { className: `text-paragraph-sm pl-4.5 ${isError ? "text-destructive/70" : "text-muted-foreground/70"}`, children: subtitle }))] }), isExpanded && (_jsxs("div", { className: "mt-2 text-sm border border-border rounded-lg bg-card overflow-hidden w-full", children: [_jsxs("div", { className: "p-3 border-b border-border", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Hook Details" }), _jsxs("div", { className: "space-y-1 text-[11px]", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Type:" }), _jsx("span", { className: "text-foreground font-mono", children: notification.hookType })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Callback:" }), _jsx("span", { className: "text-foreground font-mono", children: notification.callback })] })] })] }), notification.metadata && (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1.5 font-sans", children: "Result" }), _jsxs("div", { className: "space-y-1 text-[11px]", children: [notification.metadata.action && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Action:" }), _jsx("span", { className: "text-foreground", children: notification.metadata.action })] })), notification.metadata.messagesRemoved !== undefined && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Messages Removed:" }), _jsx("span", { className: "text-foreground", children: formatNumber(notification.metadata.messagesRemoved) })] })), notification.metadata.tokensSaved !== undefined && (_jsxs("div", { className: "flex gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Tokens Saved:" }), _jsx("span", { className: "text-green-500 font-medium", children: formatNumber(notification.metadata.tokensSaved) })] }))] })] })), notification.error && (_jsxs("div", { className: "p-3 border-b border-border last:border-0", children: [_jsx("div", { className: "text-[10px] font-bold text-destructive uppercase tracking-wider mb-1.5 font-sans", children: "Error" }), _jsx("div", { className: "text-[11px] text-destructive font-mono", children: notification.error })] })), notification.completedAt && (_jsxs("div", { className: "p-2 bg-muted/50 border-t border-border text-[10px] text-muted-foreground font-sans", children: ["Executed:", " ", new Date(notification.completedAt).toLocaleTimeString()] }))] }))] }));
|
|
50
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
|
+
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { useChatStore } from "../../core/store/chat-store.js";
|
|
6
|
+
import { isPreliminaryToolCall } from "../../core/utils/tool-call-state.js";
|
|
7
|
+
import { getDuration, getTransition, motionEasing, shimmerTransition, } from "../lib/motion.js";
|
|
5
8
|
import { cn } from "../lib/utils.js";
|
|
6
|
-
import { InvokingGroup } from "./InvokingGroup.js";
|
|
7
9
|
import { Reasoning } from "./Reasoning.js";
|
|
8
10
|
import { Response } from "./Response.js";
|
|
9
|
-
import {
|
|
10
|
-
import { ToolCallGroup } from "./ToolCallGroup.js";
|
|
11
|
+
import { ToolOperation } from "./ToolOperation.js";
|
|
11
12
|
/**
|
|
12
13
|
* MessageContent component inspired by shadcn.io/ai
|
|
13
14
|
* Provides the content container with role-based styling
|
|
@@ -35,6 +36,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
35
36
|
// Get streaming start time and current model from store
|
|
36
37
|
const streamingStartTime = useChatStore((state) => state.streamingStartTime);
|
|
37
38
|
const currentModel = useChatStore((state) => state.currentModel);
|
|
39
|
+
const shouldReduceMotion = useReducedMotion();
|
|
38
40
|
// Use smart rendering if message is provided and no custom children
|
|
39
41
|
const useSmartRendering = message && !children;
|
|
40
42
|
// Derive props from message if using smart rendering
|
|
@@ -49,33 +51,68 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
49
51
|
const hasThinking = !!thinking;
|
|
50
52
|
// Check if waiting (streaming but no content yet)
|
|
51
53
|
const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
|
|
52
|
-
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(
|
|
54
|
+
content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(motion.div, { initial: {
|
|
55
|
+
filter: "blur(12px)",
|
|
56
|
+
opacity: 0,
|
|
57
|
+
y: 12,
|
|
58
|
+
}, animate: {
|
|
59
|
+
filter: "blur(0px)",
|
|
60
|
+
opacity: 1,
|
|
61
|
+
y: 0,
|
|
62
|
+
}, exit: {
|
|
63
|
+
filter: "blur(12px)",
|
|
64
|
+
opacity: 0,
|
|
65
|
+
y: -12,
|
|
66
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
67
|
+
duration: 0.5,
|
|
68
|
+
ease: motionEasing.smooth,
|
|
69
|
+
}), children: _jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true }) })), isWaiting && streamingStartTime && (_jsx(motion.div, { initial: {
|
|
70
|
+
filter: "blur(12px)",
|
|
71
|
+
opacity: 0,
|
|
72
|
+
y: 12,
|
|
73
|
+
}, animate: {
|
|
74
|
+
filter: "blur(0px)",
|
|
75
|
+
opacity: 1,
|
|
76
|
+
y: 0,
|
|
77
|
+
}, exit: {
|
|
78
|
+
filter: "blur(12px)",
|
|
79
|
+
opacity: 0,
|
|
80
|
+
y: -12,
|
|
81
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
82
|
+
duration: 0.4,
|
|
83
|
+
ease: motionEasing.smooth,
|
|
84
|
+
}), children: _jsx(motion.div, { className: "flex flex-col my-4 rounded-md px-1 -mx-1 w-fit", animate: {
|
|
85
|
+
backgroundPosition: ["-200% 0", "200% 0"],
|
|
86
|
+
}, transition: {
|
|
87
|
+
...shimmerTransition,
|
|
88
|
+
duration: getDuration(shouldReduceMotion ?? false, 1.5),
|
|
89
|
+
}, style: {
|
|
90
|
+
backgroundImage: "linear-gradient(90deg, transparent 5%, rgba(255, 255, 255, 0.75) 25%, transparent 35%)",
|
|
91
|
+
backgroundSize: "200% 100%",
|
|
92
|
+
}, children: _jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "text-paragraph-sm text-text-secondary/70", children: "Thinking..." }) }) }) })), message.role === "assistant" ? ((() => {
|
|
53
93
|
// Sort tool calls by content position
|
|
54
94
|
const sortedToolCalls = (message.toolCalls || [])
|
|
55
95
|
.slice()
|
|
56
96
|
.sort((a, b) => (a.contentPosition ?? Infinity) -
|
|
57
97
|
(b.contentPosition ?? Infinity));
|
|
58
|
-
// Helper to check if a tool call is preliminary (invoking)
|
|
59
|
-
const isPreliminary = (tc) => tc.status === "pending" &&
|
|
60
|
-
(!tc.rawInput || Object.keys(tc.rawInput).length === 0);
|
|
61
98
|
// Helper to group tool calls by batchId, consecutive same-title calls, or consecutive preliminary calls
|
|
62
99
|
const groupToolCalls = (toolCalls) => {
|
|
63
100
|
const result = [];
|
|
64
101
|
const batchGroups = new Map();
|
|
65
|
-
let
|
|
102
|
+
let currentSelectingGroup = [];
|
|
66
103
|
let currentConsecutiveGroup = [];
|
|
67
104
|
let currentConsecutiveTitle = null;
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
105
|
+
const flushSelectingGroup = () => {
|
|
106
|
+
if (currentSelectingGroup.length > 1) {
|
|
70
107
|
result.push({
|
|
71
|
-
type: "
|
|
72
|
-
toolCalls:
|
|
108
|
+
type: "selecting",
|
|
109
|
+
toolCalls: currentSelectingGroup,
|
|
73
110
|
});
|
|
74
111
|
}
|
|
75
|
-
else if (
|
|
76
|
-
result.push(
|
|
112
|
+
else if (currentSelectingGroup.length === 1) {
|
|
113
|
+
result.push(currentSelectingGroup[0]);
|
|
77
114
|
}
|
|
78
|
-
|
|
115
|
+
currentSelectingGroup = [];
|
|
79
116
|
};
|
|
80
117
|
const flushConsecutiveGroup = () => {
|
|
81
118
|
if (currentConsecutiveGroup.length > 1) {
|
|
@@ -94,7 +131,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
94
131
|
for (const tc of toolCalls) {
|
|
95
132
|
// Handle batch groups (explicit batchId)
|
|
96
133
|
if (tc.batchId) {
|
|
97
|
-
|
|
134
|
+
flushSelectingGroup();
|
|
98
135
|
flushConsecutiveGroup();
|
|
99
136
|
const existing = batchGroups.get(tc.batchId);
|
|
100
137
|
if (existing) {
|
|
@@ -106,14 +143,14 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
106
143
|
result.push({ type: "batch", toolCalls: group });
|
|
107
144
|
}
|
|
108
145
|
}
|
|
109
|
-
// Handle consecutive preliminary (
|
|
110
|
-
else if (
|
|
146
|
+
// Handle consecutive preliminary (selecting) tool calls
|
|
147
|
+
else if (isPreliminaryToolCall(tc)) {
|
|
111
148
|
flushConsecutiveGroup();
|
|
112
|
-
|
|
149
|
+
currentSelectingGroup.push(tc);
|
|
113
150
|
}
|
|
114
151
|
// Regular tool call - group consecutive same-title calls (e.g., subagent)
|
|
115
152
|
else {
|
|
116
|
-
|
|
153
|
+
flushSelectingGroup();
|
|
117
154
|
// Check if this continues a consecutive group
|
|
118
155
|
if (currentConsecutiveTitle === tc.title) {
|
|
119
156
|
currentConsecutiveGroup.push(tc);
|
|
@@ -127,7 +164,7 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
127
164
|
}
|
|
128
165
|
}
|
|
129
166
|
// Flush any remaining groups
|
|
130
|
-
|
|
167
|
+
flushSelectingGroup();
|
|
131
168
|
flushConsecutiveGroup();
|
|
132
169
|
return result;
|
|
133
170
|
};
|
|
@@ -137,22 +174,37 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
137
174
|
if (typeof item === "object" &&
|
|
138
175
|
"type" in item &&
|
|
139
176
|
item.type === "batch") {
|
|
140
|
-
return (_jsx(
|
|
177
|
+
return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `batch-${item.toolCalls[0]?.batchId || index}`));
|
|
141
178
|
}
|
|
142
|
-
//
|
|
179
|
+
// Selecting group (consecutive preliminary tool calls)
|
|
143
180
|
if (typeof item === "object" &&
|
|
144
181
|
"type" in item &&
|
|
145
|
-
item.type === "
|
|
146
|
-
return (_jsx(
|
|
182
|
+
item.type === "selecting") {
|
|
183
|
+
return (_jsx(ToolOperation, { toolCalls: item.toolCalls, isGrouped: true }, `selecting-${item.toolCalls[0]?.id || index}`));
|
|
147
184
|
}
|
|
148
185
|
// Single tool call
|
|
149
|
-
return (_jsx(
|
|
186
|
+
return (_jsx(ToolOperation, { toolCalls: [item], isGrouped: false }, item.id));
|
|
150
187
|
};
|
|
151
|
-
// If no tool calls or they don't have positions, render
|
|
188
|
+
// If no tool calls or they don't have positions, render simplified way
|
|
152
189
|
if (sortedToolCalls.length === 0 ||
|
|
153
190
|
!sortedToolCalls.some((tc) => tc.contentPosition !== undefined)) {
|
|
154
191
|
const groupedToolCalls = groupToolCalls(sortedToolCalls);
|
|
155
|
-
return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) })), _jsx(
|
|
192
|
+
return (_jsxs(_Fragment, { children: [groupedToolCalls.length > 0 && (_jsx(AnimatePresence, { mode: "popLayout", children: _jsx("div", { className: "flex flex-col gap-2 mb-1", children: groupedToolCalls.map((item, index) => renderToolCallOrGroup(item, index)) }) })), _jsx(motion.div, { initial: {
|
|
193
|
+
filter: "blur(12px)",
|
|
194
|
+
opacity: 0,
|
|
195
|
+
y: 12,
|
|
196
|
+
}, animate: {
|
|
197
|
+
filter: "blur(0px)",
|
|
198
|
+
opacity: 1,
|
|
199
|
+
y: 0,
|
|
200
|
+
}, exit: {
|
|
201
|
+
filter: "blur(12px)",
|
|
202
|
+
opacity: 0,
|
|
203
|
+
y: -12,
|
|
204
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
205
|
+
duration: 0.4,
|
|
206
|
+
ease: motionEasing.smooth,
|
|
207
|
+
}), children: _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false }) })] }));
|
|
156
208
|
}
|
|
157
209
|
// Render content interleaved with tool calls
|
|
158
210
|
// Group consecutive tool calls with the same batchId or same title
|
|
@@ -164,18 +216,18 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
164
216
|
const flushBatch = () => {
|
|
165
217
|
if (currentBatch.length > 1) {
|
|
166
218
|
// Group multiple consecutive calls (by batchId or same title)
|
|
167
|
-
elements.push(_jsx(
|
|
219
|
+
elements.push(_jsx(ToolOperation, { toolCalls: currentBatch, isGrouped: true }, `group-${currentBatchId || currentBatchTitle}-${currentBatch[0].id}`));
|
|
168
220
|
}
|
|
169
221
|
else if (currentBatch.length === 1) {
|
|
170
|
-
elements.push(_jsx("div", { children: _jsx(
|
|
222
|
+
elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [currentBatch[0]], isGrouped: false }) }, `tool-${currentBatch[0].id}`));
|
|
171
223
|
}
|
|
172
224
|
currentBatch = [];
|
|
173
225
|
currentBatchId = undefined;
|
|
174
226
|
currentBatchTitle = undefined;
|
|
175
227
|
};
|
|
176
228
|
// Separate preliminary tool calls - they should render at the end, not break text
|
|
177
|
-
const preliminaryToolCalls = sortedToolCalls.filter(
|
|
178
|
-
const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !
|
|
229
|
+
const preliminaryToolCalls = sortedToolCalls.filter(isPreliminaryToolCall);
|
|
230
|
+
const nonPreliminaryToolCalls = sortedToolCalls.filter((tc) => !isPreliminaryToolCall(tc));
|
|
179
231
|
// Process non-preliminary tool calls inline with text
|
|
180
232
|
nonPreliminaryToolCalls.forEach((toolCall, index) => {
|
|
181
233
|
const position = toolCall.contentPosition ?? message.content.length;
|
|
@@ -185,7 +237,22 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
185
237
|
flushBatch();
|
|
186
238
|
const textChunk = message.content.slice(currentPosition, position);
|
|
187
239
|
if (textChunk) {
|
|
188
|
-
elements.push(_jsx(
|
|
240
|
+
elements.push(_jsx(motion.div, { initial: {
|
|
241
|
+
filter: "blur(12px)",
|
|
242
|
+
opacity: 0,
|
|
243
|
+
y: 12,
|
|
244
|
+
}, animate: {
|
|
245
|
+
filter: "blur(0px)",
|
|
246
|
+
opacity: 1,
|
|
247
|
+
y: 0,
|
|
248
|
+
}, exit: {
|
|
249
|
+
filter: "blur(12px)",
|
|
250
|
+
opacity: 0,
|
|
251
|
+
y: -12,
|
|
252
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
253
|
+
duration: 0.4,
|
|
254
|
+
ease: motionEasing.smooth,
|
|
255
|
+
}), children: _jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }) }, `text-before-${toolCall.id}`));
|
|
189
256
|
}
|
|
190
257
|
}
|
|
191
258
|
// Check if this tool call should be batched (by batchId or consecutive same title)
|
|
@@ -223,20 +290,65 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
|
|
|
223
290
|
if (currentPosition < message.content.length) {
|
|
224
291
|
const remainingText = message.content.slice(currentPosition);
|
|
225
292
|
if (remainingText) {
|
|
226
|
-
elements.push(_jsx(
|
|
293
|
+
elements.push(_jsx(motion.div, { initial: {
|
|
294
|
+
filter: "blur(12px)",
|
|
295
|
+
opacity: 0,
|
|
296
|
+
y: 12,
|
|
297
|
+
}, animate: {
|
|
298
|
+
filter: "blur(0px)",
|
|
299
|
+
opacity: 1,
|
|
300
|
+
y: 0,
|
|
301
|
+
}, exit: {
|
|
302
|
+
filter: "blur(12px)",
|
|
303
|
+
opacity: 0,
|
|
304
|
+
y: -12,
|
|
305
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
306
|
+
duration: 0.4,
|
|
307
|
+
ease: motionEasing.smooth,
|
|
308
|
+
}), children: _jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }) }, "text-end"));
|
|
227
309
|
}
|
|
228
310
|
}
|
|
229
|
-
// Render preliminary (
|
|
311
|
+
// Render preliminary (selecting) tool calls at the end, grouped
|
|
230
312
|
if (preliminaryToolCalls.length > 0) {
|
|
231
313
|
if (preliminaryToolCalls.length > 1) {
|
|
232
|
-
elements.push(_jsx(
|
|
314
|
+
elements.push(_jsx(ToolOperation, { toolCalls: preliminaryToolCalls, isGrouped: true }, `selecting-group-${preliminaryToolCalls[0].id}`));
|
|
233
315
|
}
|
|
234
316
|
else {
|
|
235
|
-
elements.push(_jsx("div", { children: _jsx(
|
|
317
|
+
elements.push(_jsx("div", { children: _jsx(ToolOperation, { toolCalls: [preliminaryToolCalls[0]], isGrouped: false }) }, `tool-${preliminaryToolCalls[0].id}`));
|
|
236
318
|
}
|
|
237
319
|
}
|
|
238
|
-
return _jsx(
|
|
239
|
-
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx(
|
|
320
|
+
return (_jsx(AnimatePresence, { mode: "popLayout", children: elements }));
|
|
321
|
+
})()) : (_jsxs("div", { className: "flex flex-col gap-2", children: [message.images && message.images.length > 0 && (_jsx(motion.div, { className: "flex flex-wrap gap-2", initial: {
|
|
322
|
+
filter: "blur(12px)",
|
|
323
|
+
opacity: 0,
|
|
324
|
+
y: 12,
|
|
325
|
+
}, animate: {
|
|
326
|
+
filter: "blur(0px)",
|
|
327
|
+
opacity: 1,
|
|
328
|
+
y: 0,
|
|
329
|
+
}, exit: {
|
|
330
|
+
filter: "blur(12px)",
|
|
331
|
+
opacity: 0,
|
|
332
|
+
y: -12,
|
|
333
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
334
|
+
duration: 0.5,
|
|
335
|
+
ease: motionEasing.smooth,
|
|
336
|
+
}), children: message.images.map((image, index) => (_jsx("img", { src: `data:${image.mimeType};base64,${image.data}`, alt: `Attachment ${index + 1}`, className: "max-w-[200px] max-h-[200px] rounded-lg object-cover" }, index))) })), message.content && (_jsx(motion.div, { className: "whitespace-pre-wrap", initial: {
|
|
337
|
+
filter: "blur(12px)",
|
|
338
|
+
opacity: 0,
|
|
339
|
+
y: 12,
|
|
340
|
+
}, animate: {
|
|
341
|
+
filter: "blur(0px)",
|
|
342
|
+
opacity: 1,
|
|
343
|
+
y: 0,
|
|
344
|
+
}, exit: {
|
|
345
|
+
filter: "blur(12px)",
|
|
346
|
+
opacity: 0,
|
|
347
|
+
y: -12,
|
|
348
|
+
}, transition: getTransition(shouldReduceMotion ?? false, {
|
|
349
|
+
duration: 0.4,
|
|
350
|
+
ease: motionEasing.smooth,
|
|
351
|
+
}), children: message.content }))] }))] }));
|
|
240
352
|
}
|
|
241
353
|
return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
|
|
242
354
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AcpClient } from "../../sdk/client/index.js";
|
|
2
|
+
export interface SessionHistoryProps {
|
|
3
|
+
client: AcpClient | null;
|
|
4
|
+
currentSessionId: string | null;
|
|
5
|
+
onSessionSelect?: ((sessionId: string) => void) | undefined;
|
|
6
|
+
onRenameSession?: ((sessionId: string) => void) | undefined;
|
|
7
|
+
onArchiveSession?: ((sessionId: string) => void) | undefined;
|
|
8
|
+
onDeleteSession?: ((sessionId: string) => void) | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare function SessionHistory({ client, currentSessionId, onSessionSelect, onRenameSession, onArchiveSession, onDeleteSession, }: SessionHistoryProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createLogger } from "@townco/core";
|
|
3
|
+
import { Loader2 } from "lucide-react";
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { SessionHistoryItem } from "./SessionHistoryItem.js";
|
|
6
|
+
import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, useSidebar, } from "./Sidebar.js";
|
|
7
|
+
const logger = createLogger("session-history");
|
|
8
|
+
const isToday = (date) => {
|
|
9
|
+
const today = new Date();
|
|
10
|
+
return (date.getDate() === today.getDate() &&
|
|
11
|
+
date.getMonth() === today.getMonth() &&
|
|
12
|
+
date.getFullYear() === today.getFullYear());
|
|
13
|
+
};
|
|
14
|
+
const isYesterday = (date) => {
|
|
15
|
+
const yesterday = new Date();
|
|
16
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
17
|
+
return (date.getDate() === yesterday.getDate() &&
|
|
18
|
+
date.getMonth() === yesterday.getMonth() &&
|
|
19
|
+
date.getFullYear() === yesterday.getFullYear());
|
|
20
|
+
};
|
|
21
|
+
const groupSessionsByDate = (sessions) => {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
24
|
+
const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
25
|
+
return sessions.reduce((groups, session) => {
|
|
26
|
+
const sessionDate = new Date(session.updatedAt);
|
|
27
|
+
if (isToday(sessionDate)) {
|
|
28
|
+
groups.today.push(session);
|
|
29
|
+
}
|
|
30
|
+
else if (isYesterday(sessionDate)) {
|
|
31
|
+
groups.yesterday.push(session);
|
|
32
|
+
}
|
|
33
|
+
else if (sessionDate > oneWeekAgo) {
|
|
34
|
+
groups.lastWeek.push(session);
|
|
35
|
+
}
|
|
36
|
+
else if (sessionDate > oneMonthAgo) {
|
|
37
|
+
groups.lastMonth.push(session);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
groups.older.push(session);
|
|
41
|
+
}
|
|
42
|
+
return groups;
|
|
43
|
+
}, {
|
|
44
|
+
today: [],
|
|
45
|
+
yesterday: [],
|
|
46
|
+
lastWeek: [],
|
|
47
|
+
lastMonth: [],
|
|
48
|
+
older: [],
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
export function SessionHistory({ client, currentSessionId, onSessionSelect, onRenameSession, onArchiveSession, onDeleteSession, }) {
|
|
52
|
+
const { setOpenMobile } = useSidebar();
|
|
53
|
+
const [sessions, setSessions] = useState([]);
|
|
54
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
55
|
+
const fetchSessions = useCallback(async () => {
|
|
56
|
+
if (!client)
|
|
57
|
+
return;
|
|
58
|
+
setIsLoading(true);
|
|
59
|
+
try {
|
|
60
|
+
const sessionList = await client.listSessions();
|
|
61
|
+
setSessions(sessionList);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error("Failed to fetch sessions", { error });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
setIsLoading(false);
|
|
68
|
+
}
|
|
69
|
+
}, [client]);
|
|
70
|
+
// Fetch sessions on mount and when client changes
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
fetchSessions();
|
|
73
|
+
}, [fetchSessions]);
|
|
74
|
+
const handleSessionSelect = (sessionId) => {
|
|
75
|
+
if (sessionId === currentSessionId) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (onSessionSelect) {
|
|
79
|
+
onSessionSelect(sessionId);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Default behavior: update URL with session ID
|
|
83
|
+
const url = new URL(window.location.href);
|
|
84
|
+
url.searchParams.set("session", sessionId);
|
|
85
|
+
window.location.href = url.toString();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
if (!client) {
|
|
89
|
+
return (_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx("div", { className: "flex w-full flex-row items-center justify-center gap-2 px-2 text-sm text-muted-foreground", children: "Connect to an agent to see session history" }) }) }));
|
|
90
|
+
}
|
|
91
|
+
if (isLoading) {
|
|
92
|
+
return (_jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: "Today" }), _jsx(SidebarGroupContent, { children: _jsx("div", { className: "flex flex-col", children: [44, 32, 28, 64, 52].map((item) => (_jsx("div", { className: "flex h-8 items-center gap-2 rounded-md px-2", children: _jsx("div", { className: "h-4 flex-1 rounded-md bg-sidebar-accent-foreground/10 animate-pulse", style: {
|
|
93
|
+
maxWidth: `${item}%`,
|
|
94
|
+
} }) }, item))) }) })] }));
|
|
95
|
+
}
|
|
96
|
+
if (sessions.length === 0) {
|
|
97
|
+
return (_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx("div", { className: "flex w-full flex-row items-center justify-center gap-2 px-2 py-4 text-sm text-muted-foreground", children: "Your sessions will appear here once you start chatting!" }) }) }));
|
|
98
|
+
}
|
|
99
|
+
const groupedSessions = groupSessionsByDate(sessions);
|
|
100
|
+
return (_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [groupedSessions.today.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "px-2 py-1 text-sidebar-foreground/50 text-xs", children: "Today" }), groupedSessions.today.map((session) => (_jsx(SessionHistoryItem, { session: session, isActive: session.sessionId === currentSessionId, onSelect: handleSessionSelect, onRename: onRenameSession, onArchive: onArchiveSession, onDelete: onDeleteSession, setOpenMobile: setOpenMobile }, session.sessionId)))] })), groupedSessions.yesterday.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "px-2 py-1 text-sidebar-foreground/50 text-xs", children: "Yesterday" }), groupedSessions.yesterday.map((session) => (_jsx(SessionHistoryItem, { session: session, isActive: session.sessionId === currentSessionId, onSelect: handleSessionSelect, onRename: onRenameSession, onArchive: onArchiveSession, onDelete: onDeleteSession, setOpenMobile: setOpenMobile }, session.sessionId)))] })), groupedSessions.lastWeek.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "px-2 py-1 text-sidebar-foreground/50 text-xs", children: "Last 7 days" }), groupedSessions.lastWeek.map((session) => (_jsx(SessionHistoryItem, { session: session, isActive: session.sessionId === currentSessionId, onSelect: handleSessionSelect, onRename: onRenameSession, onArchive: onArchiveSession, onDelete: onDeleteSession, setOpenMobile: setOpenMobile }, session.sessionId)))] })), groupedSessions.lastMonth.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "px-2 py-1 text-sidebar-foreground/50 text-xs", children: "Last 30 days" }), groupedSessions.lastMonth.map((session) => (_jsx(SessionHistoryItem, { session: session, isActive: session.sessionId === currentSessionId, onSelect: handleSessionSelect, onRename: onRenameSession, onArchive: onArchiveSession, onDelete: onDeleteSession, setOpenMobile: setOpenMobile }, session.sessionId)))] })), groupedSessions.older.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "px-2 py-1 text-sidebar-foreground/50 text-xs", children: "Older" }), groupedSessions.older.map((session) => (_jsx(SessionHistoryItem, { session: session, isActive: session.sessionId === currentSessionId, onSelect: handleSessionSelect, onRename: onRenameSession, onArchive: onArchiveSession, onDelete: onDeleteSession, setOpenMobile: setOpenMobile }, session.sessionId)))] }))] }) }) }) }));
|
|
101
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionSummary } from "../../sdk/transports/index.js";
|
|
2
|
+
export interface SessionHistoryItemProps {
|
|
3
|
+
session: SessionSummary;
|
|
4
|
+
isActive: boolean;
|
|
5
|
+
onSelect: (sessionId: string) => void;
|
|
6
|
+
onRename?: ((sessionId: string) => void) | undefined;
|
|
7
|
+
onArchive?: ((sessionId: string) => void) | undefined;
|
|
8
|
+
onDelete?: ((sessionId: string) => void) | undefined;
|
|
9
|
+
setOpenMobile: (open: boolean) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const SessionHistoryItem: import("react").MemoExoticComponent<({ session, isActive, onSelect, onRename, onArchive, onDelete, setOpenMobile, }: SessionHistoryItemProps) => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MoreHorizontal } from "lucide-react";
|
|
3
|
+
import { memo } from "react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./DropdownMenu.js";
|
|
6
|
+
import { SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, } from "./Sidebar.js";
|
|
7
|
+
const PureSessionHistoryItem = ({ session, isActive, onSelect, onRename, onArchive, onDelete, setOpenMobile, }) => {
|
|
8
|
+
return (_jsxs(SidebarMenuItem, { children: [_jsx(SidebarMenuButton, { asChild: true, isActive: isActive, onClick: () => {
|
|
9
|
+
onSelect(session.sessionId);
|
|
10
|
+
setOpenMobile(false);
|
|
11
|
+
}, children: _jsx("button", { type: "button", className: "w-full", children: _jsx("span", { className: "truncate", children: session.firstUserMessage || "Empty session" }) }) }), _jsxs(DropdownMenu, { modal: true, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(SidebarMenuAction, { className: "mr-0.5 data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", showOnHover: !isActive, children: [_jsx(MoreHorizontal, {}), _jsx("span", { className: "sr-only", children: "More" })] }) }), _jsxs(DropdownMenuContent, { align: "end", side: "bottom", children: [_jsx(DropdownMenuItem, { className: "cursor-pointer", disabled: !onRename, onSelect: () => onRename?.(session.sessionId), children: "Rename" }), _jsx(DropdownMenuItem, { className: "cursor-pointer", disabled: !onArchive, onSelect: () => onArchive?.(session.sessionId), children: "Archive" }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { className: cn("cursor-pointer text-destructive focus:bg-destructive/15 focus:text-destructive"), disabled: !onDelete, onSelect: () => onDelete?.(session.sessionId), children: "Delete" })] })] })] }));
|
|
12
|
+
};
|
|
13
|
+
export const SessionHistoryItem = memo(PureSessionHistoryItem, (prevProps, nextProps) => {
|
|
14
|
+
if (prevProps.isActive !== nextProps.isActive) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (prevProps.session.sessionId !== nextProps.session.sessionId) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (prevProps.session.firstUserMessage !== nextProps.session.firstUserMessage) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
declare const Sheet: React.FC<DialogPrimitive.DialogProps>;
|
|
5
|
+
declare const SheetTrigger: React.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
6
|
+
declare const SheetClose: React.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
|
|
7
|
+
declare const SheetPortal: React.FC<DialogPrimitive.DialogPortalProps>;
|
|
8
|
+
declare const SheetOverlay: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
9
|
+
declare const sheetVariants: (props?: ({
|
|
10
|
+
side?: "top" | "right" | "bottom" | "left" | null | undefined;
|
|
11
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
12
|
+
interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, VariantProps<typeof sheetVariants> {
|
|
13
|
+
}
|
|
14
|
+
declare const SheetContent: React.ForwardRefExoticComponent<SheetContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
15
|
+
declare const SheetHeader: {
|
|
16
|
+
({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
displayName: string;
|
|
18
|
+
};
|
|
19
|
+
declare const SheetFooter: {
|
|
20
|
+
({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
displayName: string;
|
|
22
|
+
};
|
|
23
|
+
declare const SheetTitle: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
|
|
24
|
+
declare const SheetDescription: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
|
|
25
|
+
export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { cn } from "../lib/utils.js";
|
|
7
|
+
const Sheet = DialogPrimitive.Root;
|
|
8
|
+
const SheetTrigger = DialogPrimitive.Trigger;
|
|
9
|
+
const SheetClose = DialogPrimitive.Close;
|
|
10
|
+
const SheetPortal = DialogPrimitive.Portal;
|
|
11
|
+
const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Overlay, { className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className), ...props, ref: ref })));
|
|
12
|
+
SheetOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
13
|
+
const sheetVariants = cva("fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", {
|
|
14
|
+
variants: {
|
|
15
|
+
side: {
|
|
16
|
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
17
|
+
bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
18
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
19
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
side: "right",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn(sheetVariants({ side }), className), ...props, children: [children, _jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] })] })] })));
|
|
27
|
+
SheetContent.displayName = DialogPrimitive.Content.displayName;
|
|
28
|
+
const SheetHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-2 text-center sm:text-left", className), ...props }));
|
|
29
|
+
SheetHeader.displayName = "SheetHeader";
|
|
30
|
+
const SheetFooter = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }));
|
|
31
|
+
SheetFooter.displayName = "SheetFooter";
|
|
32
|
+
const SheetTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn("text-lg font-semibold text-foreground", className), ...props })));
|
|
33
|
+
SheetTitle.displayName = DialogPrimitive.Title.displayName;
|
|
34
|
+
const SheetDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-sm text-muted-foreground", className), ...props })));
|
|
35
|
+
SheetDescription.displayName = DialogPrimitive.Description.displayName;
|
|
36
|
+
export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, };
|