@gugacoder/agentic-chat 0.2.0

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 (187) hide show
  1. package/dist/components/Chat.d.ts +21 -0
  2. package/dist/components/Chat.js +13 -0
  3. package/dist/components/ErrorNote.d.ts +5 -0
  4. package/dist/components/ErrorNote.js +6 -0
  5. package/dist/components/LazyRender.d.ts +8 -0
  6. package/dist/components/LazyRender.js +22 -0
  7. package/dist/components/Markdown.d.ts +5 -0
  8. package/dist/components/Markdown.js +65 -0
  9. package/dist/components/MessageBubble.d.ts +10 -0
  10. package/dist/components/MessageBubble.js +39 -0
  11. package/dist/components/MessageInput.d.ts +19 -0
  12. package/dist/components/MessageInput.js +214 -0
  13. package/dist/components/MessageList.d.ts +12 -0
  14. package/dist/components/MessageList.js +68 -0
  15. package/dist/components/StreamingIndicator.d.ts +1 -0
  16. package/dist/components/StreamingIndicator.js +9 -0
  17. package/dist/conversations/CollapsibleGroup.d.ts +11 -0
  18. package/dist/conversations/CollapsibleGroup.js +9 -0
  19. package/dist/conversations/ConversationBar.d.ts +27 -0
  20. package/dist/conversations/ConversationBar.js +53 -0
  21. package/dist/conversations/ConversationList.d.ts +33 -0
  22. package/dist/conversations/ConversationList.js +48 -0
  23. package/dist/conversations/ConversationListItem.d.ts +20 -0
  24. package/dist/conversations/ConversationListItem.js +22 -0
  25. package/dist/conversations/DeleteDialog.d.ts +13 -0
  26. package/dist/conversations/DeleteDialog.js +8 -0
  27. package/dist/conversations/RenameDialog.d.ts +15 -0
  28. package/dist/conversations/RenameDialog.js +15 -0
  29. package/dist/conversations/index.d.ts +9 -0
  30. package/dist/conversations/index.js +5 -0
  31. package/dist/conversations/types.d.ts +21 -0
  32. package/dist/conversations/types.js +1 -0
  33. package/dist/conversations/useConversations.d.ts +19 -0
  34. package/dist/conversations/useConversations.js +102 -0
  35. package/dist/conversations/utils.d.ts +8 -0
  36. package/dist/conversations/utils.js +134 -0
  37. package/dist/display/AlertRenderer.d.ts +2 -0
  38. package/dist/display/AlertRenderer.js +13 -0
  39. package/dist/display/CarouselRenderer.d.ts +2 -0
  40. package/dist/display/CarouselRenderer.js +41 -0
  41. package/dist/display/ChartRenderer.d.ts +2 -0
  42. package/dist/display/ChartRenderer.js +76 -0
  43. package/dist/display/ChoiceButtonsRenderer.d.ts +6 -0
  44. package/dist/display/ChoiceButtonsRenderer.js +23 -0
  45. package/dist/display/CodeBlockRenderer.d.ts +2 -0
  46. package/dist/display/CodeBlockRenderer.js +17 -0
  47. package/dist/display/ComparisonTableRenderer.d.ts +2 -0
  48. package/dist/display/ComparisonTableRenderer.js +26 -0
  49. package/dist/display/DataTableRenderer.d.ts +2 -0
  50. package/dist/display/DataTableRenderer.js +74 -0
  51. package/dist/display/FileCardRenderer.d.ts +2 -0
  52. package/dist/display/FileCardRenderer.js +31 -0
  53. package/dist/display/GalleryRenderer.d.ts +2 -0
  54. package/dist/display/GalleryRenderer.js +11 -0
  55. package/dist/display/ImageViewerRenderer.d.ts +2 -0
  56. package/dist/display/ImageViewerRenderer.js +15 -0
  57. package/dist/display/LinkPreviewRenderer.d.ts +2 -0
  58. package/dist/display/LinkPreviewRenderer.js +20 -0
  59. package/dist/display/MapViewRenderer.d.ts +2 -0
  60. package/dist/display/MapViewRenderer.js +20 -0
  61. package/dist/display/MetricCardRenderer.d.ts +2 -0
  62. package/dist/display/MetricCardRenderer.js +12 -0
  63. package/dist/display/PriceHighlightRenderer.d.ts +2 -0
  64. package/dist/display/PriceHighlightRenderer.js +13 -0
  65. package/dist/display/ProductCardRenderer.d.ts +2 -0
  66. package/dist/display/ProductCardRenderer.js +23 -0
  67. package/dist/display/ProgressStepsRenderer.d.ts +2 -0
  68. package/dist/display/ProgressStepsRenderer.js +14 -0
  69. package/dist/display/SourcesListRenderer.d.ts +2 -0
  70. package/dist/display/SourcesListRenderer.js +5 -0
  71. package/dist/display/SpreadsheetRenderer.d.ts +2 -0
  72. package/dist/display/SpreadsheetRenderer.js +32 -0
  73. package/dist/display/StepTimelineRenderer.d.ts +2 -0
  74. package/dist/display/StepTimelineRenderer.js +21 -0
  75. package/dist/display/index.d.ts +21 -0
  76. package/dist/display/index.js +20 -0
  77. package/dist/display/registry.d.ts +5 -0
  78. package/dist/display/registry.js +50 -0
  79. package/dist/hooks/ChatProvider.d.ts +10 -0
  80. package/dist/hooks/ChatProvider.js +14 -0
  81. package/dist/hooks/useBackboneChat.d.ts +37 -0
  82. package/dist/hooks/useBackboneChat.js +121 -0
  83. package/dist/hooks/useIsMobile.d.ts +1 -0
  84. package/dist/hooks/useIsMobile.js +12 -0
  85. package/dist/index.d.ts +47 -0
  86. package/dist/index.js +40 -0
  87. package/dist/lib/utils.d.ts +2 -0
  88. package/dist/lib/utils.js +5 -0
  89. package/dist/parts/PartRenderer.d.ts +40 -0
  90. package/dist/parts/PartRenderer.js +97 -0
  91. package/dist/parts/ReasoningBlock.d.ts +6 -0
  92. package/dist/parts/ReasoningBlock.js +18 -0
  93. package/dist/parts/ToolActivity.d.ts +11 -0
  94. package/dist/parts/ToolActivity.js +52 -0
  95. package/dist/parts/ToolResult.d.ts +7 -0
  96. package/dist/parts/ToolResult.js +38 -0
  97. package/dist/styles.css +2 -0
  98. package/dist/ui/alert.d.ts +12 -0
  99. package/dist/ui/alert.js +28 -0
  100. package/dist/ui/badge.d.ts +9 -0
  101. package/dist/ui/badge.js +20 -0
  102. package/dist/ui/button.d.ts +11 -0
  103. package/dist/ui/button.js +31 -0
  104. package/dist/ui/card.d.ts +8 -0
  105. package/dist/ui/card.js +21 -0
  106. package/dist/ui/collapsible.d.ts +1 -0
  107. package/dist/ui/collapsible.js +2 -0
  108. package/dist/ui/dialog.d.ts +19 -0
  109. package/dist/ui/dialog.js +23 -0
  110. package/dist/ui/dropdown-menu.d.ts +11 -0
  111. package/dist/ui/dropdown-menu.js +15 -0
  112. package/dist/ui/input.d.ts +3 -0
  113. package/dist/ui/input.js +6 -0
  114. package/dist/ui/progress.d.ts +7 -0
  115. package/dist/ui/progress.js +9 -0
  116. package/dist/ui/scroll-area.d.ts +5 -0
  117. package/dist/ui/scroll-area.js +12 -0
  118. package/dist/ui/separator.d.ts +4 -0
  119. package/dist/ui/separator.js +8 -0
  120. package/dist/ui/skeleton.d.ts +3 -0
  121. package/dist/ui/skeleton.js +6 -0
  122. package/dist/ui/table.d.ts +10 -0
  123. package/dist/ui/table.js +27 -0
  124. package/package.json +53 -0
  125. package/src/components/Chat.tsx +80 -0
  126. package/src/components/ErrorNote.tsx +32 -0
  127. package/src/components/LazyRender.tsx +42 -0
  128. package/src/components/Markdown.tsx +114 -0
  129. package/src/components/MessageBubble.tsx +102 -0
  130. package/src/components/MessageInput.tsx +421 -0
  131. package/src/components/MessageList.tsx +139 -0
  132. package/src/components/StreamingIndicator.tsx +19 -0
  133. package/src/conversations/CollapsibleGroup.tsx +41 -0
  134. package/src/conversations/ConversationBar.tsx +200 -0
  135. package/src/conversations/ConversationList.tsx +234 -0
  136. package/src/conversations/ConversationListItem.tsx +123 -0
  137. package/src/conversations/DeleteDialog.tsx +55 -0
  138. package/src/conversations/RenameDialog.tsx +74 -0
  139. package/src/conversations/index.ts +14 -0
  140. package/src/conversations/types.ts +17 -0
  141. package/src/conversations/useConversations.ts +148 -0
  142. package/src/conversations/utils.ts +159 -0
  143. package/src/display/AlertRenderer.tsx +27 -0
  144. package/src/display/CarouselRenderer.tsx +141 -0
  145. package/src/display/ChartRenderer.tsx +195 -0
  146. package/src/display/ChoiceButtonsRenderer.tsx +114 -0
  147. package/src/display/CodeBlockRenderer.tsx +49 -0
  148. package/src/display/ComparisonTableRenderer.tsx +132 -0
  149. package/src/display/DataTableRenderer.tsx +144 -0
  150. package/src/display/FileCardRenderer.tsx +55 -0
  151. package/src/display/GalleryRenderer.tsx +65 -0
  152. package/src/display/ImageViewerRenderer.tsx +114 -0
  153. package/src/display/LinkPreviewRenderer.tsx +74 -0
  154. package/src/display/MapViewRenderer.tsx +75 -0
  155. package/src/display/MetricCardRenderer.tsx +29 -0
  156. package/src/display/PriceHighlightRenderer.tsx +44 -0
  157. package/src/display/ProductCardRenderer.tsx +112 -0
  158. package/src/display/ProgressStepsRenderer.tsx +59 -0
  159. package/src/display/SourcesListRenderer.tsx +47 -0
  160. package/src/display/SpreadsheetRenderer.tsx +86 -0
  161. package/src/display/StepTimelineRenderer.tsx +75 -0
  162. package/src/display/index.ts +21 -0
  163. package/src/display/registry.ts +81 -0
  164. package/src/hooks/ChatProvider.tsx +22 -0
  165. package/src/hooks/useBackboneChat.ts +148 -0
  166. package/src/hooks/useIsMobile.ts +15 -0
  167. package/src/index.ts +80 -0
  168. package/src/lib/utils.ts +6 -0
  169. package/src/parts/PartRenderer.tsx +198 -0
  170. package/src/parts/ReasoningBlock.tsx +41 -0
  171. package/src/parts/ToolActivity.tsx +79 -0
  172. package/src/parts/ToolResult.tsx +79 -0
  173. package/src/styles.css +2 -0
  174. package/src/ui/alert.tsx +77 -0
  175. package/src/ui/badge.tsx +36 -0
  176. package/src/ui/button.tsx +54 -0
  177. package/src/ui/card.tsx +68 -0
  178. package/src/ui/collapsible.tsx +7 -0
  179. package/src/ui/dialog.tsx +122 -0
  180. package/src/ui/dropdown-menu.tsx +76 -0
  181. package/src/ui/input.tsx +24 -0
  182. package/src/ui/progress.tsx +36 -0
  183. package/src/ui/scroll-area.tsx +48 -0
  184. package/src/ui/separator.tsx +31 -0
  185. package/src/ui/skeleton.tsx +9 -0
  186. package/src/ui/table.tsx +114 -0
  187. package/tsconfig.json +17 -0
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import type { DisplayRendererMap } from "../display/registry.js";
3
+ export interface ChatProps {
4
+ endpoint: string;
5
+ token: string;
6
+ sessionId: string;
7
+ initialMessages?: Array<{
8
+ id?: string;
9
+ role: "user" | "assistant";
10
+ content: string;
11
+ parts?: unknown[];
12
+ }>;
13
+ displayRenderers?: DisplayRendererMap;
14
+ placeholder?: string;
15
+ header?: React.ReactNode;
16
+ footer?: React.ReactNode;
17
+ className?: string;
18
+ enableAttachments?: boolean;
19
+ enableVoice?: boolean;
20
+ }
21
+ export declare function Chat({ endpoint, token, sessionId, initialMessages, displayRenderers, placeholder, header, footer, className, enableAttachments, enableVoice, }: ChatProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { ChatProvider } from "../hooks/ChatProvider.js";
4
+ import { useChatContext } from "../hooks/ChatProvider.js";
5
+ import { MessageList } from "./MessageList.js";
6
+ import { MessageInput } from "./MessageInput.js";
7
+ function ChatContent({ displayRenderers, placeholder, enableAttachments = true, enableVoice = true }) {
8
+ const { messages, input, setInput, handleSubmit, isLoading, isUploading, stop, error, reload, buildAttachmentUrl } = useChatContext();
9
+ return (_jsxs(_Fragment, { children: [_jsx(MessageList, { messages: messages, isLoading: isLoading, displayRenderers: displayRenderers, attachmentUrl: buildAttachmentUrl, error: error ?? undefined, onRetry: reload }), _jsx("div", { className: "px-4 pb-4", children: _jsx(MessageInput, { input: input, setInput: setInput, handleSubmit: handleSubmit, isLoading: isLoading, isUploading: isUploading, stop: stop, placeholder: placeholder, enableAttachments: enableAttachments, enableVoice: enableVoice }) })] }));
10
+ }
11
+ export function Chat({ endpoint, token, sessionId, initialMessages, displayRenderers, placeholder, header, footer, className, enableAttachments, enableVoice, }) {
12
+ return (_jsx(ChatProvider, { endpoint: endpoint, token: token, sessionId: sessionId, initialMessages: initialMessages, children: _jsxs("div", { className: cn("flex flex-col h-full bg-background text-foreground", className), children: [header, _jsx(ChatContent, { displayRenderers: displayRenderers, placeholder: placeholder, enableAttachments: enableAttachments, enableVoice: enableVoice }), footer] }) }, sessionId));
13
+ }
@@ -0,0 +1,5 @@
1
+ export interface ErrorNoteProps {
2
+ onRetry?: () => void;
3
+ className?: string;
4
+ }
5
+ export declare function ErrorNote({ onRetry, className }: ErrorNoteProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangle, RotateCcw } from "lucide-react";
3
+ import { cn } from "../lib/utils.js";
4
+ export function ErrorNote({ onRetry, className }) {
5
+ return (_jsxs("div", { role: "alert", className: cn("flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", className), children: [_jsx(AlertTriangle, { className: "size-4 shrink-0" }), _jsx("span", { className: "flex-1", children: "Falha ao processar mensagem" }), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 transition-colors", children: [_jsx(RotateCcw, { className: "size-3.5" }), "Tentar novamente"] }))] }));
6
+ }
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ interface LazyRenderProps {
3
+ children: ReactNode;
4
+ minHeight?: number;
5
+ rootMargin?: string;
6
+ }
7
+ export declare function LazyRender({ children, minHeight, rootMargin }: LazyRenderProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,22 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef, useState, useEffect } from "react";
3
+ export function LazyRender({ children, minHeight = 120, rootMargin = "200px" }) {
4
+ const ref = useRef(null);
5
+ const [visible, setVisible] = useState(false);
6
+ useEffect(() => {
7
+ const el = ref.current;
8
+ if (!el)
9
+ return;
10
+ const observer = new IntersectionObserver(([entry]) => {
11
+ if (entry.isIntersecting) {
12
+ setVisible(true);
13
+ observer.disconnect();
14
+ }
15
+ }, { rootMargin });
16
+ observer.observe(el);
17
+ return () => observer.disconnect();
18
+ }, [rootMargin]);
19
+ if (visible)
20
+ return _jsx(_Fragment, { children: children });
21
+ return (_jsx("div", { ref: ref, className: "flex items-center justify-center text-muted-foreground text-xs rounded-md bg-muted/20 animate-pulse", style: { minHeight }, children: "Carregando..." }));
22
+ }
@@ -0,0 +1,5 @@
1
+ interface MarkdownProps {
2
+ children: string;
3
+ }
4
+ export declare const Markdown: import("react").NamedExoticComponent<MarkdownProps>;
5
+ export {};
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import rehypeHighlight from "rehype-highlight";
6
+ import { cn } from "../lib/utils.js";
7
+ const REMARK_PLUGINS = [remarkGfm];
8
+ const REHYPE_PLUGINS = [rehypeHighlight];
9
+ const components = {
10
+ h1({ children }) {
11
+ return _jsx("h1", { className: "text-xl font-semibold", style: { marginTop: "20px", marginBottom: "8px" }, children: children });
12
+ },
13
+ h2({ children }) {
14
+ return _jsx("h2", { className: "text-lg font-semibold", style: { marginTop: "20px", marginBottom: "8px" }, children: children });
15
+ },
16
+ h3({ children }) {
17
+ return _jsx("h3", { className: "text-base font-semibold", style: { marginTop: "20px", marginBottom: "8px" }, children: children });
18
+ },
19
+ h4({ children }) {
20
+ return _jsx("h4", { className: "font-semibold", style: { marginTop: "20px", marginBottom: "8px" }, children: children });
21
+ },
22
+ p({ children }) {
23
+ return _jsx("p", { className: "mb-4 last:mb-0", children: children });
24
+ },
25
+ ul({ children }) {
26
+ return _jsx("ul", { style: { paddingLeft: "24px", marginTop: "8px", marginBottom: "8px", listStyleType: "disc" }, children: children });
27
+ },
28
+ ol({ children }) {
29
+ return _jsx("ol", { style: { paddingLeft: "24px", marginTop: "8px", marginBottom: "8px", listStyleType: "decimal" }, children: children });
30
+ },
31
+ li({ children }) {
32
+ return _jsx("li", { style: { marginTop: "4px", marginBottom: "4px" }, children: children });
33
+ },
34
+ hr() {
35
+ return _jsx("hr", { className: "border-border", style: { marginTop: "16px", marginBottom: "16px" } });
36
+ },
37
+ pre({ children }) {
38
+ return (_jsx("pre", { className: "bg-muted border border-border rounded-md overflow-hidden", style: { marginTop: "12px", marginBottom: "12px" }, children: children }));
39
+ },
40
+ code({ className, children }) {
41
+ const isBlock = className?.startsWith("language-");
42
+ if (isBlock) {
43
+ return (_jsx("code", { className: cn("block p-4 overflow-x-auto font-mono text-sm", className), children: children }));
44
+ }
45
+ return (_jsx("code", { className: "bg-muted border border-border rounded-sm px-1.5 py-0.5 font-mono text-sm", children: children }));
46
+ },
47
+ a({ href, children }) {
48
+ return (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: "text-primary underline underline-offset-2 hover:opacity-80", children: children }));
49
+ },
50
+ blockquote({ children }) {
51
+ return (_jsx("blockquote", { className: "border-l-[3px] border-border py-1 px-3 text-muted-foreground", style: { marginTop: "12px", marginBottom: "12px" }, children: children }));
52
+ },
53
+ table({ children }) {
54
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsx("table", { className: "w-full border-collapse text-sm", children: children }) }));
55
+ },
56
+ th({ children }) {
57
+ return (_jsx("th", { className: "border border-border px-3 py-1.5 text-left font-semibold bg-muted", children: children }));
58
+ },
59
+ td({ children }) {
60
+ return (_jsx("td", { className: "border border-border px-3 py-1.5 text-left", children: children }));
61
+ },
62
+ };
63
+ export const Markdown = memo(function Markdown({ children }) {
64
+ return (_jsx("div", { className: "text-foreground text-sm", style: { lineHeight: "1.625em" }, children: _jsx(ReactMarkdown, { remarkPlugins: REMARK_PLUGINS, rehypePlugins: REHYPE_PLUGINS, components: components, children: children }) }));
65
+ });
@@ -0,0 +1,10 @@
1
+ import type { Message } from "@ai-sdk/react";
2
+ import type { DisplayRendererMap } from "../display/registry.js";
3
+ export interface MessageBubbleProps {
4
+ message: Message;
5
+ isStreaming?: boolean;
6
+ displayRenderers?: DisplayRendererMap;
7
+ attachmentUrl?: (ref: string) => string;
8
+ className?: string;
9
+ }
10
+ export declare const MessageBubble: import("react").NamedExoticComponent<MessageBubbleProps>;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useState, useCallback } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Markdown } from "./Markdown.js";
5
+ import { StreamingIndicator } from "./StreamingIndicator.js";
6
+ import { PartRenderer } from "../parts/PartRenderer.js";
7
+ import { Copy, Check } from "lucide-react";
8
+ function extractText(message) {
9
+ if (message.content)
10
+ return message.content;
11
+ if (!Array.isArray(message.parts))
12
+ return "";
13
+ return message.parts
14
+ .filter((p) => p.type === "text")
15
+ .map((p) => p.text)
16
+ .join("\n");
17
+ }
18
+ function CopyButton({ text }) {
19
+ const [copied, setCopied] = useState(false);
20
+ const handleCopy = useCallback(async () => {
21
+ await navigator.clipboard.writeText(text);
22
+ setCopied(true);
23
+ setTimeout(() => setCopied(false), 2000);
24
+ }, [text]);
25
+ return (_jsx("button", { type: "button", onClick: handleCopy, className: cn("h-7 w-7 flex items-center justify-center rounded-lg transition-all", "text-muted-foreground/50 opacity-0 group-hover/bubble:opacity-100", "hover:bg-muted/50 hover:text-muted-foreground"), "aria-label": copied ? "Copiado" : "Copiar mensagem", children: copied ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(Copy, { className: "h-3.5 w-3.5" }) }));
26
+ }
27
+ export const MessageBubble = memo(function MessageBubble({ message, isStreaming, displayRenderers, attachmentUrl, className }) {
28
+ const isUser = message.role === "user";
29
+ const hasParts = Array.isArray(message.parts) && message.parts.length > 0;
30
+ return (_jsxs("div", { className: cn("group/bubble", isUser ? "flex flex-col items-end" : "flex flex-col items-start"), children: [_jsxs("div", { className: cn("min-w-0 overflow-hidden", isUser
31
+ ? "max-w-[80%] rounded-lg rounded-br-sm bg-muted text-foreground px-4 py-2.5"
32
+ : "w-full text-foreground py-1", className), children: [hasParts
33
+ ? _jsx("div", { className: "flex flex-col gap-3", children: message.parts.map((part, i) => (_jsx(PartRenderer, { part: part, isStreaming: isStreaming, displayRenderers: displayRenderers, attachmentUrl: attachmentUrl }, i))) })
34
+ : _jsx(Markdown, { children: message.content }), isStreaming && !isUser && _jsx(StreamingIndicator, {})] }), !isStreaming && (_jsx("div", { className: cn("flex items-center gap-0.5 mt-0.5", isUser ? "justify-end" : "justify-start"), children: _jsx(CopyButton, { text: extractText(message) }) }))] }));
35
+ }, (prev, next) => prev.message === next.message
36
+ && prev.isStreaming === next.isStreaming
37
+ && prev.displayRenderers === next.displayRenderers
38
+ && prev.attachmentUrl === next.attachmentUrl
39
+ && prev.className === next.className);
@@ -0,0 +1,19 @@
1
+ export interface Attachment {
2
+ id: string;
3
+ file: File;
4
+ preview?: string;
5
+ type: "image" | "file" | "audio";
6
+ }
7
+ export interface MessageInputProps {
8
+ input: string;
9
+ setInput: (value: string) => void;
10
+ handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => void;
11
+ isLoading?: boolean;
12
+ isUploading?: boolean;
13
+ stop?: () => void;
14
+ placeholder?: string;
15
+ className?: string;
16
+ enableAttachments?: boolean;
17
+ enableVoice?: boolean;
18
+ }
19
+ export declare function MessageInput({ input, setInput, handleSubmit, isLoading, isUploading, stop, placeholder, className, enableAttachments, enableVoice, }: MessageInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,214 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useEffect, useState, useCallback } from "react";
3
+ import { Send, Square, Plus, Mic, X, Camera, Paperclip, Image as ImageIcon, CircleStop, Loader2 } from "lucide-react";
4
+ import { Button } from "../ui/button.js";
5
+ import { cn } from "../lib/utils.js";
6
+ // ── Constants ──
7
+ const LINE_HEIGHT_PX = 24;
8
+ const MAX_ROWS = 10;
9
+ const MULTILINE_THRESHOLD_PX = LINE_HEIGHT_PX * 1.5; // 36px — above this = multiline
10
+ // ── Recording Hook ──
11
+ function useAudioRecording(onComplete) {
12
+ const [isRecording, setIsRecording] = useState(false);
13
+ const [elapsed, setElapsed] = useState(0);
14
+ const mediaRecorderRef = useRef(null);
15
+ const chunksRef = useRef([]);
16
+ const timerRef = useRef();
17
+ const start = useCallback(async () => {
18
+ try {
19
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
20
+ const recorder = new MediaRecorder(stream);
21
+ mediaRecorderRef.current = recorder;
22
+ chunksRef.current = [];
23
+ recorder.ondataavailable = (e) => { if (e.data.size > 0)
24
+ chunksRef.current.push(e.data); };
25
+ recorder.onstop = () => {
26
+ const blob = new Blob(chunksRef.current, { type: "audio/webm" });
27
+ const file = new File([blob], `audio-${Date.now()}.webm`, { type: "audio/webm" });
28
+ stream.getTracks().forEach((t) => t.stop());
29
+ onComplete(file);
30
+ };
31
+ recorder.start();
32
+ setIsRecording(true);
33
+ setElapsed(0);
34
+ timerRef.current = setInterval(() => setElapsed((s) => s + 1), 1000);
35
+ }
36
+ catch { /* permission denied */ }
37
+ }, [onComplete]);
38
+ const stop = useCallback(() => {
39
+ mediaRecorderRef.current?.stop();
40
+ setIsRecording(false);
41
+ clearInterval(timerRef.current);
42
+ }, []);
43
+ const cancel = useCallback(() => {
44
+ if (mediaRecorderRef.current) {
45
+ mediaRecorderRef.current.onstop = null;
46
+ mediaRecorderRef.current.stop();
47
+ mediaRecorderRef.current.stream.getTracks().forEach((t) => t.stop());
48
+ }
49
+ setIsRecording(false);
50
+ setElapsed(0);
51
+ clearInterval(timerRef.current);
52
+ }, []);
53
+ useEffect(() => () => clearInterval(timerRef.current), []);
54
+ return { isRecording, elapsed, start, stop, cancel };
55
+ }
56
+ function formatTime(seconds) {
57
+ const m = Math.floor(seconds / 60);
58
+ const s = seconds % 60;
59
+ return `${m}:${s.toString().padStart(2, "0")}`;
60
+ }
61
+ // ── Attachment Preview ──
62
+ function AttachmentPreview({ attachment, onRemove }) {
63
+ return (_jsxs("div", { className: "relative group shrink-0", children: [attachment.type === "image" && attachment.preview ? (_jsx("div", { className: "relative h-16 w-16 rounded-lg overflow-hidden border border-border/50", children: _jsx("img", { src: attachment.preview, alt: attachment.file.name, className: "h-full w-full object-cover" }) })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-border/50 bg-background/50 px-3 py-2", children: [_jsx(Paperclip, { className: "h-4 w-4 text-muted-foreground shrink-0" }), _jsx("span", { className: "text-xs text-muted-foreground truncate max-w-[120px]", children: attachment.file.name })] })), _jsx("button", { type: "button", onClick: onRemove, className: "absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full bg-foreground text-background flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity", "aria-label": "Remover", children: _jsx(X, { className: "h-3 w-3" }) })] }));
64
+ }
65
+ // ── Plus Menu ──
66
+ function PlusMenu({ onFile, onCamera, onGallery, onClose }) {
67
+ const menuRef = useRef(null);
68
+ useEffect(() => {
69
+ function handleClick(e) {
70
+ if (menuRef.current && !menuRef.current.contains(e.target))
71
+ onClose();
72
+ }
73
+ document.addEventListener("mousedown", handleClick);
74
+ return () => document.removeEventListener("mousedown", handleClick);
75
+ }, [onClose]);
76
+ return (_jsx("div", { ref: menuRef, className: "absolute bottom-full left-0 mb-2 rounded-xl border border-border bg-popover text-popover-foreground shadow-lg py-1 min-w-[160px] z-10", children: [
77
+ { icon: Paperclip, label: "Arquivo", onClick: onFile },
78
+ { icon: Camera, label: "Camera", onClick: onCamera },
79
+ { icon: ImageIcon, label: "Galeria", onClick: onGallery },
80
+ ].map((item) => (_jsxs("button", { type: "button", onClick: () => { item.onClick(); onClose(); }, className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm hover:bg-muted/50 transition-colors", children: [_jsx(item.icon, { className: "h-4 w-4 text-muted-foreground" }), item.label] }, item.label))) }));
81
+ }
82
+ // ── Main Component ──
83
+ export function MessageInput({ input, setInput, handleSubmit, isLoading, isUploading = false, stop, placeholder = "Caixa de mensagem...", className, enableAttachments = true, enableVoice = true, }) {
84
+ const textareaRef = useRef(null);
85
+ const fileInputRef = useRef(null);
86
+ const imageInputRef = useRef(null);
87
+ const containerRef = useRef(null);
88
+ const [attachments, setAttachments] = useState([]);
89
+ const [showMenu, setShowMenu] = useState(false);
90
+ const [isDragging, setIsDragging] = useState(false);
91
+ const [isMultiline, setIsMultiline] = useState(false);
92
+ const historyRef = useRef([]);
93
+ const historyPosRef = useRef(-1);
94
+ const savedInputRef = useRef("");
95
+ const onRecordingComplete = useCallback((file) => {
96
+ const att = { id: crypto.randomUUID(), file, type: "audio" };
97
+ setAttachments((prev) => [...prev, att]);
98
+ }, []);
99
+ const { isRecording, elapsed, start: startRecording, stop: stopRecording, cancel: cancelRecording } = useAudioRecording(onRecordingComplete);
100
+ const hasContent = input.trim().length > 0 || attachments.length > 0;
101
+ // Auto-focus
102
+ useEffect(() => { textareaRef.current?.focus(); }, []);
103
+ // Auto-expand — stable measurement with height:auto
104
+ useEffect(() => {
105
+ const el = textareaRef.current;
106
+ if (!el)
107
+ return;
108
+ el.style.height = "auto";
109
+ const scrollH = el.scrollHeight;
110
+ const maxH = MAX_ROWS * LINE_HEIGHT_PX;
111
+ const clampedH = Math.min(scrollH, maxH);
112
+ el.style.height = `${clampedH}px`;
113
+ setIsMultiline(scrollH > MULTILINE_THRESHOLD_PX);
114
+ }, [input]);
115
+ // ── Attachments ──
116
+ const addFiles = useCallback((files) => {
117
+ const newAttachments = Array.from(files).map((file) => {
118
+ const isImage = file.type.startsWith("image/");
119
+ const att = { id: crypto.randomUUID(), file, type: isImage ? "image" : "file" };
120
+ if (isImage) {
121
+ const reader = new FileReader();
122
+ reader.onload = (e) => {
123
+ setAttachments((prev) => prev.map((a) => (a.id === att.id ? { ...a, preview: e.target?.result } : a)));
124
+ };
125
+ reader.readAsDataURL(file);
126
+ }
127
+ return att;
128
+ });
129
+ setAttachments((prev) => [...prev, ...newAttachments]);
130
+ }, []);
131
+ const removeAttachment = useCallback((id) => {
132
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
133
+ }, []);
134
+ // ── Drag & Drop ──
135
+ const handleDragOver = useCallback((e) => { e.preventDefault(); setIsDragging(true); }, []);
136
+ const handleDragLeave = useCallback((e) => {
137
+ if (containerRef.current && !containerRef.current.contains(e.relatedTarget))
138
+ setIsDragging(false);
139
+ }, []);
140
+ const handleDrop = useCallback((e) => {
141
+ e.preventDefault();
142
+ setIsDragging(false);
143
+ if (e.dataTransfer.files.length)
144
+ addFiles(e.dataTransfer.files);
145
+ }, [addFiles]);
146
+ // ── Clipboard Paste ──
147
+ const handlePaste = useCallback((e) => {
148
+ const files = [];
149
+ for (let i = 0; i < e.clipboardData.items.length; i++) {
150
+ if (e.clipboardData.items[i].kind === "file") {
151
+ const file = e.clipboardData.items[i].getAsFile();
152
+ if (file)
153
+ files.push(file);
154
+ }
155
+ }
156
+ if (files.length) {
157
+ e.preventDefault();
158
+ addFiles(files);
159
+ }
160
+ }, [addFiles]);
161
+ // ── Submit ──
162
+ function onSubmit(e) {
163
+ e.preventDefault();
164
+ if (!hasContent || isLoading)
165
+ return;
166
+ if (input.trim()) {
167
+ historyRef.current.unshift(input);
168
+ if (historyRef.current.length > 50)
169
+ historyRef.current.length = 50;
170
+ }
171
+ historyPosRef.current = -1;
172
+ savedInputRef.current = "";
173
+ handleSubmit(e, attachments.length > 0 ? attachments : undefined);
174
+ setAttachments([]);
175
+ }
176
+ function handleKeyDown(e) {
177
+ // History navigation — only when cursor is at start/end and not multiline content
178
+ const el = textareaRef.current;
179
+ if (e.key === "ArrowUp" && historyRef.current.length > 0 && el) {
180
+ const atTop = el.selectionStart === 0 && el.selectionEnd === 0;
181
+ const isEmpty = input === "";
182
+ if (atTop || isEmpty) {
183
+ e.preventDefault();
184
+ if (historyPosRef.current === -1)
185
+ savedInputRef.current = input;
186
+ const nextPos = Math.min(historyPosRef.current + 1, historyRef.current.length - 1);
187
+ if (nextPos !== historyPosRef.current) {
188
+ historyPosRef.current = nextPos;
189
+ setInput(historyRef.current[nextPos]);
190
+ }
191
+ return;
192
+ }
193
+ }
194
+ if (e.key === "ArrowDown" && historyPosRef.current >= 0 && el) {
195
+ const atBottom = el.selectionStart === input.length;
196
+ if (atBottom) {
197
+ e.preventDefault();
198
+ const nextPos = historyPosRef.current - 1;
199
+ historyPosRef.current = nextPos;
200
+ setInput(nextPos < 0 ? savedInputRef.current : historyRef.current[nextPos]);
201
+ return;
202
+ }
203
+ }
204
+ if (e.key === "Enter" && !e.shiftKey) {
205
+ e.preventDefault();
206
+ if (hasContent && !isLoading)
207
+ onSubmit(e);
208
+ }
209
+ }
210
+ // ── Render ──
211
+ return (_jsxs("div", { ref: containerRef, onDragOver: enableAttachments ? handleDragOver : undefined, onDragLeave: enableAttachments ? handleDragLeave : undefined, onDrop: enableAttachments ? handleDrop : undefined, className: cn("relative border border-border/50 bg-muted transition-[border-radius] duration-200", isMultiline || attachments.length > 0 ? "rounded-2xl" : "rounded-full", isDragging && "ring-2 ring-primary/50", className), children: [isDragging && (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-[inherit] bg-primary/5 border-2 border-dashed border-primary/30", children: _jsx("span", { className: "text-sm text-primary font-medium", children: "Solte aqui" }) })), isRecording && (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: cancelRecording, className: "h-8 w-8 rounded-full shrink-0 text-muted-foreground", "aria-label": "Cancelar", children: _jsx(X, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex items-center gap-2 flex-1", children: [_jsx("span", { className: "h-2 w-2 rounded-full bg-red-500 animate-pulse" }), _jsx("span", { className: "text-sm font-medium tabular-nums", children: formatTime(elapsed) }), _jsx("div", { className: "flex-1 flex items-center gap-0.5 px-2", children: Array.from({ length: 20 }, (_, i) => (_jsx("span", { className: "w-1 bg-foreground/30 rounded-full", style: { height: `${4 + Math.random() * 12}px` } }, i))) })] }), _jsx(Button, { type: "button", size: "icon", onClick: stopRecording, className: "h-8 w-8 rounded-full shrink-0", "aria-label": "Parar", children: _jsx(CircleStop, { className: "h-4 w-4" }) })] })), _jsxs("div", { className: cn(isRecording && "hidden"), children: [(attachments.length > 0 || isUploading) && (_jsxs("div", { className: "flex flex-wrap gap-2 px-4 pt-3 pb-1", children: [attachments.map((att) => (_jsx(AttachmentPreview, { attachment: att, onRemove: () => removeAttachment(att.id) }, att.id))), isUploading && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-border/50 bg-background/50 px-3 py-2 text-xs text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin shrink-0" }), _jsx("span", { children: "Enviando arquivos..." })] }))] })), _jsxs("div", { className: cn("flex gap-1 p-1.5", isMultiline ? "items-end" : "items-center"), children: [enableAttachments && (_jsxs("div", { className: "relative shrink-0", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full", onClick: () => setShowMenu(!showMenu), "aria-label": "Adicionar", children: _jsx(Plus, { className: "h-4 w-4" }) }), showMenu && (_jsx(PlusMenu, { onFile: () => fileInputRef.current?.click(), onCamera: () => imageInputRef.current?.click(), onGallery: () => imageInputRef.current?.click(), onClose: () => setShowMenu(false) }))] })), _jsx("textarea", { ref: textareaRef, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, onPaste: enableAttachments ? handlePaste : undefined, placeholder: placeholder, rows: 1, disabled: isLoading, "aria-label": "Mensagem", className: "flex-1 min-w-0 bg-transparent text-foreground text-sm resize-none outline-none placeholder:text-muted-foreground leading-6 py-1 px-2" }), _jsxs("div", { className: "flex items-center gap-0.5 shrink-0", children: [enableVoice && !hasContent && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8 rounded-full text-muted-foreground", onClick: startRecording, "aria-label": "Gravar audio", children: _jsx(Mic, { className: "h-4 w-4" }) })), isLoading && stop ? (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: stop, className: "h-8 w-8 rounded-full", "aria-label": "Parar gera\u00E7\u00E3o", children: _jsx(Square, { className: "h-4 w-4" }) })) : (_jsx(Button, { type: "button", size: "icon", onClick: onSubmit, disabled: !hasContent || !!isLoading, className: "h-8 w-8 rounded-full", "aria-label": "Enviar mensagem", children: _jsx(Send, { className: "h-4 w-4" }) }))] })] })] }), _jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", onChange: (e) => { if (e.target.files)
212
+ addFiles(e.target.files); e.target.value = ""; } }), _jsx("input", { ref: imageInputRef, type: "file", accept: "image/*", multiple: true, className: "hidden", onChange: (e) => { if (e.target.files)
213
+ addFiles(e.target.files); e.target.value = ""; } })] }));
214
+ }
@@ -0,0 +1,12 @@
1
+ import type { Message } from "@ai-sdk/react";
2
+ import type { DisplayRendererMap } from "../display/registry.js";
3
+ export interface MessageListProps {
4
+ messages: Message[];
5
+ isLoading?: boolean;
6
+ displayRenderers?: DisplayRendererMap;
7
+ attachmentUrl?: (ref: string) => string;
8
+ className?: string;
9
+ error?: Error;
10
+ onRetry?: () => void;
11
+ }
12
+ export declare function MessageList({ messages, isLoading, displayRenderers, attachmentUrl, className, error, onRetry }: MessageListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
3
+ import { useRef, useEffect, useMemo, useCallback } from "react";
4
+ import { useVirtualizer } from "@tanstack/react-virtual";
5
+ import { MessageBubble } from "./MessageBubble.js";
6
+ import { StreamingIndicator } from "./StreamingIndicator.js";
7
+ import { ErrorNote } from "./ErrorNote.js";
8
+ import { ScrollBar } from "../ui/scroll-area.js";
9
+ import { cn } from "../lib/utils.js";
10
+ export function MessageList({ messages, isLoading, displayRenderers, attachmentUrl, className, error, onRetry }) {
11
+ const viewportRef = useRef(null);
12
+ const isFollowingRef = useRef(true);
13
+ const lastAssistantIndex = useMemo(() => messages.reduceRight((found, msg, i) => {
14
+ if (found !== -1)
15
+ return found;
16
+ return msg.role === "assistant" ? i : -1;
17
+ }, -1), [messages]);
18
+ const virtualizer = useVirtualizer({
19
+ count: messages.length,
20
+ getScrollElement: () => viewportRef.current,
21
+ estimateSize: () => 80,
22
+ overscan: 5,
23
+ paddingStart: 16,
24
+ });
25
+ // Track scroll position to detect if user is following
26
+ const handleScroll = useCallback(() => {
27
+ const viewport = viewportRef.current;
28
+ if (!viewport)
29
+ return;
30
+ const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
31
+ isFollowingRef.current = distanceFromBottom <= 100;
32
+ }, []);
33
+ useEffect(() => {
34
+ const viewport = viewportRef.current;
35
+ if (!viewport)
36
+ return;
37
+ viewport.addEventListener("scroll", handleScroll, { passive: true });
38
+ return () => viewport.removeEventListener("scroll", handleScroll);
39
+ }, [handleScroll]);
40
+ // Auto-scroll to bottom when following
41
+ useEffect(() => {
42
+ if (messages.length > 0 && isFollowingRef.current) {
43
+ virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
44
+ }
45
+ }, [messages, virtualizer]);
46
+ // Auto-scroll when error appears
47
+ useEffect(() => {
48
+ if (error && isFollowingRef.current) {
49
+ const viewport = viewportRef.current;
50
+ if (viewport)
51
+ viewport.scrollTop = viewport.scrollHeight;
52
+ }
53
+ }, [error]);
54
+ const virtualItems = virtualizer.getVirtualItems();
55
+ if (messages.length === 0) {
56
+ return (_jsxs(ScrollAreaPrimitive.Root, { className: cn("flex-1 relative overflow-hidden", className), children: [_jsx(ScrollAreaPrimitive.Viewport, { ref: viewportRef, className: "h-full w-full rounded-[inherit]", children: _jsx("div", { className: "flex items-center justify-center text-muted-foreground text-sm py-8", children: "Envie uma mensagem para comecar" }) }), _jsx(ScrollBar, {}), _jsx(ScrollAreaPrimitive.Corner, {})] }));
57
+ }
58
+ return (_jsxs(ScrollAreaPrimitive.Root, { className: cn("flex-1 relative overflow-hidden", className), children: [_jsxs(ScrollAreaPrimitive.Viewport, { ref: viewportRef, className: "h-full w-full rounded-[inherit]", children: [_jsx("div", { className: "relative w-full", style: { height: virtualizer.getTotalSize() + 16 }, children: _jsx("div", { children: virtualItems.map((virtualRow) => {
59
+ const message = messages[virtualRow.index];
60
+ return (_jsx("div", { "data-index": virtualRow.index, ref: virtualizer.measureElement, className: "pb-3 px-4", style: {
61
+ position: "absolute",
62
+ top: 0,
63
+ left: 0,
64
+ right: 0,
65
+ transform: `translateY(${virtualRow.start}px)`,
66
+ }, children: _jsx(MessageBubble, { message: message, isStreaming: virtualRow.index === lastAssistantIndex && isLoading && messages[messages.length - 1]?.role === "assistant", displayRenderers: displayRenderers, attachmentUrl: attachmentUrl }) }, message.id ?? virtualRow.index));
67
+ }) }) }), isLoading && messages[messages.length - 1]?.role !== "assistant" && (_jsx("div", { className: "px-4 pb-3", children: _jsx(StreamingIndicator, {}) })), !isLoading && error && messages.length > 0 && messages[messages.length - 1]?.role !== "assistant" && (_jsx("div", { className: "px-4 pb-3", children: _jsx(ErrorNote, { onRetry: onRetry }) }))] }), _jsx(ScrollBar, {}), _jsx(ScrollAreaPrimitive.Corner, {})] }));
68
+ }
@@ -0,0 +1 @@
1
+ export declare function StreamingIndicator(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function StreamingIndicator() {
3
+ return (_jsxs("span", { className: "inline-flex items-end gap-1.5 py-1 mt-4", "aria-label": "Gerando resposta...", role: "status", children: [_jsx("span", { className: "size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_0ms]" }), _jsx("span", { className: "size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_150ms]" }), _jsx("span", { className: "size-1 rounded-full bg-primary animate-[streaming-bounce_1.2s_ease-in-out_infinite_300ms]" }), _jsx("style", { children: `
4
+ @keyframes streaming-bounce {
5
+ 0%, 60%, 100% { transform: translateY(0); }
6
+ 30% { transform: translateY(-4px); }
7
+ }
8
+ ` })] }));
9
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ interface CollapsibleGroupProps {
3
+ label: string;
4
+ icon?: React.ReactNode;
5
+ open: boolean;
6
+ onToggle: () => void;
7
+ children: React.ReactNode;
8
+ }
9
+ declare function CollapsibleGroup({ label, icon, open, onToggle, children }: CollapsibleGroupProps): import("react/jsx-runtime").JSX.Element;
10
+ export { CollapsibleGroup };
11
+ export type { CollapsibleGroupProps };
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronRight } from "lucide-react";
4
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
5
+ import { cn } from "../lib/utils.js";
6
+ function CollapsibleGroup({ label, icon, open, onToggle, children }) {
7
+ return (_jsxs(Collapsible, { open: open, onOpenChange: onToggle, children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { className: "flex w-full items-center gap-1 px-2 py-1 hover:bg-accent rounded-md", "aria-expanded": open, children: [_jsx(ChevronRight, { className: cn("size-3 shrink-0 text-muted-foreground transition-transform duration-200", open && "rotate-90") }), icon && _jsx("span", { className: "shrink-0", children: icon }), _jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label })] }) }), _jsx(CollapsibleContent, { children: children })] }));
8
+ }
9
+ export { CollapsibleGroup };
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+ interface ConversationBarProps {
3
+ title?: string;
4
+ agentLabel?: string;
5
+ isLoading?: boolean;
6
+ onRename?: (title: string) => void;
7
+ onExport?: () => void;
8
+ onDelete?: () => void;
9
+ onBack?: () => void;
10
+ renameOpen?: boolean;
11
+ onRenameOpenChange?: (open: boolean) => void;
12
+ deleteOpen?: boolean;
13
+ onDeleteOpenChange?: (open: boolean) => void;
14
+ isPendingRename?: boolean;
15
+ isPendingDelete?: boolean;
16
+ renameLabel?: string;
17
+ exportLabel?: string;
18
+ deleteLabel?: string;
19
+ untitledLabel?: string;
20
+ actionsExtra?: React.ReactNode;
21
+ menuItemsExtra?: React.ReactNode;
22
+ afterBar?: React.ReactNode;
23
+ className?: string;
24
+ }
25
+ declare function ConversationBar({ title, agentLabel, isLoading, onRename, onExport, onDelete, onBack, renameOpen: renameOpenProp, onRenameOpenChange, deleteOpen: deleteOpenProp, onDeleteOpenChange, isPendingRename, isPendingDelete, renameLabel, exportLabel, deleteLabel, untitledLabel, actionsExtra, menuItemsExtra, afterBar, className, }: ConversationBarProps): import("react/jsx-runtime").JSX.Element;
26
+ export { ConversationBar };
27
+ export type { ConversationBarProps };