1 line
9.6 KiB
Plaintext
1 line
9.6 KiB
Plaintext
{"version":3,"file":"CopilotChatReasoningMessage.mjs","names":[],"sources":["../../../src/components/chat/CopilotChatReasoningMessage.tsx"],"sourcesContent":["import { ReasoningMessage, Message } from \"@ag-ui/core\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { ChevronRight } from \"lucide-react\";\nimport { twMerge } from \"tailwind-merge\";\nimport { Streamdown } from \"streamdown\";\nimport { WithSlots, renderSlot } from \"@/lib/slots\";\n\nexport type CopilotChatReasoningMessageProps = WithSlots<\n {\n header: typeof CopilotChatReasoningMessage.Header;\n contentView: typeof CopilotChatReasoningMessage.Content;\n toggle: typeof CopilotChatReasoningMessage.Toggle;\n },\n {\n message: ReasoningMessage;\n messages?: Message[];\n isRunning?: boolean;\n } & React.HTMLAttributes<HTMLDivElement>\n>;\n\n/**\n * Formats an elapsed duration (in seconds) to a human-readable string.\n */\nfunction formatDuration(seconds: number): string {\n if (seconds < 1) return \"a few seconds\";\n if (seconds < 60) return `${Math.round(seconds)} seconds`;\n const mins = Math.floor(seconds / 60);\n const secs = Math.round(seconds % 60);\n if (secs === 0) return `${mins} minute${mins > 1 ? \"s\" : \"\"}`;\n return `${mins}m ${secs}s`;\n}\n\nexport function CopilotChatReasoningMessage({\n message,\n messages,\n isRunning,\n header,\n contentView,\n toggle,\n children,\n className,\n ...props\n}: CopilotChatReasoningMessageProps) {\n const isLatest = messages?.[messages.length - 1]?.id === message.id;\n const isStreaming = !!(isRunning && isLatest);\n const hasContent = !!(message.content && message.content.length > 0);\n\n // Track elapsed time while streaming\n const startTimeRef = useRef<number | null>(null);\n const [elapsed, setElapsed] = useState(0);\n\n useEffect(() => {\n if (isStreaming && startTimeRef.current === null) {\n startTimeRef.current = Date.now();\n }\n\n if (!isStreaming && startTimeRef.current !== null) {\n // Final snapshot of elapsed time\n setElapsed((Date.now() - startTimeRef.current) / 1000);\n return;\n }\n\n if (!isStreaming) return;\n\n // Tick every second while streaming\n const timer = setInterval(() => {\n if (startTimeRef.current !== null) {\n setElapsed((Date.now() - startTimeRef.current) / 1000);\n }\n }, 1000);\n return () => clearInterval(timer);\n }, [isStreaming]);\n\n // Default to open while streaming, auto-collapse when streaming ends\n const [isOpen, setIsOpen] = useState(isStreaming);\n\n useEffect(() => {\n if (isStreaming) {\n setIsOpen(true);\n } else {\n // Auto-collapse when reasoning finishes\n setIsOpen(false);\n }\n }, [isStreaming]);\n\n const label = isStreaming\n ? \"Thinking…\"\n : `Thought for ${formatDuration(elapsed)}`;\n\n const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {\n isOpen,\n label,\n hasContent,\n isStreaming,\n onClick: hasContent ? () => setIsOpen((prev) => !prev) : undefined,\n });\n\n const boundContent = renderSlot(\n contentView,\n CopilotChatReasoningMessage.Content,\n {\n isStreaming,\n hasContent,\n children: message.content,\n },\n );\n\n const boundToggle = renderSlot(toggle, CopilotChatReasoningMessage.Toggle, {\n isOpen,\n children: boundContent,\n });\n\n if (children) {\n return (\n <div data-copilotkit style={{ display: \"contents\" }}>\n {children({\n header: boundHeader,\n contentView: boundContent,\n toggle: boundToggle,\n message,\n messages,\n isRunning,\n })}\n </div>\n );\n }\n\n return (\n <div\n className={twMerge(\"cpk:my-1\", className)}\n data-message-id={message.id}\n {...props}\n >\n {boundHeader}\n {boundToggle}\n </div>\n );\n}\n\nexport namespace CopilotChatReasoningMessage {\n export const Header: React.FC<\n React.ButtonHTMLAttributes<HTMLButtonElement> & {\n isOpen?: boolean;\n label?: string;\n hasContent?: boolean;\n isStreaming?: boolean;\n }\n > = ({\n isOpen,\n label = \"Thoughts\",\n hasContent,\n isStreaming,\n className,\n children: headerChildren,\n ...headerProps\n }) => {\n const isExpandable = !!hasContent;\n\n return (\n <button\n type=\"button\"\n className={twMerge(\n \"cpk:inline-flex cpk:items-center cpk:gap-1 cpk:py-1 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:select-none\",\n isExpandable\n ? \"cpk:hover:text-foreground cpk:cursor-pointer\"\n : \"cpk:cursor-default\",\n className,\n )}\n aria-expanded={isExpandable ? isOpen : undefined}\n {...headerProps}\n >\n <span className=\"cpk:font-medium\">{label}</span>\n {isStreaming && !hasContent && (\n <span className=\"cpk:inline-flex cpk:items-center cpk:ml-1\">\n <span className=\"cpk:w-1.5 cpk:h-1.5 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse\" />\n </span>\n )}\n {headerChildren}\n {isExpandable && (\n <ChevronRight\n className={twMerge(\n \"cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200\",\n isOpen && \"cpk:rotate-90\",\n )}\n />\n )}\n </button>\n );\n };\n\n export const Content: React.FC<\n React.HTMLAttributes<HTMLDivElement> & {\n isStreaming?: boolean;\n hasContent?: boolean;\n }\n > = ({\n isStreaming,\n hasContent,\n className,\n children: contentChildren,\n ...contentProps\n }) => {\n // Don't render the content area at all when there's nothing to show\n if (!hasContent && !isStreaming) return null;\n\n return (\n <div\n className={twMerge(\"cpk:pb-2 cpk:pt-1\", className)}\n {...contentProps}\n >\n <div className=\"cpk:text-sm cpk:text-muted-foreground\">\n <Streamdown>\n {typeof contentChildren === \"string\" ? contentChildren : \"\"}\n </Streamdown>\n {isStreaming && hasContent && (\n <span className=\"cpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle\">\n <span className=\"cpk:w-2 cpk:h-2 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse-cursor\" />\n </span>\n )}\n </div>\n </div>\n );\n };\n\n export const Toggle: React.FC<\n React.HTMLAttributes<HTMLDivElement> & {\n isOpen?: boolean;\n }\n > = ({ isOpen, className, children: toggleChildren, ...toggleProps }) => {\n return (\n <div\n className={twMerge(\n \"cpk:grid cpk:transition-[grid-template-rows] cpk:duration-200 cpk:ease-in-out\",\n className,\n )}\n style={{ gridTemplateRows: isOpen ? \"1fr\" : \"0fr\" }}\n {...toggleProps}\n >\n <div className=\"cpk:overflow-hidden\">{toggleChildren}</div>\n </div>\n );\n };\n}\n\nCopilotChatReasoningMessage.Header.displayName =\n \"CopilotChatReasoningMessage.Header\";\nCopilotChatReasoningMessage.Content.displayName =\n \"CopilotChatReasoningMessage.Content\";\nCopilotChatReasoningMessage.Toggle.displayName =\n \"CopilotChatReasoningMessage.Toggle\";\n\nexport default CopilotChatReasoningMessage;\n"],"mappings":"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"} |