@moldable-ai/ui 0.2.5 → 0.2.7

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.
Files changed (100) hide show
  1. package/LICENSE +75 -71
  2. package/dist/components/chat/chat-input.d.ts +15 -3
  3. package/dist/components/chat/chat-input.d.ts.map +1 -1
  4. package/dist/components/chat/chat-input.js +19 -8
  5. package/dist/components/chat/chat-message.d.ts +6 -0
  6. package/dist/components/chat/chat-message.d.ts.map +1 -1
  7. package/dist/components/chat/chat-message.js +20 -5
  8. package/dist/components/chat/chat-messages.d.ts +26 -1
  9. package/dist/components/chat/chat-messages.d.ts.map +1 -1
  10. package/dist/components/chat/chat-messages.js +33 -22
  11. package/dist/components/chat/chat-panel.d.ts +40 -3
  12. package/dist/components/chat/chat-panel.d.ts.map +1 -1
  13. package/dist/components/chat/chat-panel.js +173 -36
  14. package/dist/components/chat/checkpoint-badge.d.ts +20 -0
  15. package/dist/components/chat/checkpoint-badge.d.ts.map +1 -0
  16. package/dist/components/chat/checkpoint-badge.js +16 -0
  17. package/dist/components/chat/conversation-history.d.ts +6 -1
  18. package/dist/components/chat/conversation-history.d.ts.map +1 -1
  19. package/dist/components/chat/conversation-history.js +6 -6
  20. package/dist/components/chat/index.d.ts +5 -3
  21. package/dist/components/chat/index.d.ts.map +1 -1
  22. package/dist/components/chat/index.js +3 -1
  23. package/dist/components/chat/markdown.d.ts +9 -0
  24. package/dist/components/chat/markdown.d.ts.map +1 -0
  25. package/dist/components/chat/markdown.js +47 -0
  26. package/dist/components/chat/model-selector.d.ts +3 -1
  27. package/dist/components/chat/model-selector.d.ts.map +1 -1
  28. package/dist/components/chat/model-selector.js +2 -2
  29. package/dist/components/chat/reasoning-effort-selector.d.ts +4 -1
  30. package/dist/components/chat/reasoning-effort-selector.d.ts.map +1 -1
  31. package/dist/components/chat/reasoning-effort-selector.js +6 -6
  32. package/dist/components/chat/restore-dialog.d.ts +33 -0
  33. package/dist/components/chat/restore-dialog.d.ts.map +1 -0
  34. package/dist/components/chat/restore-dialog.js +23 -0
  35. package/dist/components/chat/tool-handlers.d.ts.map +1 -1
  36. package/dist/components/chat/tool-handlers.js +194 -2
  37. package/dist/components/ui/alert-dialog.d.ts +8 -4
  38. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  39. package/dist/components/ui/alert-dialog.js +14 -11
  40. package/dist/components/ui/avatar.d.ts +7 -2
  41. package/dist/components/ui/avatar.d.ts.map +1 -1
  42. package/dist/components/ui/avatar.js +13 -4
  43. package/dist/components/ui/badge.d.ts +1 -1
  44. package/dist/components/ui/badge.d.ts.map +1 -1
  45. package/dist/components/ui/badge.js +9 -7
  46. package/dist/components/ui/button.d.ts +3 -4
  47. package/dist/components/ui/button.d.ts.map +1 -1
  48. package/dist/components/ui/button.js +6 -6
  49. package/dist/components/ui/card.d.ts +8 -7
  50. package/dist/components/ui/card.d.ts.map +1 -1
  51. package/dist/components/ui/card.js +22 -14
  52. package/dist/components/ui/checkbox.js +1 -1
  53. package/dist/components/ui/collapsible.d.ts +3 -3
  54. package/dist/components/ui/collapsible.d.ts.map +1 -1
  55. package/dist/components/ui/collapsible.js +11 -3
  56. package/dist/components/ui/combobox.d.ts +25 -0
  57. package/dist/components/ui/combobox.d.ts.map +1 -0
  58. package/dist/components/ui/combobox.js +58 -0
  59. package/dist/components/ui/command.d.ts +15 -15
  60. package/dist/components/ui/command.d.ts.map +1 -1
  61. package/dist/components/ui/command.js +29 -24
  62. package/dist/components/ui/dialog.d.ts +15 -17
  63. package/dist/components/ui/dialog.d.ts.map +1 -1
  64. package/dist/components/ui/dialog.js +33 -19
  65. package/dist/components/ui/dropdown-menu.js +6 -6
  66. package/dist/components/ui/input-group.d.ts +2 -2
  67. package/dist/components/ui/input.d.ts +1 -2
  68. package/dist/components/ui/input.d.ts.map +1 -1
  69. package/dist/components/ui/input.js +4 -5
  70. package/dist/components/ui/item.d.ts +1 -1
  71. package/dist/components/ui/label.d.ts +1 -1
  72. package/dist/components/ui/label.d.ts.map +1 -1
  73. package/dist/components/ui/label.js +3 -3
  74. package/dist/components/ui/popover.d.ts +4 -1
  75. package/dist/components/ui/popover.d.ts.map +1 -1
  76. package/dist/components/ui/popover.js +10 -1
  77. package/dist/components/ui/scroll-area.d.ts +1 -1
  78. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  79. package/dist/components/ui/scroll-area.js +9 -5
  80. package/dist/components/ui/select.d.ts +13 -11
  81. package/dist/components/ui/select.d.ts.map +1 -1
  82. package/dist/components/ui/select.js +35 -23
  83. package/dist/components/ui/sheet.d.ts +2 -1
  84. package/dist/components/ui/sheet.d.ts.map +1 -1
  85. package/dist/components/ui/sheet.js +2 -2
  86. package/dist/components/ui/switch.d.ts +4 -2
  87. package/dist/components/ui/switch.d.ts.map +1 -1
  88. package/dist/components/ui/switch.js +4 -4
  89. package/dist/components/ui/tabs.d.ts +7 -3
  90. package/dist/components/ui/tabs.d.ts.map +1 -1
  91. package/dist/components/ui/tabs.js +18 -6
  92. package/dist/components/ui/textarea.d.ts +1 -2
  93. package/dist/components/ui/textarea.d.ts.map +1 -1
  94. package/dist/components/ui/textarea.js +4 -5
  95. package/dist/components/ui/tooltip.js +1 -1
  96. package/dist/lib/commands.d.ts +49 -0
  97. package/dist/lib/commands.d.ts.map +1 -1
  98. package/dist/lib/commands.js +56 -0
  99. package/package.json +10 -7
  100. package/src/styles/index.css +7 -0
@@ -1,35 +1,55 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { AlertCircle, ChevronDown, MessageCircle, Plus } from 'lucide-react';
4
- import { useCallback, useEffect, useRef, useState, } from 'react';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AlertCircle, ChevronDown, Code2, CornerDownRight, Image as ImageIcon, ListPlus, MessageCircle, X, } from 'lucide-react';
4
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
5
5
  import { cn } from '../../lib/utils';
6
6
  import { Button } from '../ui/button';
7
7
  import { ScrollArea } from '../ui/scroll-area';
8
8
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
9
- import { ChatInput } from './chat-input';
10
- import { Messages } from './chat-messages';
9
+ import { ChatInput, } from './chat-input';
10
+ import { ThinkingMessage } from './chat-message';
11
+ import { MessageRow, computeIsThinking, } from './chat-messages';
11
12
  import { ConversationHistory, } from './conversation-history';
12
13
  import { ModelSelector } from './model-selector';
13
14
  import { ReasoningEffortSelector, } from './reasoning-effort-selector';
14
15
  import { ToolApprovalProvider, } from './tool-approval-context';
15
16
  import { ToolProgressProvider } from './tool-progress-context';
16
17
  import { AnimatePresence, motion } from 'framer-motion';
18
+ import { Virtualizer } from 'virtua';
17
19
  /**
18
20
  * Floating chat panel with model selector
19
21
  */
20
- export function ChatPanel({ messages, status, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder = 'Ask anything...', welcomeMessage, isExpanded, onExpandedChange, isMinimized = false, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress = {}, onApprovalResponse, }) {
22
+ export function ChatPanel({ messages, status, isTurnInFlight, input, onInputChange, onSubmit, onStop, onNewChat, models, selectedModel, onModelChange, reasoningEffortOptions, selectedReasoningEffort, onReasoningEffortChange, conversations, currentConversationId, onSelectConversation, onDeleteConversation, placeholder = 'Ask anything...', welcomeMessage, isExpanded, onExpandedChange, isMinimized = false, onMinimizedChange, className, error, missingApiKey, onAddApiKey, toolProgress = {}, onApprovalResponse, isEditingApp = true, onEditingAppChange, showEditingAppToggle = false, checkpoints, restoringMessageId, onRestoreCheckpoint, imageAttachments = [], onRemoveImageAttachment, isImageDropActive = false, queuedMessages = [], onRemoveQueuedMessage, onRetryQueuedMessage, onSteerQueuedMessage, }) {
21
23
  const inputRef = useRef(null);
22
- const messagesEndRef = useRef(null);
23
24
  const scrollAreaRef = useRef(null);
25
+ const viewportRef = useRef(null);
26
+ const virtualizerRef = useRef(null);
27
+ const prevMessageCountRef = useRef(messages.length);
28
+ const scrollRafRef = useRef(null);
29
+ const [viewportVersion, setViewportVersion] = useState(0);
24
30
  const [isAtBottom, setIsAtBottom] = useState(true);
25
31
  const isResponding = status === 'streaming' || status === 'submitted';
26
- // Track scroll position to detect if user is at bottom
32
+ const controlsDisabled = isTurnInFlight ?? isResponding;
33
+ const composerControls = (_jsxs(_Fragment, { children: [_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelChange: onModelChange, disabled: controlsDisabled, dropdownAlign: "end", dropdownSide: "top" }), reasoningEffortOptions &&
34
+ selectedReasoningEffort &&
35
+ onReasoningEffortChange && (_jsx(ReasoningEffortSelector, { options: reasoningEffortOptions, selectedEffort: selectedReasoningEffort, onEffortChange: onReasoningEffortChange, disabled: controlsDisabled, showLabel: true, dropdownAlign: "end", dropdownSide: "top" }))] }));
36
+ // Capture the Radix viewport element so virtua can use it as the scroll container
27
37
  useEffect(() => {
28
38
  const scrollArea = scrollAreaRef.current;
29
39
  if (!scrollArea)
30
40
  return;
31
- // Radix ScrollArea puts the viewport as first child element
32
41
  const viewport = scrollArea.querySelector('[data-radix-scroll-area-viewport]');
42
+ if (!viewport)
43
+ return;
44
+ viewport.style.overflowAnchor = 'none';
45
+ if (viewportRef.current !== viewport) {
46
+ viewportRef.current = viewport;
47
+ setViewportVersion((version) => version + 1);
48
+ }
49
+ }, [isExpanded]);
50
+ // Track scroll position to detect if user is at bottom
51
+ useEffect(() => {
52
+ const viewport = viewportRef.current;
33
53
  if (!viewport)
34
54
  return;
35
55
  const handleScroll = () => {
@@ -38,9 +58,10 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
38
58
  const atBottom = scrollHeight - scrollTop - clientHeight < 50;
39
59
  setIsAtBottom(atBottom);
40
60
  };
61
+ handleScroll();
41
62
  viewport.addEventListener('scroll', handleScroll);
42
63
  return () => viewport.removeEventListener('scroll', handleScroll);
43
- }, [isExpanded]);
64
+ }, [viewportVersion]);
44
65
  // Focus input when expanded
45
66
  useEffect(() => {
46
67
  if (isExpanded) {
@@ -50,18 +71,13 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
50
71
  return () => clearTimeout(timer);
51
72
  }
52
73
  }, [isExpanded]);
53
- // Scroll to bottom on new messages only if user is already at bottom
54
- useEffect(() => {
55
- if (isExpanded && messagesEndRef.current && isAtBottom) {
56
- messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
57
- }
58
- }, [messages, isExpanded, isAtBottom]);
59
- const handleSubmit = useCallback((e) => {
74
+ const handleSubmit = useCallback((e, mode) => {
60
75
  e?.preventDefault();
61
- if (!input.trim())
76
+ if (!input.trim() && imageAttachments.length === 0)
62
77
  return;
63
- // If streaming, stop the current response first
64
- if (isResponding && onStop) {
78
+ const submitMode = mode ?? (isResponding ? 'steer' : 'send');
79
+ // Steer interrupts the current response and sends the new instruction now.
80
+ if (submitMode === 'steer' && isResponding && onStop) {
65
81
  onStop();
66
82
  }
67
83
  if (!isExpanded) {
@@ -69,26 +85,44 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
69
85
  }
70
86
  // Reset to bottom when user sends a message
71
87
  setIsAtBottom(true);
72
- onSubmit(e);
73
- }, [input, isResponding, isExpanded, onExpandedChange, onSubmit, onStop]);
88
+ onSubmit(e, submitMode);
89
+ }, [
90
+ imageAttachments.length,
91
+ input,
92
+ isResponding,
93
+ isExpanded,
94
+ onExpandedChange,
95
+ onSubmit,
96
+ onStop,
97
+ ]);
74
98
  const handleNewChat = useCallback(() => {
75
99
  onNewChat?.();
76
100
  inputRef.current?.focus();
77
101
  }, [onNewChat]);
102
+ const composerStartControls = (_jsxs(_Fragment, { children: [conversations &&
103
+ (conversations.length > 0 || onNewChat) &&
104
+ onSelectConversation && (_jsx(ConversationHistory, { conversations: conversations, currentConversationId: currentConversationId, onSelect: onSelectConversation, onDelete: onDeleteConversation, onNewChat: handleNewChat, newChatDisabled: controlsDisabled, disabled: controlsDisabled, dropdownAlign: "start", dropdownSide: "top", tooltipSide: "top" })), showEditingAppToggle && onEditingAppChange && (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: (event) => {
105
+ event.preventDefault();
106
+ onEditingAppChange(!isEditingApp);
107
+ }, className: cn('cursor-pointer', isEditingApp
108
+ ? 'text-primary bg-primary/10 hover:bg-primary/20'
109
+ : 'text-muted-foreground hover:text-foreground'), children: _jsx(Code2, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "top", children: _jsx("p", { children: isEditingApp
110
+ ? 'Editing this app (click to disable)'
111
+ : 'Not editing app (click to enable)' }) })] }) }))] }));
78
112
  // Keyboard shortcut: Cmd+Shift+O for new chat
79
113
  useEffect(() => {
80
114
  const handleKeyDown = (e) => {
81
115
  if (e.key === 'o' && e.metaKey && e.shiftKey) {
82
116
  e.preventDefault();
83
- // Only create new chat if not currently streaming and panel is expanded
84
- if (!isResponding && isExpanded) {
117
+ // Only create new chat if conversation controls are currently available.
118
+ if (!controlsDisabled && isExpanded) {
85
119
  handleNewChat();
86
120
  }
87
121
  }
88
122
  };
89
123
  document.addEventListener('keydown', handleKeyDown);
90
124
  return () => document.removeEventListener('keydown', handleKeyDown);
91
- }, [isResponding, isExpanded, handleNewChat]);
125
+ }, [controlsDisabled, isExpanded, handleNewChat]);
92
126
  // Handle clicking the minimized flap
93
127
  const handleFlapClick = useCallback(() => {
94
128
  onMinimizedChange?.(false);
@@ -100,11 +134,110 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
100
134
  }
101
135
  onMinimizedChange?.(true);
102
136
  }, [isExpanded, onExpandedChange, onMinimizedChange]);
137
+ const isStreaming = status === 'streaming' || status === 'submitted';
138
+ const isThinking = useMemo(() => computeIsThinking(messages, status), [messages, status]);
139
+ const showMissingApiKeyPrompt = missingApiKey ||
140
+ (error &&
141
+ (error.message?.includes('API_KEY not configured') ||
142
+ error.message?.includes('credential required in Vault') ||
143
+ error.message?.includes('credential not configured in Vault')));
144
+ const showErrorMessage = Boolean(error) && status === 'error' && !showMissingApiKeyPrompt;
145
+ const lastMessageId = messages[messages.length - 1]?.id;
146
+ const listItems = useMemo(() => {
147
+ const items = [];
148
+ if (messages.length === 0 && welcomeMessage) {
149
+ items.push({ key: 'welcome', kind: 'welcome' });
150
+ }
151
+ for (const message of messages) {
152
+ items.push({ key: message.id, kind: 'message', message });
153
+ }
154
+ if (isThinking) {
155
+ items.push({ key: 'thinking', kind: 'thinking' });
156
+ }
157
+ if (showMissingApiKeyPrompt) {
158
+ items.push({ key: 'missing-api-key', kind: 'missing-api-key' });
159
+ }
160
+ if (showErrorMessage) {
161
+ items.push({ key: 'error', kind: 'error' });
162
+ }
163
+ return items;
164
+ }, [
165
+ isThinking,
166
+ messages,
167
+ showErrorMessage,
168
+ showMissingApiKeyPrompt,
169
+ welcomeMessage,
170
+ ]);
171
+ const renderVirtualItem = useCallback((item) => {
172
+ switch (item.kind) {
173
+ case 'welcome':
174
+ return (_jsx("div", { className: "px-2 pb-4", children: messages.length === 0 && welcomeMessage && (_jsx("div", { className: "bg-muted/50 text-muted-foreground mx-2 rounded-2xl p-4 text-sm", children: welcomeMessage })) }));
175
+ case 'message': {
176
+ const checkpoint = checkpoints?.get(item.message.id);
177
+ return (_jsx("div", { className: "px-2 pb-4", children: _jsx(MessageRow, { message: item.message, isLast: item.message.id === lastMessageId, isStreaming: isStreaming, checkpoint: checkpoint, restoringMessageId: restoringMessageId, onRestoreCheckpoint: onRestoreCheckpoint }) }));
178
+ }
179
+ case 'thinking':
180
+ return (_jsx("div", { className: "px-2 pb-4", children: _jsx(ThinkingMessage, {}) }));
181
+ case 'missing-api-key':
182
+ return (_jsx("div", { className: "px-2 pb-4", children: _jsxs("div", { className: "border-primary/30 bg-primary/5 mx-2 flex flex-col items-center gap-3 rounded-lg border p-4 text-center", children: [_jsx("div", { className: "bg-primary/10 flex size-10 items-center justify-center rounded-full", children: _jsx(AlertCircle, { className: "text-primary size-5" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-foreground font-medium", children: "API key required" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: "Add an API key to Vault to start chatting with AI" })] }), onAddApiKey && (_jsx(Button, { onClick: onAddApiKey, size: "sm", className: "cursor-pointer", children: "Add API key" }))] }) }));
183
+ case 'error':
184
+ return (_jsx("div", { className: "px-2 pb-4", children: error && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 text-destructive mx-2 flex items-start gap-2 rounded-lg border p-3 text-sm", children: [_jsx(AlertCircle, { className: "mt-0.5 size-4 shrink-0" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium", children: "Request failed" }), _jsx("p", { className: "text-destructive/80 mt-0.5 break-words", children: error.message || 'An unknown error occurred' })] })] })) }));
185
+ }
186
+ }, [
187
+ checkpoints,
188
+ error,
189
+ isStreaming,
190
+ lastMessageId,
191
+ messages.length,
192
+ onAddApiKey,
193
+ onRestoreCheckpoint,
194
+ restoringMessageId,
195
+ welcomeMessage,
196
+ ]);
197
+ // Scroll to bottom on new messages if the user is already at bottom
198
+ useEffect(() => {
199
+ const previousMessageCount = prevMessageCountRef.current;
200
+ const hasNewMessage = messages.length > previousMessageCount;
201
+ prevMessageCountRef.current = messages.length;
202
+ if (!isExpanded || !isAtBottom) {
203
+ return;
204
+ }
205
+ const lastIndex = listItems.length - 1;
206
+ if (lastIndex < 0)
207
+ return;
208
+ if (scrollRafRef.current !== null) {
209
+ cancelAnimationFrame(scrollRafRef.current);
210
+ }
211
+ scrollRafRef.current = requestAnimationFrame(() => {
212
+ virtualizerRef.current?.scrollToIndex(lastIndex, {
213
+ align: 'end',
214
+ smooth: hasNewMessage,
215
+ });
216
+ scrollRafRef.current = null;
217
+ });
218
+ return () => {
219
+ if (scrollRafRef.current !== null) {
220
+ cancelAnimationFrame(scrollRafRef.current);
221
+ scrollRafRef.current = null;
222
+ }
223
+ };
224
+ }, [isAtBottom, isExpanded, listItems.length, messages.length]);
103
225
  return (_jsxs(_Fragment, { children: [_jsx(AnimatePresence, { children: isExpanded && !isMinimized && (_jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, className: "fixed inset-0 z-40 bg-black/20", onClick: () => onExpandedChange(false) })) }), _jsx(AnimatePresence, { children: isMinimized && (_jsx(motion.div, { initial: { y: 20, opacity: 0 }, animate: { y: 0, opacity: 1 }, exit: { y: 20, opacity: 0 }, transition: { type: 'tween', duration: 0.2, ease: 'easeOut' }, className: "fixed bottom-0 left-[calc(50%+var(--sidebar-width,0px)/2)] z-50 -translate-x-1/2", children: _jsxs("button", { onClick: handleFlapClick, className: cn('bg-background/95 hover:bg-background group flex cursor-pointer items-center gap-2 rounded-t-xl border border-b-0 px-4 py-2 shadow-lg backdrop-blur transition-colors', 'text-muted-foreground hover:text-foreground'), children: [_jsx(MessageCircle, { className: "size-4" }), _jsx("span", { className: "text-sm font-medium", children: "Chat" }), _jsx("span", { className: "bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary rounded px-1.5 py-0.5 text-xs transition-colors", children: "\u2318M" })] }) })) }), _jsx(motion.div, { initial: false, animate: {
104
226
  width: isExpanded ? 'min(90vw, 640px)' : '400px',
105
227
  y: isMinimized ? 'calc(100% + 24px)' : 0,
106
228
  opacity: isMinimized ? 0 : 1,
107
- }, transition: { type: 'tween', duration: 0.25, ease: 'easeOut' }, className: cn('pointer-events-none fixed bottom-6 left-[calc(50%+var(--sidebar-width,0px)/2)] z-50 flex -translate-x-1/2 justify-center', isMinimized && 'pointer-events-none', className), children: _jsxs("div", { className: cn('bg-background pointer-events-auto relative w-full border shadow-lg', 'supports-[backdrop-filter]:bg-background/95 backdrop-blur', 'rounded-4xl', isExpanded ? 'overflow-hidden' : 'overflow-visible'), children: [!isExpanded && (_jsx(motion.div, { className: "rounded-4xl pointer-events-none absolute inset-0 z-0", animate: {
229
+ }, transition: { type: 'tween', duration: 0.25, ease: 'easeOut' }, className: cn('pointer-events-none fixed bottom-6 left-[calc(50%+var(--sidebar-width,0px)/2)] z-50 flex -translate-x-1/2 justify-center', isMinimized && 'pointer-events-none', className), children: _jsxs("div", { "data-chat-drop-target": true, className: cn('bg-background/80 pointer-events-auto relative w-full shadow-[0_8px_40px_-12px_rgba(0,0,0,0.15)]', isExpanded
230
+ ? 'bg-background'
231
+ : 'bg-background/80 supports-[backdrop-filter]:backdrop-blur-xl', 'rounded-[28px]', isExpanded ? 'overflow-hidden' : 'overflow-visible'), children: [!isExpanded && (_jsx("div", { className: "pointer-events-none absolute inset-0 z-0 rounded-[inherit]", style: {
232
+ background: 'linear-gradient(134deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 50%, transparent 55%)',
233
+ } })), isImageDropActive && (_jsxs("div", { className: "bg-background/90 border-primary/50 text-foreground absolute inset-0 z-[70] flex flex-col items-center justify-center gap-2 rounded-[inherit] border-2 border-dashed backdrop-blur-sm", children: [_jsx(ImageIcon, { className: "text-primary size-7" }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-sm font-medium", children: "Drop images to attach" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "They stay on disk and are read when sent" })] })] })), _jsx("div", { className: "pointer-events-none absolute inset-0 z-50 rounded-[inherit]", style: {
234
+ padding: '1px',
235
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.1) 20%, rgba(255,255,255,0.05) 45%, rgba(255,255,255,0.05) 55%, rgba(255,255,255,0.1) 80%, rgba(255,255,255,0.25) 100%)',
236
+ mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
237
+ WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
238
+ maskComposite: 'exclude',
239
+ WebkitMaskComposite: 'xor',
240
+ } }), !isExpanded && (_jsx(motion.div, { className: "rounded-4xl pointer-events-none absolute inset-0 z-0", animate: {
108
241
  boxShadow: [
109
242
  '0 0 0px 0px color-mix(in oklch, var(--primary) 0%, #ff8800 00%)',
110
243
  '0 0 20px 0px color-mix(in oklch, var(--primary) 20%, #ff8800 30%)',
@@ -118,17 +251,21 @@ export function ChatPanel({ messages, status, input, onInputChange, onSubmit, on
118
251
  } })), _jsxs(motion.div, { initial: false, animate: {
119
252
  height: isExpanded ? 'min(60vh, 480px)' : '0px',
120
253
  opacity: isExpanded ? 1 : 0,
121
- }, transition: { type: 'tween', duration: 0.2, ease: 'easeOut' }, className: "relative z-10 flex flex-col overflow-hidden", style: { pointerEvents: isExpanded ? 'auto' : 'none' }, children: [_jsxs("div", { className: "flex items-center justify-between border-b px-4 py-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelChange: onModelChange, disabled: isResponding }), reasoningEffortOptions &&
122
- selectedReasoningEffort &&
123
- onReasoningEffortChange && (_jsx(ReasoningEffortSelector, { options: reasoningEffortOptions, selectedEffort: selectedReasoningEffort, onEffortChange: onReasoningEffortChange, disabled: isResponding }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [conversations &&
124
- conversations.length > 0 &&
125
- onSelectConversation && (_jsx(ConversationHistory, { conversations: conversations, currentConversationId: currentConversationId, onSelect: onSelectConversation, onDelete: onDeleteConversation, disabled: isResponding })), _jsxs(TooltipProvider, { children: [messages.length > 0 && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleNewChat, disabled: isResponding, className: "text-muted-foreground hover:text-foreground", children: _jsx(Plus, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "New chat" }) })] })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleMinimize, className: "text-muted-foreground hover:text-foreground", children: _jsx(ChevronDown, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Minimize" }) })] })] })] })] }), _jsx(ScrollArea, { ref: scrollAreaRef, className: "min-w-0 flex-1 px-2", children: _jsxs("div", { className: "min-w-0 max-w-full space-y-4 overflow-hidden py-4", children: [messages.length === 0 && welcomeMessage && (_jsx("div", { className: "bg-muted/50 text-muted-foreground mx-2 rounded-2xl p-4 text-sm", children: welcomeMessage })), _jsx(ToolProgressProvider, { value: toolProgress, children: _jsx(ToolApprovalProvider, { onApprovalResponse: onApprovalResponse ?? null, children: _jsx(Messages, { messages: messages, status: status }) }) }), (missingApiKey ||
126
- (error &&
127
- error.message?.includes('API_KEY not configured'))) && (_jsxs("div", { className: "border-primary/30 bg-primary/5 mx-2 flex flex-col items-center gap-3 rounded-lg border p-4 text-center", children: [_jsx("div", { className: "bg-primary/10 flex size-10 items-center justify-center rounded-full", children: _jsx(AlertCircle, { className: "text-primary size-5" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-foreground font-medium", children: "API key required" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: "Add an API key to start chatting with AI" })] }), onAddApiKey && (_jsx(Button, { onClick: onAddApiKey, size: "sm", className: "cursor-pointer", children: "Add API key" }))] })), error &&
128
- status === 'error' &&
129
- !error.message?.includes('API_KEY not configured') && (_jsxs("div", { className: "border-destructive/30 bg-destructive/10 text-destructive mx-2 flex items-start gap-2 rounded-lg border p-3 text-sm", children: [_jsx(AlertCircle, { className: "mt-0.5 size-4 shrink-0" }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium", children: "Request failed" }), _jsx("p", { className: "text-destructive/80 mt-0.5 break-words", children: error.message || 'An unknown error occurred' })] })] })), _jsx("div", { ref: messagesEndRef })] }) })] }), _jsx("div", { className: "relative z-10", onClick: () => {
254
+ }, transition: { type: 'tween', duration: 0.2, ease: 'easeOut' }, className: "relative z-10 flex flex-col overflow-hidden", style: { pointerEvents: isExpanded ? 'auto' : 'none' }, children: [_jsx("div", { className: "absolute right-3 top-3 z-20", children: _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleMinimize, className: "text-muted-foreground hover:text-foreground cursor-pointer", children: _jsx(ChevronDown, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Minimize" }) })] }) }) }), _jsx(ScrollArea, { ref: scrollAreaRef, className: "min-h-0 min-w-0 flex-1 px-2", children: _jsx("div", { className: "min-w-0 max-w-full overflow-hidden py-4 pr-10", children: _jsx(ToolProgressProvider, { value: toolProgress, children: _jsx(ToolApprovalProvider, { onApprovalResponse: onApprovalResponse ?? null, children: viewportVersion > 0 ? (_jsx(Virtualizer, { ref: virtualizerRef, data: listItems, scrollRef: viewportRef, bufferSize: 300, children: (item) => renderVirtualItem(item) }, viewportVersion)) : (listItems.map((item) => (_jsx("div", { children: renderVirtualItem(item) }, item.key)))) }) }) }) })] }), _jsxs("div", { className: "relative z-10", onClick: () => {
130
255
  if (!isExpanded) {
131
256
  onExpandedChange(true);
132
257
  }
133
- }, children: _jsx(ChatInput, { input: input, onInputChange: onInputChange, onSubmit: handleSubmit, isResponding: isResponding, placeholder: placeholder, inputRef: inputRef, onStop: onStop, compact: !isExpanded }) })] }) })] }));
258
+ }, children: [queuedMessages.length > 0 && (_jsxs("div", { className: "border-border/70 bg-background/95 border-t px-4 py-2", children: [_jsxs("div", { className: "text-muted-foreground mb-2 flex items-center gap-2 text-xs font-medium", children: [_jsx(ListPlus, { className: "size-3.5" }), _jsx("span", { children: "Queued follow-ups" })] }), _jsx("div", { className: "space-y-1.5", children: queuedMessages.map((message) => (_jsxs("div", { className: "bg-muted/60 text-foreground flex min-h-9 items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs", children: [_jsxs("span", { className: "min-w-0 flex-1 truncate", children: [message.preview, message.attachmentCount > 0 &&
259
+ ` (${message.attachmentCount} image${message.attachmentCount === 1 ? '' : 's'})`] }), message.status === 'failed' &&
260
+ message.error &&
261
+ onRetryQueuedMessage && (_jsxs("button", { type: "button", onClick: (event) => {
262
+ event.stopPropagation();
263
+ onRetryQueuedMessage(message.id);
264
+ }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs transition-colors", "aria-label": "Retry queued follow-up", children: [_jsx(CornerDownRight, { className: "size-3.5" }), _jsx("span", { children: "Retry" })] })), isResponding && onSteerQueuedMessage && (_jsxs("button", { type: "button", onClick: (event) => {
265
+ event.stopPropagation();
266
+ onSteerQueuedMessage(message.id);
267
+ }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 text-xs transition-colors", "aria-label": "Steer active response with queued follow-up", children: [_jsx(CornerDownRight, { className: "size-3.5" }), _jsx("span", { children: "Steer" })] })), onRemoveQueuedMessage && (_jsx("button", { type: "button", onClick: (event) => {
268
+ event.stopPropagation();
269
+ onRemoveQueuedMessage(message.id);
270
+ }, className: "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors", "aria-label": "Remove queued follow-up", children: _jsx(X, { className: "size-3.5" }) }))] }, message.id))) })] })), _jsx(ChatInput, { input: input, onInputChange: onInputChange, onSubmit: handleSubmit, isResponding: isResponding, placeholder: placeholder, inputRef: inputRef, onStop: onStop, compact: !isExpanded, imageAttachments: imageAttachments, onRemoveImageAttachment: onRemoveImageAttachment, footerStartControls: isExpanded ? composerStartControls : null, footerControls: isExpanded ? composerControls : null })] })] }) })] }));
134
271
  }
@@ -0,0 +1,20 @@
1
+ export interface CheckpointBadgeProps {
2
+ /** Message ID this checkpoint belongs to */
3
+ messageId: string;
4
+ /** Number of files in the checkpoint */
5
+ fileCount: number;
6
+ /** Total bytes of the checkpoint */
7
+ totalBytes?: number;
8
+ /** Whether a restore is in progress */
9
+ isRestoring?: boolean;
10
+ /** Callback when restore is requested */
11
+ onRestore: () => void;
12
+ /** Additional class name */
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Badge shown on messages that have a checkpoint restore point.
17
+ * Clicking triggers the restore confirmation dialog.
18
+ */
19
+ export declare function CheckpointBadge({ isRestoring, onRestore, className, }: CheckpointBadgeProps): import("react/jsx-runtime").JSX.Element;
20
+ //# sourceMappingURL=checkpoint-badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-badge.d.ts","sourceRoot":"","sources":["../../../src/components/chat/checkpoint-badge.tsx"],"names":[],"mappings":"AAYA,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uCAAuC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yCAAyC;IACzC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,SAAS,EACT,SAAS,GACV,EAAE,oBAAoB,2CAiCtB"}
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { History, Loader2 } from 'lucide-react';
4
+ import { cn } from '../../lib/utils';
5
+ import { Button } from '../ui/button';
6
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
7
+ /**
8
+ * Badge shown on messages that have a checkpoint restore point.
9
+ * Clicking triggers the restore confirmation dialog.
10
+ */
11
+ export function CheckpointBadge({ isRestoring, onRestore, className, }) {
12
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: (e) => {
13
+ e.stopPropagation();
14
+ onRestore();
15
+ }, disabled: isRestoring, className: cn('text-muted-foreground hover:text-foreground hover:bg-muted/50 size-6', !isRestoring && 'cursor-pointer', isRestoring && 'cursor-wait', className), children: isRestoring ? (_jsx(Loader2, { className: "size-3.5 animate-spin" })) : (_jsx(History, { className: "size-3.5" })) }) }), _jsx(TooltipContent, { side: "top", children: _jsx("p", { children: "Undo all changes from this point onwards" }) })] }) }));
16
+ }
@@ -10,9 +10,14 @@ interface ConversationHistoryProps {
10
10
  currentConversationId?: string | null;
11
11
  onSelect: (id: string) => void;
12
12
  onDelete?: (id: string) => void;
13
+ onNewChat?: () => void;
14
+ newChatDisabled?: boolean;
13
15
  disabled?: boolean;
14
16
  className?: string;
17
+ dropdownAlign?: 'start' | 'center' | 'end';
18
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
19
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
15
20
  }
16
- export declare function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, disabled, className, }: ConversationHistoryProps): import("react/jsx-runtime").JSX.Element | null;
21
+ export declare function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, onNewChat, newChatDisabled, disabled, className, dropdownAlign, dropdownSide, tooltipSide, }: ConversationHistoryProps): import("react/jsx-runtime").JSX.Element | null;
17
22
  export {};
18
23
  //# sourceMappingURL=conversation-history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../../../src/components/chat/conversation-history.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAiBD,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,QAAgB,EAChB,SAAS,GACV,EAAE,wBAAwB,kDAsE1B"}
1
+ {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../../../src/components/chat/conversation-history.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,gBAAgB,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAClD,WAAW,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CAClD;AAiBD,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,eAAuB,EACvB,QAAgB,EAChB,SAAS,EACT,aAAuB,EACvB,YAAuB,EACvB,WAAsB,GACvB,EAAE,wBAAwB,kDA6F1B"}
@@ -1,8 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Clock, Trash2 } from 'lucide-react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Clock, Plus, Trash2 } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../ui/dropdown-menu';
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
6
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
7
7
  function formatRelativeTime(dateStr) {
8
8
  const date = new Date(dateStr);
@@ -21,11 +21,11 @@ function formatRelativeTime(dateStr) {
21
21
  return `${diffDays}d ago`;
22
22
  return date.toLocaleDateString();
23
23
  }
24
- export function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, disabled = false, className, }) {
25
- if (conversations.length === 0) {
24
+ export function ConversationHistory({ conversations, currentConversationId, onSelect, onDelete, onNewChat, newChatDisabled = false, disabled = false, className, dropdownAlign = 'start', dropdownSide = 'bottom', tooltipSide = 'bottom', }) {
25
+ if (conversations.length === 0 && !onNewChat) {
26
26
  return null;
27
27
  }
28
- return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground', className), children: _jsx(Clock, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsx("p", { children: "Conversation history" }) })] }), _jsxs(DropdownMenuContent, { align: "start", className: "w-64", children: [_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs font-medium", children: "Recent conversations" }), _jsx(DropdownMenuSeparator, {}), _jsx("div", { className: "max-h-64 overflow-y-auto", children: conversations.slice(0, 20).map((conv) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(conv.id), className: cn('group flex cursor-pointer items-start gap-2 py-2', conv.id === currentConversationId && 'bg-accent'), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm", children: conv.title }), _jsxs("p", { className: "text-muted-foreground text-xs", children: [formatRelativeTime(conv.updatedAt), " \u00B7 ", conv.messageCount, ' ', "messages"] })] }), onDelete && (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: (e) => {
28
+ return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground cursor-pointer', className), children: _jsx(Clock, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: tooltipSide, children: _jsx("p", { children: "Conversation history" }) })] }), _jsxs(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "w-64", children: [onNewChat && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onClick: onNewChat, disabled: newChatDisabled, title: "New conversation (Cmd+Shift+O)", className: "flex cursor-pointer items-center gap-2", children: [_jsx(Plus, { className: "size-4" }), _jsx("span", { children: "New conversation" }), _jsx(DropdownMenuShortcut, { children: "\u2318\u21E7O" })] }), conversations.length > 0 && _jsx(DropdownMenuSeparator, {})] })), conversations.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "text-muted-foreground px-2 py-1.5 text-xs font-medium", children: "Recent conversations" }), _jsx(DropdownMenuSeparator, {})] })), _jsx("div", { className: "max-h-64 overflow-y-auto", children: conversations.slice(0, 20).map((conv) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(conv.id), className: cn('group flex cursor-pointer items-start gap-2 py-2', conv.id === currentConversationId && 'bg-accent'), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm", children: conv.title }), _jsxs("p", { className: "text-muted-foreground text-xs", children: [formatRelativeTime(conv.updatedAt), " \u00B7 ", conv.messageCount, ' ', "messages"] })] }), onDelete && (_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: (e) => {
29
29
  e.stopPropagation();
30
30
  onDelete(conv.id);
31
31
  }, className: "text-muted-foreground hover:text-destructive size-6 shrink-0 opacity-0 group-hover:opacity-100", children: _jsx(Trash2, { className: "size-3" }) }))] }, conv.id))) })] })] }) }));
@@ -1,10 +1,12 @@
1
- export { ChatInput } from './chat-input';
1
+ export { ChatInput, type ChatImageAttachment, type ChatSubmitMode, } from './chat-input';
2
2
  export { Message, ThinkingMessage, type ChatMessage, type ChatMessagePart, } from './chat-message';
3
- export { Messages } from './chat-messages';
3
+ export { Messages, type MessageCheckpoint } from './chat-messages';
4
+ export { CheckpointBadge, type CheckpointBadgeProps } from './checkpoint-badge';
5
+ export { RestoreDialog, type CheckpointInfo, type RestoreDialogProps, } from './restore-dialog';
4
6
  export { ModelSelector, type ModelOption } from './model-selector';
5
7
  export { ReasoningEffortSelector, type ReasoningEffortOption, } from './reasoning-effort-selector';
6
8
  export { ConversationHistory, type ConversationMeta, } from './conversation-history';
7
- export { ChatPanel, type ChatPanelProps, type ToolProgressData, } from './chat-panel';
9
+ export { ChatPanel, type ChatPanelProps, type QueuedChatMessage, type ToolProgressData, } from './chat-panel';
8
10
  export { ThinkingTimeline, ThinkingTimelineMarker, type ThinkingTimelineItem, type ThinkingTimelineProps, } from './thinking-timeline';
9
11
  export { getToolHandler, DEFAULT_TOOL_HANDLERS, type ToolHandler, } from './tool-handlers';
10
12
  export { ToolProgressProvider, useToolProgress, useToolCallProgress, } from './tool-progress-context';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/chat/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EACL,OAAO,EACP,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EACL,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,GACpB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/chat/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,OAAO,EACP,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACL,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EACL,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,GACpB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAA"}
@@ -1,6 +1,8 @@
1
- export { ChatInput } from './chat-input';
1
+ export { ChatInput, } from './chat-input';
2
2
  export { Message, ThinkingMessage, } from './chat-message';
3
3
  export { Messages } from './chat-messages';
4
+ export { CheckpointBadge } from './checkpoint-badge';
5
+ export { RestoreDialog, } from './restore-dialog';
4
6
  export { ModelSelector } from './model-selector';
5
7
  export { ReasoningEffortSelector, } from './reasoning-effort-selector';
6
8
  export { ConversationHistory, } from './conversation-history';
@@ -0,0 +1,9 @@
1
+ type MarkdownProps = {
2
+ markdown: string;
3
+ className?: string;
4
+ proseSize?: 'sm' | 'base' | 'lg';
5
+ };
6
+ declare function NonMemoizedMarkdown({ markdown, className, proseSize }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare const Markdown: import("react").MemoExoticComponent<typeof NonMemoizedMarkdown>;
8
+ export {};
9
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/components/chat/markdown.tsx"],"names":[],"mappings":"AAMA,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;CACjC,CAAA;AAwFD,iBAAS,mBAAmB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAgB,EAAE,EAAE,aAAa,2CAyBpF;AAED,eAAO,MAAM,QAAQ,iEAMpB,CAAA"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import ReactMarkdown from 'react-markdown';
4
+ import { cn } from '../../lib/utils';
5
+ import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
7
+ const components = {
8
+ img({ src, alt }) {
9
+ if (!src)
10
+ return null;
11
+ return (_jsx("span", { className: "flex justify-center", children: _jsx("img", { src: src, alt: alt ?? '', className: "h-auto max-w-full rounded-lg" }) }));
12
+ },
13
+ table({ children }) {
14
+ return (_jsx("table", { className: "border-collapse border px-3 py-1", children: children }));
15
+ },
16
+ th({ children }) {
17
+ return (_jsx("th", { className: "bg-muted border px-3 py-1 break-words", children: children }));
18
+ },
19
+ td({ children }) {
20
+ return _jsx("td", { className: "border px-3 py-1 break-words", children: children });
21
+ },
22
+ ol: ({ children, ...props }) => (_jsx("ol", { className: "ml-4 list-outside list-decimal", ...props, children: children })),
23
+ ul: ({ children, ...props }) => (_jsx("ul", { className: "ml-4 list-outside list-disc", ...props, children: children })),
24
+ li: ({ children, ...props }) => (_jsx("li", { className: "py-1", ...props, children: children })),
25
+ strong: ({ children, ...props }) => (_jsx("span", { className: "font-semibold", ...props, children: children })),
26
+ a: ({ children, href, ...props }) => (_jsx("a", { className: "font-bold hover:underline", href: href ?? '#', target: "_blank", rel: "noreferrer", ...props, children: children })),
27
+ pre: ({ children, ...props }) => (_jsx("pre", { className: "overflow-x-auto rounded-lg bg-muted/50 p-3 text-sm", ...props, children: children })),
28
+ code: ({ children, className, ...props }) => {
29
+ // Check if this is an inline code block (no className means inline)
30
+ const isInline = !className;
31
+ if (isInline) {
32
+ return (_jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 text-sm font-mono", ...props, children: children }));
33
+ }
34
+ return (_jsx("code", { className: cn('font-mono text-sm', className), ...props, children: children }));
35
+ },
36
+ };
37
+ function NonMemoizedMarkdown({ markdown, className, proseSize = 'sm' }) {
38
+ const proseSizeClass = {
39
+ sm: 'prose-sm',
40
+ base: 'prose-base',
41
+ lg: 'prose-lg',
42
+ }[proseSize];
43
+ return (_jsx("div", { className: cn('prose w-full max-w-none dark:prose-invert', proseSizeClass, 'text-foreground', className), children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeRaw], components: components, children: markdown }) }));
44
+ }
45
+ export const Markdown = memo(NonMemoizedMarkdown, (prevProps, nextProps) => prevProps.markdown === nextProps.markdown &&
46
+ prevProps.className === nextProps.className &&
47
+ prevProps.proseSize === nextProps.proseSize);
@@ -10,7 +10,9 @@ type ModelSelectorProps = {
10
10
  onModelChange: (modelId: string) => void;
11
11
  disabled?: boolean;
12
12
  className?: string;
13
+ dropdownAlign?: 'start' | 'center' | 'end';
14
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
13
15
  };
14
- export declare function ModelSelector({ models, selectedModel, onModelChange, disabled, className, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function ModelSelector({ models, selectedModel, onModelChange, disabled, className, dropdownAlign, dropdownSide, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
15
17
  export {};
16
18
  //# sourceMappingURL=model-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/model-selector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAUtC,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,aAAa,EACb,aAAa,EACb,QAAgB,EAChB,SAAS,GACV,EAAE,kBAAkB,2CAqCpB"}
1
+ {"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/model-selector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAUtC,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CACnD,CAAA;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,aAAa,EACb,aAAa,EACb,QAAgB,EAChB,SAAS,EACT,aAAuB,EACvB,YAAuB,GACxB,EAAE,kBAAkB,2CAyCpB"}
@@ -3,7 +3,7 @@ import { Check, ChevronDown } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
- export function ModelSelector({ models, selectedModel, onModelChange, disabled = false, className, }) {
6
+ export function ModelSelector({ models, selectedModel, onModelChange, disabled = false, className, dropdownAlign = 'start', dropdownSide = 'bottom', }) {
7
7
  const currentModel = models.find((m) => m.id === selectedModel) ?? models[0];
8
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground h-7 gap-1.5 rounded-full px-2.5 text-xs font-medium', className), children: [_jsx("span", { children: currentModel?.icon }), _jsx("span", { children: currentModel?.name }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] }) }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[160px]", children: models.map((model) => (_jsxs(DropdownMenuItem, { onClick: () => onModelChange(model.id), className: "flex items-center gap-2", children: [_jsx("span", { children: model.icon }), _jsx("span", { className: "flex-1", children: model.name }), model.id === selectedModel && (_jsx(Check, { className: "text-primary size-4" }))] }, model.id))) })] }));
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground h-8 min-w-0 max-w-[180px] cursor-pointer gap-1.5 rounded-full px-2.5 text-xs font-medium', className), children: [_jsx("span", { children: currentModel?.icon }), _jsx("span", { className: "min-w-0 truncate", children: currentModel?.name }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] }) }), _jsx(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "min-w-[160px]", children: models.map((model) => (_jsxs(DropdownMenuItem, { onClick: () => onModelChange(model.id), className: "flex cursor-pointer items-center gap-2", children: [_jsx("span", { children: model.icon }), _jsx("span", { className: "flex-1", children: model.name }), model.id === selectedModel && (_jsx(Check, { className: "text-primary size-4" }))] }, model.id))) })] }));
9
9
  }
@@ -8,7 +8,10 @@ type ReasoningEffortSelectorProps = {
8
8
  onEffortChange: (effort: string) => void;
9
9
  disabled?: boolean;
10
10
  className?: string;
11
+ showLabel?: boolean;
12
+ dropdownAlign?: 'start' | 'center' | 'end';
13
+ dropdownSide?: 'top' | 'right' | 'bottom' | 'left';
11
14
  };
12
- export declare function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled, className, }: ReasoningEffortSelectorProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled, className, showLabel, dropdownAlign, dropdownSide, }: ReasoningEffortSelectorProps): import("react/jsx-runtime").JSX.Element;
13
16
  export {};
14
17
  //# sourceMappingURL=reasoning-effort-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-effort-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/reasoning-effort-selector.tsx"],"names":[],"mappings":"AAgBA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,cAAc,EACd,cAAc,EACd,QAAgB,EAChB,SAAS,GACV,EAAE,4BAA4B,2CA6C9B"}
1
+ {"version":3,"file":"reasoning-effort-selector.d.ts","sourceRoot":"","sources":["../../../src/components/chat/reasoning-effort-selector.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAA;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAC1C,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CACnD,CAAA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,cAAc,EACd,cAAc,EACd,QAAgB,EAChB,SAAS,EACT,SAAiB,EACjB,aAAuB,EACvB,YAAuB,GACxB,EAAE,4BAA4B,2CAoD9B"}
@@ -1,10 +1,10 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Brain, Check } from 'lucide-react';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Brain, Check, ChevronDown } from 'lucide-react';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { Button } from '../ui/button';
5
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip';
7
- export function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled = false, className, }) {
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from '../ui/dropdown-menu';
6
+ export function ReasoningEffortSelector({ options, selectedEffort, onEffortChange, disabled = false, className, showLabel = false, dropdownAlign = 'start', dropdownSide = 'bottom', }) {
8
7
  const currentOption = options.find((o) => o.value === selectedEffort) ?? options[1]; // Default to medium
9
- return (_jsx(TooltipProvider, { children: _jsxs(DropdownMenu, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground', className), children: _jsx(Brain, { className: "size-4" }) }) }) }), _jsx(TooltipContent, { side: "bottom", children: _jsxs("p", { children: ["Reasoning: ", currentOption?.label] }) })] }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[140px]", children: options.map((option) => (_jsxs(DropdownMenuItem, { onClick: () => onEffortChange(option.value), className: "flex items-center gap-2", children: [_jsx(Brain, { className: "text-muted-foreground size-3.5" }), _jsx("span", { className: "flex-1", children: option.label }), option.value === selectedEffort && (_jsx(Check, { className: "text-primary size-4" }))] }, option.value))) })] }) }));
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", disabled: disabled, className: cn('text-muted-foreground hover:text-foreground cursor-pointer', showLabel &&
9
+ 'h-8 w-auto gap-1.5 rounded-full px-3 text-xs font-medium', className), children: showLabel ? (_jsxs(_Fragment, { children: [_jsx("span", { children: currentOption?.label }), _jsx(ChevronDown, { className: "size-3 opacity-50" })] })) : (_jsx(Brain, { className: "size-4" })) }) }), _jsxs(DropdownMenuContent, { align: dropdownAlign, side: dropdownSide, className: "min-w-[140px]", children: [_jsx(DropdownMenuLabel, { className: "text-muted-foreground font-normal", children: "Reasoning" }), options.map((option) => (_jsxs(DropdownMenuItem, { onClick: () => onEffortChange(option.value), className: "flex cursor-pointer items-center gap-2", children: [!showLabel && _jsx(Brain, { className: "text-muted-foreground size-3.5" }), _jsx("span", { className: "flex-1", children: option.label }), option.value === selectedEffort && (_jsx(Check, { className: "text-primary size-4" }))] }, option.value)))] })] }));
10
10
  }