@humain/ui 2.2.7 → 2.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index101.js +2 -2
- package/dist/index101.js.map +1 -1
- package/dist/index103.js +3 -3
- package/dist/index103.js.map +1 -1
- package/dist/index108.js +1 -1
- package/dist/index108.js.map +1 -1
- package/dist/index117.js +1 -1
- package/dist/index117.js.map +1 -1
- package/dist/index122.js +6 -6
- package/dist/index122.js.map +1 -1
- package/dist/index126.js +1 -1
- package/dist/index126.js.map +1 -1
- package/dist/index130.js +2 -2
- package/dist/index130.js.map +1 -1
- package/dist/index146.js +4 -4
- package/dist/index146.js.map +1 -1
- package/dist/index152.js +1 -1
- package/dist/index152.js.map +1 -1
- package/dist/index154.js +2 -2
- package/dist/index154.js.map +1 -1
- package/dist/index166.js +1 -1
- package/dist/index166.js.map +1 -1
- package/dist/index168.js +1 -1
- package/dist/index168.js.map +1 -1
- package/dist/index169.js +1 -1
- package/dist/index169.js.map +1 -1
- package/dist/index173.js +2 -2
- package/dist/index173.js.map +1 -1
- package/dist/index175.js +4 -4
- package/dist/index175.js.map +1 -1
- package/dist/index176.js +2 -2
- package/dist/index176.js.map +1 -1
- package/dist/index177.js +2 -2
- package/dist/index177.js.map +1 -1
- package/dist/index184.js +1 -1
- package/dist/index184.js.map +1 -1
- package/dist/index191.js +3 -3
- package/dist/index191.js.map +1 -1
- package/dist/index192.js +2 -2
- package/dist/index192.js.map +1 -1
- package/dist/index201.js +2 -2
- package/dist/index201.js.map +1 -1
- package/dist/index204.js +4 -4
- package/dist/index204.js.map +1 -1
- package/dist/index228.js +1 -1
- package/dist/index228.js.map +1 -1
- package/dist/index229.js +1 -1
- package/dist/index229.js.map +1 -1
- package/dist/index24.js +2 -2
- package/dist/index24.js.map +1 -1
- package/dist/index5.js +1 -1
- package/dist/index5.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/wc/AICodeBlock.js +41 -41
- package/dist/wc/AICodeBlock.js.map +1 -1
- package/dist/wc/AIMermaidDiagram.js +7 -7
- package/dist/wc/AIMermaidDiagram.js.map +1 -1
- package/dist/wc/AIMessage.js +6 -6
- package/dist/wc/AIMessage.js.map +1 -1
- package/dist/wc/AISource.js +2 -2
- package/dist/wc/AISource.js.map +1 -1
- package/dist/wc/AIToolCall.js +1 -1
- package/dist/wc/AIToolCall.js.map +1 -1
- package/dist/wc/Breadcrumb.js +43 -43
- package/dist/wc/Breadcrumb.js.map +1 -1
- package/dist/wc/DropdownMenu.js +9 -9
- package/dist/wc/DropdownMenu.js.map +1 -1
- package/dist/wc/Kbd.js +22 -22
- package/dist/wc/Kbd.js.map +1 -1
- package/dist/wc/button.js +7 -7
- package/dist/wc/button.js.map +1 -1
- package/dist/wc/close-button.js +19 -19
- package/dist/wc/close-button.js.map +1 -1
- package/dist/wc/hf-ai-prompt-suggestion.js +14 -14
- package/dist/wc/hf-ai-prompt-suggestion.js.map +1 -1
- package/dist/wc/hf-ai-reasoning-block.js +13 -13
- package/dist/wc/hf-ai-reasoning-block.js.map +1 -1
- package/dist/wc/hf-ai-steps.js +53 -53
- package/dist/wc/hf-ai-steps.js.map +1 -1
- package/dist/wc/hf-ai-system-message.js +26 -26
- package/dist/wc/hf-ai-system-message.js.map +1 -1
- package/dist/wc/hf-code-snippet.js +35 -35
- package/dist/wc/hf-code-snippet.js.map +1 -1
- package/dist/wc/hf-command-menu.js +39 -39
- package/dist/wc/hf-command-menu.js.map +1 -1
- package/dist/wc/hf-data-table.js +3 -3
- package/dist/wc/hf-data-table.js.map +1 -1
- package/dist/wc/hf-display-value.js +50 -50
- package/dist/wc/hf-display-value.js.map +1 -1
- package/dist/wc/hf-nav-account-menu.js +5 -5
- package/dist/wc/hf-nav-account-menu.js.map +1 -1
- package/dist/wc/hf-rich-textarea.js +1 -1
- package/dist/wc/hf-rich-textarea.js.map +1 -1
- package/dist/wc/hf-steps.js +8 -8
- package/dist/wc/hf-steps.js.map +1 -1
- package/dist/wc/markdown-renderer.js +1 -1
- package/dist/wc/markdown-renderer.js.map +1 -1
- package/dist/wc/selection-group.js +26 -26
- package/dist/wc/selection-group.js.map +1 -1
- package/dist/wc/syntax-highlight.js +4 -4
- package/dist/wc/syntax-highlight.js.map +1 -1
- package/dist/wc/workflow-step-renderer.js +51 -51
- package/dist/wc/workflow-step-renderer.js.map +1 -1
- package/dist/wc-react/AICodeBlock.js +17 -17
- package/dist/wc-react/AICodeBlock.js.map +1 -1
- package/dist/wc-react/AIMermaidDiagram.js +16 -16
- package/dist/wc-react/AIMermaidDiagram.js.map +1 -1
- package/dist/wc-react/AIMessage.js +7 -7
- package/dist/wc-react/AIMessage.js.map +1 -1
- package/dist/wc-react/AISource.js +41 -41
- package/dist/wc-react/AISource.js.map +1 -1
- package/dist/wc-react/AIToolCall.js +6 -6
- package/dist/wc-react/AIToolCall.js.map +1 -1
- package/dist/wc-react/Breadcrumb.js +18 -18
- package/dist/wc-react/Breadcrumb.js.map +1 -1
- package/dist/wc-react/DropdownMenu.js +27 -27
- package/dist/wc-react/DropdownMenu.js.map +1 -1
- package/dist/wc-react/Kbd.js +17 -17
- package/dist/wc-react/Kbd.js.map +1 -1
- package/dist/wc-react/button.js +12 -12
- package/dist/wc-react/button.js.map +1 -1
- package/dist/wc-react/close-button.js +19 -19
- package/dist/wc-react/close-button.js.map +1 -1
- package/dist/wc-react/hf-ai-prompt-suggestion.js +1 -1
- package/dist/wc-react/hf-ai-prompt-suggestion.js.map +1 -1
- package/dist/wc-react/hf-ai-reasoning-block.js +1 -1
- package/dist/wc-react/hf-ai-reasoning-block.js.map +1 -1
- package/dist/wc-react/hf-ai-steps.js +33 -33
- package/dist/wc-react/hf-ai-steps.js.map +1 -1
- package/dist/wc-react/hf-ai-system-message.js +9 -9
- package/dist/wc-react/hf-ai-system-message.js.map +1 -1
- package/dist/wc-react/hf-code-snippet.js +3 -3
- package/dist/wc-react/hf-code-snippet.js.map +1 -1
- package/dist/wc-react/hf-command-menu.js +56 -56
- package/dist/wc-react/hf-command-menu.js.map +1 -1
- package/dist/wc-react/hf-data-table.js +3 -3
- package/dist/wc-react/hf-data-table.js.map +1 -1
- package/dist/wc-react/hf-display-value.js +58 -58
- package/dist/wc-react/hf-display-value.js.map +1 -1
- package/dist/wc-react/hf-nav-account-menu.js +7 -7
- package/dist/wc-react/hf-nav-account-menu.js.map +1 -1
- package/dist/wc-react/hf-rich-textarea.js +5 -5
- package/dist/wc-react/hf-rich-textarea.js.map +1 -1
- package/dist/wc-react/hf-steps.js +2 -2
- package/dist/wc-react/hf-steps.js.map +1 -1
- package/dist/wc-react/markdown-renderer.js +1 -1
- package/dist/wc-react/markdown-renderer.js.map +1 -1
- package/dist/wc-react/selection-group.js +7 -7
- package/dist/wc-react/selection-group.js.map +1 -1
- package/dist/wc-react/syntax-highlight.js +4 -4
- package/dist/wc-react/syntax-highlight.js.map +1 -1
- package/dist/wc-react/workflow-step-renderer.js +44 -44
- package/dist/wc-react/workflow-step-renderer.js.map +1 -1
- package/package.json +1 -1
- package/skills/humain-ui/SKILL.md +1 -1
- package/skills/humain-ui-wc/SKILL.md +1 -1
- package/skills/humain-ui-wc.skill +0 -0
- package/skills/humain-ui.skill +0 -0
- package/src/tokens.css +4 -0
package/dist/index146.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index146.js","names":[],"sources":["../src/components/organisms/ai/AICodeBlock/AICodeBlock.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Check, Copy } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from '@/components/atoms/buttons/Button';\nimport { SUGAR_HIGH_STYLE } from '@/lib/syntax-highlight';\nimport {\n renderHighlightedCode,\n renderHighlightedMarkup,\n} from '@/lib/syntax-highlight-react';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Variants\n// =============================================================================\n\nconst codeBlockVariants = cva(\n 'relative overflow-hidden rounded-xl border border-border bg-muted',\n {\n variants: {\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n },\n);\n\ninterface CopyFeedbackState {\n copied: boolean;\n resetKey: number;\n}\n\nfunction copyFeedbackReducer(\n state: CopyFeedbackState,\n copied: boolean,\n): CopyFeedbackState {\n return copied\n ? { copied: true, resetKey: state.resetKey + 1 }\n : { ...state, copied: false };\n}\n\n// =============================================================================\n// Code Block Header\n// =============================================================================\n\nexport interface AICodeBlockHeaderProps {\n /** Programming language */\n language?: string;\n /** File name to display */\n fileName?: string;\n /** Whether to show copy button */\n showCopyButton?: boolean;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Whether content was copied */\n copied?: boolean;\n /** Additional class name */\n className?: string;\n /** Extra actions to render */\n actions?: React.ReactNode;\n}\n\nfunction AICodeBlockHeader({\n language,\n fileName,\n showCopyButton = true,\n onCopy,\n copied = false,\n className,\n actions,\n}: AICodeBlockHeaderProps) {\n return (\n <div\n data-slot=\"ai-code-block-header\"\n className={cn(\n 'flex items-center justify-between px-4 py-2 border-b border-border bg-accent',\n className,\n )}\n >\n <div className=\"flex items-center gap-2\">\n {fileName && (\n <span className=\"text-sm font-medium text-secondary-foreground\">\n {fileName}\n </span>\n )}\n {language && !fileName && (\n <span className=\"text-xs font-medium text-text-quaternary uppercase\">\n {language}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {actions}\n {showCopyButton && (\n <Button\n appearance=\"ghost\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onCopy}\n aria-label={\n copied ? 'Copied to clipboard' : 'Copy code to clipboard'\n }\n className=\"h-7 px-2 text-text-quaternary hover:text-secondary-foreground transition-colors duration-150 ease-out\"\n >\n {copied ? (\n <Check className=\"size-4 text-success delight-bounce-in motion-reduce:animate-none\" />\n ) : (\n <Copy className=\"size-4\" />\n )}\n <span className=\"ms-1.5 text-xs\">\n {copied ? 'Copied!' : 'Copy'}\n </span>\n </Button>\n )}\n </div>\n </div>\n );\n}\n\nAICodeBlockHeader.displayName = 'AICodeBlockHeader';\n\n// =============================================================================\n// Code Block Content\n// =============================================================================\n\nexport interface AICodeBlockContentProps {\n /** Code content */\n children: React.ReactNode;\n /** Whether to show line numbers */\n showLineNumbers?: boolean;\n /** Starting line number */\n startLineNumber?: number;\n /** Additional class name */\n className?: string;\n}\n\ninterface AICodeBlockContentBodyProps {\n children: React.ReactNode;\n showLineNumbers: boolean;\n startLineNumber: number;\n}\n\nfunction AICodeBlockContentBody({\n children,\n showLineNumbers,\n startLineNumber,\n}: AICodeBlockContentBodyProps): React.ReactNode {\n if (showLineNumbers && typeof children === 'string') {\n const lines = children.split('\\n');\n return (\n <div className=\"flex\">\n <div className=\"flex flex-col items-end pe-4 select-none border-e border-border me-4\">\n {lines.map((_, index) => (\n <span key={index} className=\"text-muted-foreground leading-relaxed\">\n {startLineNumber + index}\n </span>\n ))}\n </div>\n <section\n className=\"flex-1 overflow-x-auto [overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"m-0 [overflow-wrap:normal]\">\n <code>{children}</code>\n </pre>\n </section>\n </div>\n );\n }\n\n return (\n <section\n className=\"overflow-x-auto [overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"m-0 [overflow-wrap:normal]\">\n <code>{children}</code>\n </pre>\n </section>\n );\n}\n\nfunction AICodeBlockContent({\n children,\n showLineNumbers = false,\n startLineNumber = 1,\n className,\n}: AICodeBlockContentProps) {\n return (\n <div\n data-slot=\"ai-code-block-content\"\n className={cn('p-4 text-sm font-mono leading-relaxed', className)}\n >\n <AICodeBlockContentBody\n showLineNumbers={showLineNumbers}\n startLineNumber={startLineNumber}\n >\n {children}\n </AICodeBlockContentBody>\n </div>\n );\n}\n\nAICodeBlockContent.displayName = 'AICodeBlockContent';\n\n// =============================================================================\n// Main AICodeBlock Component\n// =============================================================================\n\nexport interface AICodeBlockProps\n extends VariantProps<typeof codeBlockVariants> {\n /** Code content to display */\n code: string;\n /** Programming language for syntax highlighting */\n language?: string;\n /** File name to display in header */\n fileName?: string;\n /** Whether to show the header */\n showHeader?: boolean;\n /** Whether to show copy button */\n showCopyButton?: boolean;\n /** Whether to show line numbers */\n showLineNumbers?: boolean;\n /** Starting line number */\n startLineNumber?: number;\n /** Pre-highlighted HTML content */\n highlightedCode?: string;\n /** Additional class name */\n className?: string;\n /** Extra actions for the header */\n headerActions?: React.ReactNode;\n}\n\nfunction AICodeBlock({\n code,\n language = 'plaintext',\n fileName,\n showHeader = true,\n showCopyButton = true,\n showLineNumbers = false,\n startLineNumber = 1,\n highlightedCode,\n size,\n className,\n headerActions,\n}: AICodeBlockProps) {\n const highlightedContent = React.useMemo(\n () =>\n highlightedCode\n ? renderHighlightedMarkup(highlightedCode)\n : renderHighlightedCode(code),\n [code, highlightedCode],\n );\n const [copyFeedback, setCopyFeedback] = React.useReducer(\n copyFeedbackReducer,\n { copied: false, resetKey: 0 },\n );\n const { copied } = copyFeedback;\n const rootRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!copyFeedback.copied) return;\n\n const copyResetTimer = setTimeout(() => {\n setCopyFeedback(false);\n }, 2000);\n\n return () => {\n clearTimeout(copyResetTimer);\n };\n }, [copyFeedback]);\n\n const handleCopy = React.useCallback(async () => {\n try {\n // SSR guard: access clipboard via ref's ownerDocument\n const win = rootRef.current?.ownerDocument.defaultView;\n if (!win) return;\n await win.navigator.clipboard.writeText(code);\n setCopyFeedback(true);\n } catch (err) {\n console.error('Failed to copy code:', err);\n }\n }, [code]);\n\n return (\n <div\n ref={rootRef}\n data-slot=\"ai-code-block\"\n className={cn(codeBlockVariants({ size }), className)}\n style={SUGAR_HIGH_STYLE}\n >\n {showHeader && (\n <AICodeBlockHeader\n language={language}\n fileName={fileName}\n showCopyButton={showCopyButton}\n onCopy={handleCopy}\n copied={copied}\n actions={headerActions}\n />\n )}\n <section\n className={cn(\n 'p-4 overflow-x-auto font-mono leading-relaxed [overflow-wrap:normal] !bg-transparent !m-0 !border-0 !rounded-none',\n '[&_.sh__line]:block [&_.sh__line]:leading-relaxed',\n showLineNumbers && [\n '[&_.sh__line]:[counter-increment:sh-line]',\n '[&_.sh__line]:before:[content:counter(sh-line)] [&_.sh__line]:before:inline-block [&_.sh__line]:before:w-8 [&_.sh__line]:before:me-4 [&_.sh__line]:before:text-end [&_.sh__line]:before:text-muted-foreground [&_.sh__line]:before:select-none',\n ],\n )}\n style={\n showLineNumbers\n ? ({\n counterReset: `sh-line ${startLineNumber - 1}`,\n } as React.CSSProperties)\n : undefined\n }\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"!m-0 !bg-transparent [overflow-wrap:normal]\">\n {highlightedContent}\n </pre>\n </section>\n </div>\n );\n}\n\nAICodeBlock.displayName = 'AICodeBlock';\n\nexport {\n AICodeBlock,\n AICodeBlockContent,\n AICodeBlockHeader,\n codeBlockVariants,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index146.js","names":[],"sources":["../src/components/organisms/ai/AICodeBlock/AICodeBlock.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Check, Copy } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from '@/components/atoms/buttons/Button';\nimport { SUGAR_HIGH_STYLE } from '@/lib/syntax-highlight';\nimport {\n renderHighlightedCode,\n renderHighlightedMarkup,\n} from '@/lib/syntax-highlight-react';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Variants\n// =============================================================================\n\nconst codeBlockVariants = cva(\n 'relative overflow-hidden rounded-xl border border-border bg-muted',\n {\n variants: {\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n },\n);\n\ninterface CopyFeedbackState {\n copied: boolean;\n resetKey: number;\n}\n\nfunction copyFeedbackReducer(\n state: CopyFeedbackState,\n copied: boolean,\n): CopyFeedbackState {\n return copied\n ? { copied: true, resetKey: state.resetKey + 1 }\n : { ...state, copied: false };\n}\n\n// =============================================================================\n// Code Block Header\n// =============================================================================\n\nexport interface AICodeBlockHeaderProps {\n /** Programming language */\n language?: string;\n /** File name to display */\n fileName?: string;\n /** Whether to show copy button */\n showCopyButton?: boolean;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Whether content was copied */\n copied?: boolean;\n /** Additional class name */\n className?: string;\n /** Extra actions to render */\n actions?: React.ReactNode;\n}\n\nfunction AICodeBlockHeader({\n language,\n fileName,\n showCopyButton = true,\n onCopy,\n copied = false,\n className,\n actions,\n}: AICodeBlockHeaderProps) {\n return (\n <div\n data-slot=\"ai-code-block-header\"\n className={cn(\n 'flex items-center justify-between px-4 py-2 border-b border-border bg-accent',\n className,\n )}\n >\n <div className=\"flex items-center gap-2\">\n {fileName && (\n <span className=\"text-sm font-medium text-secondary-foreground\">\n {fileName}\n </span>\n )}\n {language && !fileName && (\n <span className=\"text-xs font-medium text-secondary-foreground uppercase\">\n {language}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {actions}\n {showCopyButton && (\n <Button\n appearance=\"ghost\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onCopy}\n aria-label={\n copied ? 'Copied to clipboard' : 'Copy code to clipboard'\n }\n className=\"h-7 px-2 text-secondary-foreground hover:text-foreground transition-colors duration-150 ease-out\"\n >\n {copied ? (\n <Check className=\"size-4 text-success delight-bounce-in motion-reduce:animate-none\" />\n ) : (\n <Copy className=\"size-4\" />\n )}\n <span className=\"ms-1.5 text-xs\">\n {copied ? 'Copied!' : 'Copy'}\n </span>\n </Button>\n )}\n </div>\n </div>\n );\n}\n\nAICodeBlockHeader.displayName = 'AICodeBlockHeader';\n\n// =============================================================================\n// Code Block Content\n// =============================================================================\n\nexport interface AICodeBlockContentProps {\n /** Code content */\n children: React.ReactNode;\n /** Whether to show line numbers */\n showLineNumbers?: boolean;\n /** Starting line number */\n startLineNumber?: number;\n /** Additional class name */\n className?: string;\n}\n\ninterface AICodeBlockContentBodyProps {\n children: React.ReactNode;\n showLineNumbers: boolean;\n startLineNumber: number;\n}\n\nfunction AICodeBlockContentBody({\n children,\n showLineNumbers,\n startLineNumber,\n}: AICodeBlockContentBodyProps): React.ReactNode {\n if (showLineNumbers && typeof children === 'string') {\n const lines = children.split('\\n');\n return (\n <div className=\"flex\">\n <div className=\"flex flex-col items-end pe-4 select-none border-e border-border me-4\">\n {lines.map((_, index) => (\n <span\n key={index}\n className=\"text-secondary-foreground leading-relaxed\"\n >\n {startLineNumber + index}\n </span>\n ))}\n </div>\n <section\n className=\"flex-1 overflow-x-auto [overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"m-0 [overflow-wrap:normal]\">\n <code>{children}</code>\n </pre>\n </section>\n </div>\n );\n }\n\n return (\n <section\n className=\"overflow-x-auto [overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"m-0 [overflow-wrap:normal]\">\n <code>{children}</code>\n </pre>\n </section>\n );\n}\n\nfunction AICodeBlockContent({\n children,\n showLineNumbers = false,\n startLineNumber = 1,\n className,\n}: AICodeBlockContentProps) {\n return (\n <div\n data-slot=\"ai-code-block-content\"\n className={cn('p-4 text-sm font-mono leading-relaxed', className)}\n >\n <AICodeBlockContentBody\n showLineNumbers={showLineNumbers}\n startLineNumber={startLineNumber}\n >\n {children}\n </AICodeBlockContentBody>\n </div>\n );\n}\n\nAICodeBlockContent.displayName = 'AICodeBlockContent';\n\n// =============================================================================\n// Main AICodeBlock Component\n// =============================================================================\n\nexport interface AICodeBlockProps\n extends VariantProps<typeof codeBlockVariants> {\n /** Code content to display */\n code: string;\n /** Programming language for syntax highlighting */\n language?: string;\n /** File name to display in header */\n fileName?: string;\n /** Whether to show the header */\n showHeader?: boolean;\n /** Whether to show copy button */\n showCopyButton?: boolean;\n /** Whether to show line numbers */\n showLineNumbers?: boolean;\n /** Starting line number */\n startLineNumber?: number;\n /** Pre-highlighted HTML content */\n highlightedCode?: string;\n /** Additional class name */\n className?: string;\n /** Extra actions for the header */\n headerActions?: React.ReactNode;\n}\n\nfunction AICodeBlock({\n code,\n language = 'plaintext',\n fileName,\n showHeader = true,\n showCopyButton = true,\n showLineNumbers = false,\n startLineNumber = 1,\n highlightedCode,\n size,\n className,\n headerActions,\n}: AICodeBlockProps) {\n const highlightedContent = React.useMemo(\n () =>\n highlightedCode\n ? renderHighlightedMarkup(highlightedCode)\n : renderHighlightedCode(code),\n [code, highlightedCode],\n );\n const [copyFeedback, setCopyFeedback] = React.useReducer(\n copyFeedbackReducer,\n { copied: false, resetKey: 0 },\n );\n const { copied } = copyFeedback;\n const rootRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!copyFeedback.copied) return;\n\n const copyResetTimer = setTimeout(() => {\n setCopyFeedback(false);\n }, 2000);\n\n return () => {\n clearTimeout(copyResetTimer);\n };\n }, [copyFeedback]);\n\n const handleCopy = React.useCallback(async () => {\n try {\n // SSR guard: access clipboard via ref's ownerDocument\n const win = rootRef.current?.ownerDocument.defaultView;\n if (!win) return;\n await win.navigator.clipboard.writeText(code);\n setCopyFeedback(true);\n } catch (err) {\n console.error('Failed to copy code:', err);\n }\n }, [code]);\n\n return (\n <div\n ref={rootRef}\n data-slot=\"ai-code-block\"\n className={cn(codeBlockVariants({ size }), className)}\n style={SUGAR_HIGH_STYLE}\n >\n {showHeader && (\n <AICodeBlockHeader\n language={language}\n fileName={fileName}\n showCopyButton={showCopyButton}\n onCopy={handleCopy}\n copied={copied}\n actions={headerActions}\n />\n )}\n <section\n className={cn(\n 'p-4 overflow-x-auto font-mono leading-relaxed [overflow-wrap:normal] !bg-transparent !m-0 !border-0 !rounded-none',\n '[&_.sh__line]:block [&_.sh__line]:leading-relaxed',\n showLineNumbers && [\n '[&_.sh__line]:[counter-increment:sh-line]',\n '[&_.sh__line]:before:[content:counter(sh-line)] [&_.sh__line]:before:inline-block [&_.sh__line]:before:w-8 [&_.sh__line]:before:me-4 [&_.sh__line]:before:text-end [&_.sh__line]:before:text-secondary-foreground [&_.sh__line]:before:select-none',\n ],\n )}\n style={\n showLineNumbers\n ? ({\n counterReset: `sh-line ${startLineNumber - 1}`,\n } as React.CSSProperties)\n : undefined\n }\n aria-label=\"Code content\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <pre className=\"!m-0 !bg-transparent [overflow-wrap:normal]\">\n {highlightedContent}\n </pre>\n </section>\n </div>\n );\n}\n\nAICodeBlock.displayName = 'AICodeBlock';\n\nexport {\n AICodeBlock,\n AICodeBlockContent,\n AICodeBlockHeader,\n codeBlockVariants,\n};\n"],"mappings":"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"}
|
package/dist/index152.js
CHANGED
package/dist/index152.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index152.js","names":[],"sources":["../src/components/organisms/ai/_shared/markdown-renderer.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport rehypeKatex from 'rehype-katex';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport type { PluggableList } from 'unified';\nimport { cn } from '@/lib/utils';\nimport { createCodeComponent } from './code-handler';\n\nconst remarkPlugins: PluggableList = [remarkGfm, remarkMath];\nconst rehypePlugins: PluggableList = [[rehypeKatex, { output: 'mathml' }]];\nconst markdownOverflowWrapClasses =\n 'min-w-0 max-w-full break-words [overflow-wrap:anywhere]';\n\n// =============================================================================\n// Variant-aware color mapping\n// =============================================================================\n\nimport type { MarkdownVariant } from './types';\n\nexport type { MarkdownVariant } from './types';\n\nconst variantColors = {\n heading: { default: 'text-foreground', inverted: 'text-primary-foreground' },\n body: {\n default: 'text-secondary-foreground',\n inverted: 'text-primary-foreground/90',\n },\n link: { default: 'text-primary', inverted: 'text-primary-foreground' },\n muted: {\n default: 'text-muted-foreground',\n inverted: 'text-primary-foreground/70',\n },\n faint: {\n default: 'text-text-quaternary',\n inverted: 'text-primary-foreground/50',\n },\n border: {\n default: 'border-border',\n inverted: 'border-primary-foreground/25',\n },\n bgMuted: { default: 'bg-muted', inverted: 'bg-background/20' },\n} as const;\n\ntype ColorToken = keyof typeof variantColors;\n\nfunction vc(variant: MarkdownVariant, token: ColorToken): string {\n return variantColors[token][variant];\n}\n\n// =============================================================================\n// Create variant-aware component overrides for react-markdown\n// =============================================================================\n\nfunction createMarkdownComponents(\n variant: MarkdownVariant,\n): Partial<Components> {\n return {\n h1: ({ className, children, ...props }) => (\n <h1\n className={cn(\n 'text-display-xs font-bold mt-6 mb-4',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h1>\n ),\n h2: ({ className, children, ...props }) => (\n <h2\n className={cn(\n 'text-xl font-semibold mt-5 mb-3',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h2>\n ),\n h3: ({ className, children, ...props }) => (\n <h3\n className={cn(\n 'text-lg font-semibold mt-4 mb-2',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h3>\n ),\n h4: ({ className, children, ...props }) => (\n <h4\n className={cn(\n 'text-base font-semibold mt-3 mb-2',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h4>\n ),\n p: ({ className, children, ...props }) => (\n <p\n className={cn(\n 'text-base leading-relaxed mb-4',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </p>\n ),\n ul: ({ className, children, ...props }) => (\n <ul\n className={cn(\n 'list-disc list-inside mb-4 space-y-1',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </ul>\n ),\n ol: ({ className, children, ...props }) => (\n <ol\n className={cn(\n 'list-decimal list-inside mb-4 space-y-1',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </ol>\n ),\n li: ({ className, children, ...props }) => (\n <li\n className={cn(\n 'leading-relaxed',\n markdownOverflowWrapClasses,\n className,\n )}\n {...props}\n >\n {children}\n </li>\n ),\n a: ({ className, children, href, ...props }) => (\n <a\n href={href}\n className={cn(\n 'underline underline-offset-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring rounded-sm',\n markdownOverflowWrapClasses,\n vc(variant, 'link'),\n variant === 'default' && 'hover:text-primary',\n variant === 'inverted' && 'hover:text-primary-foreground',\n className,\n )}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...props}\n >\n {children}\n </a>\n ),\n blockquote: ({ className, children, ...props }) => (\n <blockquote\n className={cn(\n 'border-s-4 ps-4 py-2 my-4 italic',\n markdownOverflowWrapClasses,\n vc(variant, 'muted'),\n vc(variant, 'border'),\n className,\n )}\n {...props}\n >\n {children}\n </blockquote>\n ),\n code: createCodeComponent(variant),\n pre: ({ children }) => <>{children}</>,\n hr: ({ className, ...props }) => (\n <hr className={cn('my-6', vc(variant, 'border'), className)} {...props} />\n ),\n table: ({ className, children, ...props }) => (\n <section\n className=\"not-prose overflow-x-auto my-4 [overflow-wrap:normal] [&_a]:break-normal [&_a]:[overflow-wrap:normal] [&_code]:break-normal [&_code]:[overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Data table\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <table\n className={cn(\n 'min-w-full border-collapse border [overflow-wrap:normal]',\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </table>\n </section>\n ),\n thead: ({ className, children, ...props }) => (\n <thead\n className={cn(\n variant === 'default' ? vc(variant, 'bgMuted') : '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </thead>\n ),\n tbody: ({ className, children, ...props }) => (\n <tbody\n className={cn(\n variant === 'default'\n ? 'divide-y divide-border'\n : 'divide-y divide-primary-foreground/20',\n className,\n )}\n {...props}\n >\n {children}\n </tbody>\n ),\n tr: ({ children, ...props }) => <tr {...props}>{children}</tr>,\n th: ({ className, children, ...props }) => (\n <th\n className={cn(\n 'px-4 py-2 text-start text-sm font-semibold border',\n '[overflow-wrap:normal]',\n vc(variant, 'body'),\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </th>\n ),\n td: ({ className, children, ...props }) => (\n <td\n className={cn(\n 'px-4 py-2 text-sm border',\n '[overflow-wrap:normal]',\n vc(variant, 'body'),\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </td>\n ),\n strong: ({ className, children, ...props }) => (\n <strong\n className={cn(\n 'font-semibold',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </strong>\n ),\n em: ({ className, children, ...props }) => (\n <em className={cn(markdownOverflowWrapClasses, className)} {...props}>\n {children}\n </em>\n ),\n del: ({ className, children, ...props }) => (\n <del\n className={cn(\n 'line-through',\n markdownOverflowWrapClasses,\n vc(variant, 'faint'),\n className,\n )}\n {...props}\n >\n {children}\n </del>\n ),\n img: ({ className, alt, src, ...props }) => (\n <img\n src={src}\n alt={alt}\n className={cn('max-w-full h-auto rounded-lg my-4', className)}\n loading=\"lazy\"\n {...props}\n />\n ),\n };\n}\n\n// =============================================================================\n// Component cache (at most 2 entries: 'default' and 'inverted')\n// =============================================================================\n\nconst componentCache = new Map<MarkdownVariant, Partial<Components>>();\n\nfunction getComponents(variant: MarkdownVariant): Partial<Components> {\n let cached = componentCache.get(variant);\n if (!cached) {\n cached = createMarkdownComponents(variant);\n componentCache.set(variant, cached);\n }\n return cached;\n}\n\n// =============================================================================\n// MarkdownRenderer\n// =============================================================================\n\nexport interface MarkdownRendererProps {\n /** Markdown content to render */\n children: string;\n /** Additional class name */\n className?: string;\n /** Color variant — 'inverted' for white text on colored backgrounds */\n variant?: MarkdownVariant;\n}\n\nconst MarkdownRenderer = React.memo(function MarkdownRenderer({\n children,\n className,\n variant = 'default',\n}: MarkdownRendererProps) {\n const components = getComponents(variant);\n\n return (\n <div\n data-slot=\"ai-markdown\"\n className={cn(\n 'prose prose-gray max-w-none',\n markdownOverflowWrapClasses,\n className,\n )}\n >\n <ReactMarkdown\n remarkPlugins={remarkPlugins}\n rehypePlugins={rehypePlugins}\n components={components}\n >\n {children}\n </ReactMarkdown>\n </div>\n );\n});\n\nMarkdownRenderer.displayName = 'MarkdownRenderer';\n\nexport { MarkdownRenderer };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index152.js","names":[],"sources":["../src/components/organisms/ai/_shared/markdown-renderer.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport rehypeKatex from 'rehype-katex';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport type { PluggableList } from 'unified';\nimport { cn } from '@/lib/utils';\nimport { createCodeComponent } from './code-handler';\n\nconst remarkPlugins: PluggableList = [remarkGfm, remarkMath];\nconst rehypePlugins: PluggableList = [[rehypeKatex, { output: 'mathml' }]];\nconst markdownOverflowWrapClasses =\n 'min-w-0 max-w-full break-words [overflow-wrap:anywhere]';\n\n// =============================================================================\n// Variant-aware color mapping\n// =============================================================================\n\nimport type { MarkdownVariant } from './types';\n\nexport type { MarkdownVariant } from './types';\n\nconst variantColors = {\n heading: { default: 'text-foreground', inverted: 'text-primary-foreground' },\n body: {\n default: 'text-secondary-foreground',\n inverted: 'text-primary-foreground/90',\n },\n link: { default: 'text-primary', inverted: 'text-primary-foreground' },\n muted: {\n default: 'text-muted-foreground',\n inverted: 'text-primary-foreground/70',\n },\n faint: {\n default: 'text-secondary-foreground',\n inverted: 'text-primary-foreground/50',\n },\n border: {\n default: 'border-border',\n inverted: 'border-primary-foreground/25',\n },\n bgMuted: { default: 'bg-muted', inverted: 'bg-background/20' },\n} as const;\n\ntype ColorToken = keyof typeof variantColors;\n\nfunction vc(variant: MarkdownVariant, token: ColorToken): string {\n return variantColors[token][variant];\n}\n\n// =============================================================================\n// Create variant-aware component overrides for react-markdown\n// =============================================================================\n\nfunction createMarkdownComponents(\n variant: MarkdownVariant,\n): Partial<Components> {\n return {\n h1: ({ className, children, ...props }) => (\n <h1\n className={cn(\n 'text-display-xs font-bold mt-6 mb-4',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h1>\n ),\n h2: ({ className, children, ...props }) => (\n <h2\n className={cn(\n 'text-xl font-semibold mt-5 mb-3',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h2>\n ),\n h3: ({ className, children, ...props }) => (\n <h3\n className={cn(\n 'text-lg font-semibold mt-4 mb-2',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h3>\n ),\n h4: ({ className, children, ...props }) => (\n <h4\n className={cn(\n 'text-base font-semibold mt-3 mb-2',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </h4>\n ),\n p: ({ className, children, ...props }) => (\n <p\n className={cn(\n 'text-base leading-relaxed mb-4',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </p>\n ),\n ul: ({ className, children, ...props }) => (\n <ul\n className={cn(\n 'list-disc list-inside mb-4 space-y-1',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </ul>\n ),\n ol: ({ className, children, ...props }) => (\n <ol\n className={cn(\n 'list-decimal list-inside mb-4 space-y-1',\n markdownOverflowWrapClasses,\n vc(variant, 'body'),\n className,\n )}\n {...props}\n >\n {children}\n </ol>\n ),\n li: ({ className, children, ...props }) => (\n <li\n className={cn(\n 'leading-relaxed',\n markdownOverflowWrapClasses,\n className,\n )}\n {...props}\n >\n {children}\n </li>\n ),\n a: ({ className, children, href, ...props }) => (\n <a\n href={href}\n className={cn(\n 'underline underline-offset-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring rounded-sm',\n markdownOverflowWrapClasses,\n vc(variant, 'link'),\n variant === 'default' && 'hover:text-primary',\n variant === 'inverted' && 'hover:text-primary-foreground',\n className,\n )}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...props}\n >\n {children}\n </a>\n ),\n blockquote: ({ className, children, ...props }) => (\n <blockquote\n className={cn(\n 'border-s-4 ps-4 py-2 my-4 italic',\n markdownOverflowWrapClasses,\n vc(variant, 'muted'),\n vc(variant, 'border'),\n className,\n )}\n {...props}\n >\n {children}\n </blockquote>\n ),\n code: createCodeComponent(variant),\n pre: ({ children }) => <>{children}</>,\n hr: ({ className, ...props }) => (\n <hr className={cn('my-6', vc(variant, 'border'), className)} {...props} />\n ),\n table: ({ className, children, ...props }) => (\n <section\n className=\"not-prose overflow-x-auto my-4 [overflow-wrap:normal] [&_a]:break-normal [&_a]:[overflow-wrap:normal] [&_code]:break-normal [&_code]:[overflow-wrap:normal] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n aria-label=\"Data table\"\n // biome-ignore lint/a11y/noNoninteractiveTabindex: Focusable scroll region satisfies axe scrollable-region-focusable.\n tabIndex={0}\n >\n <table\n className={cn(\n 'min-w-full border-collapse border [overflow-wrap:normal]',\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </table>\n </section>\n ),\n thead: ({ className, children, ...props }) => (\n <thead\n className={cn(\n variant === 'default' ? vc(variant, 'bgMuted') : '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </thead>\n ),\n tbody: ({ className, children, ...props }) => (\n <tbody\n className={cn(\n variant === 'default'\n ? 'divide-y divide-border'\n : 'divide-y divide-primary-foreground/20',\n className,\n )}\n {...props}\n >\n {children}\n </tbody>\n ),\n tr: ({ children, ...props }) => <tr {...props}>{children}</tr>,\n th: ({ className, children, ...props }) => (\n <th\n className={cn(\n 'px-4 py-2 text-start text-sm font-semibold border',\n '[overflow-wrap:normal]',\n vc(variant, 'body'),\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </th>\n ),\n td: ({ className, children, ...props }) => (\n <td\n className={cn(\n 'px-4 py-2 text-sm border',\n '[overflow-wrap:normal]',\n vc(variant, 'body'),\n vc(variant, 'border'),\n variant === 'inverted' && '!bg-transparent',\n className,\n )}\n {...props}\n >\n {children}\n </td>\n ),\n strong: ({ className, children, ...props }) => (\n <strong\n className={cn(\n 'font-semibold',\n markdownOverflowWrapClasses,\n vc(variant, 'heading'),\n className,\n )}\n {...props}\n >\n {children}\n </strong>\n ),\n em: ({ className, children, ...props }) => (\n <em className={cn(markdownOverflowWrapClasses, className)} {...props}>\n {children}\n </em>\n ),\n del: ({ className, children, ...props }) => (\n <del\n className={cn(\n 'line-through',\n markdownOverflowWrapClasses,\n vc(variant, 'faint'),\n className,\n )}\n {...props}\n >\n {children}\n </del>\n ),\n img: ({ className, alt, src, ...props }) => (\n <img\n src={src}\n alt={alt}\n className={cn('max-w-full h-auto rounded-lg my-4', className)}\n loading=\"lazy\"\n {...props}\n />\n ),\n };\n}\n\n// =============================================================================\n// Component cache (at most 2 entries: 'default' and 'inverted')\n// =============================================================================\n\nconst componentCache = new Map<MarkdownVariant, Partial<Components>>();\n\nfunction getComponents(variant: MarkdownVariant): Partial<Components> {\n let cached = componentCache.get(variant);\n if (!cached) {\n cached = createMarkdownComponents(variant);\n componentCache.set(variant, cached);\n }\n return cached;\n}\n\n// =============================================================================\n// MarkdownRenderer\n// =============================================================================\n\nexport interface MarkdownRendererProps {\n /** Markdown content to render */\n children: string;\n /** Additional class name */\n className?: string;\n /** Color variant — 'inverted' for white text on colored backgrounds */\n variant?: MarkdownVariant;\n}\n\nconst MarkdownRenderer = React.memo(function MarkdownRenderer({\n children,\n className,\n variant = 'default',\n}: MarkdownRendererProps) {\n const components = getComponents(variant);\n\n return (\n <div\n data-slot=\"ai-markdown\"\n className={cn(\n 'prose prose-gray max-w-none',\n markdownOverflowWrapClasses,\n className,\n )}\n >\n <ReactMarkdown\n remarkPlugins={remarkPlugins}\n rehypePlugins={rehypePlugins}\n components={components}\n >\n {children}\n </ReactMarkdown>\n </div>\n );\n});\n\nMarkdownRenderer.displayName = 'MarkdownRenderer';\n\nexport { MarkdownRenderer };\n"],"mappings":";;;;;;;;;;AAWA,IAAM,gBAA+B,CAAC,WAAW,UAAU;AAC3D,IAAM,gBAA+B,CAAC,CAAC,aAAa,EAAE,QAAQ,SAAS,CAAC,CAAC;AACzE,IAAM,8BACJ;AAUF,IAAM,gBAAgB;CACpB,SAAS;EAAE,SAAS;EAAmB,UAAU;CAA0B;CAC3E,MAAM;EACJ,SAAS;EACT,UAAU;CACZ;CACA,MAAM;EAAE,SAAS;EAAgB,UAAU;CAA0B;CACrE,OAAO;EACL,SAAS;EACT,UAAU;CACZ;CACA,OAAO;EACL,SAAS;EACT,UAAU;CACZ;CACA,QAAQ;EACN,SAAS;EACT,UAAU;CACZ;CACA,SAAS;EAAE,SAAS;EAAY,UAAU;CAAmB;AAC/D;AAIA,SAAS,GAAG,SAA0B,OAA2B;CAC/D,OAAO,cAAc,MAAM,CAAC;AAC9B;AAMA,SAAS,yBACP,SACqB;CACrB,OAAO;EACL,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,uCACA,6BACA,GAAG,SAAS,SAAS,GACrB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,mCACA,6BACA,GAAG,SAAS,SAAS,GACrB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,mCACA,6BACA,GAAG,SAAS,SAAS,GACrB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,qCACA,6BACA,GAAG,SAAS,SAAS,GACrB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,IAAI,EAAE,WAAW,UAAU,GAAG,YAC5B,oBAAC,KAAD;GACE,WAAW,GACT,kCACA,6BACA,GAAG,SAAS,MAAM,GAClB,SACF;GACA,GAAI;GAEH;EACA,CAAA;EAEL,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,wCACA,6BACA,GAAG,SAAS,MAAM,GAClB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,2CACA,6BACA,GAAG,SAAS,MAAM,GAClB,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,mBACA,6BACA,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,IAAI,EAAE,WAAW,UAAU,MAAM,GAAG,YAClC,oBAAC,KAAD;GACQ;GACN,WAAW,GACT,+IACA,6BACA,GAAG,SAAS,MAAM,GAClB,YAAY,aAAa,sBACzB,YAAY,cAAc,iCAC1B,SACF;GACA,QAAO;GACP,KAAI;GACJ,GAAI;GAEH;EACA,CAAA;EAEL,aAAa,EAAE,WAAW,UAAU,GAAG,YACrC,oBAAC,cAAD;GACE,WAAW,GACT,oCACA,6BACA,GAAG,SAAS,OAAO,GACnB,GAAG,SAAS,QAAQ,GACpB,SACF;GACA,GAAI;GAEH;EACS,CAAA;EAEd,MAAM,oBAAoB,OAAO;EACjC,MAAM,EAAE,eAAe,oBAAA,UAAA,EAAG,SAAW,CAAA;EACrC,KAAK,EAAE,WAAW,GAAG,YACnB,oBAAC,MAAD;GAAI,WAAW,GAAG,QAAQ,GAAG,SAAS,QAAQ,GAAG,SAAS;GAAG,GAAI;EAAQ,CAAA;EAE3E,QAAQ,EAAE,WAAW,UAAU,GAAG,YAChC,oBAAC,WAAD;GACE,WAAU;GACV,cAAW;GAEX,UAAU;aAEV,oBAAC,SAAD;IACE,WAAW,GACT,4DACA,GAAG,SAAS,QAAQ,GACpB,YAAY,cAAc,mBAC1B,SACF;IACA,GAAI;IAEH;GACI,CAAA;EACA,CAAA;EAEX,QAAQ,EAAE,WAAW,UAAU,GAAG,YAChC,oBAAC,SAAD;GACE,WAAW,GACT,YAAY,YAAY,GAAG,SAAS,SAAS,IAAI,mBACjD,SACF;GACA,GAAI;GAEH;EACI,CAAA;EAET,QAAQ,EAAE,WAAW,UAAU,GAAG,YAChC,oBAAC,SAAD;GACE,WAAW,GACT,YAAY,YACR,2BACA,yCACJ,SACF;GACA,GAAI;GAEH;EACI,CAAA;EAET,KAAK,EAAE,UAAU,GAAG,YAAY,oBAAC,MAAD;GAAI,GAAI;GAAQ;EAAa,CAAA;EAC7D,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,qDACA,0BACA,GAAG,SAAS,MAAM,GAClB,GAAG,SAAS,QAAQ,GACpB,YAAY,cAAc,mBAC1B,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GACE,WAAW,GACT,4BACA,0BACA,GAAG,SAAS,MAAM,GAClB,GAAG,SAAS,QAAQ,GACpB,YAAY,cAAc,mBAC1B,SACF;GACA,GAAI;GAEH;EACC,CAAA;EAEN,SAAS,EAAE,WAAW,UAAU,GAAG,YACjC,oBAAC,UAAD;GACE,WAAW,GACT,iBACA,6BACA,GAAG,SAAS,SAAS,GACrB,SACF;GACA,GAAI;GAEH;EACK,CAAA;EAEV,KAAK,EAAE,WAAW,UAAU,GAAG,YAC7B,oBAAC,MAAD;GAAI,WAAW,GAAG,6BAA6B,SAAS;GAAG,GAAI;GAC5D;EACC,CAAA;EAEN,MAAM,EAAE,WAAW,UAAU,GAAG,YAC9B,oBAAC,OAAD;GACE,WAAW,GACT,gBACA,6BACA,GAAG,SAAS,OAAO,GACnB,SACF;GACA,GAAI;GAEH;EACE,CAAA;EAEP,MAAM,EAAE,WAAW,KAAK,KAAK,GAAG,YAC9B,oBAAC,OAAD;GACO;GACA;GACL,WAAW,GAAG,qCAAqC,SAAS;GAC5D,SAAQ;GACR,GAAI;EACL,CAAA;CAEL;AACF;AAMA,IAAM,iCAAiB,IAAI,IAA0C;AAErE,SAAS,cAAc,SAA+C;CACpE,IAAI,SAAS,eAAe,IAAI,OAAO;CACvC,IAAI,CAAC,QAAQ;EACX,SAAS,yBAAyB,OAAO;EACzC,eAAe,IAAI,SAAS,MAAM;CACpC;CACA,OAAO;AACT;AAeA,IAAM,mBAAmB,QAAM,KAAK,SAAS,iBAAiB,EAC5D,UACA,WACA,UAAU,aACc;CACxB,MAAM,aAAa,cAAc,OAAO;CAExC,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,+BACA,6BACA,SACF;YAEA,oBAAC,eAAD;GACiB;GACA;GACH;GAEX;EACY,CAAA;CACZ,CAAA;AAET,CAAC;AAED,iBAAiB,cAAc"}
|
package/dist/index154.js
CHANGED
|
@@ -75,7 +75,7 @@ AIMessageReactions.displayName = "AIMessage.Reactions";
|
|
|
75
75
|
function AIMessageThumbsUp({ onClick, active, className }) {
|
|
76
76
|
return /* @__PURE__ */ jsx(ReactionButton, {
|
|
77
77
|
icon: /* @__PURE__ */ jsx(ThumbsUp, {
|
|
78
|
-
className: "size-4 text-
|
|
78
|
+
className: "size-4 text-muted-foreground",
|
|
79
79
|
"aria-hidden": "true"
|
|
80
80
|
}),
|
|
81
81
|
onClick,
|
|
@@ -88,7 +88,7 @@ AIMessageThumbsUp.displayName = "AIMessage.ThumbsUp";
|
|
|
88
88
|
function AIMessageThumbsDown({ onClick, active, className }) {
|
|
89
89
|
return /* @__PURE__ */ jsx(ReactionButton, {
|
|
90
90
|
icon: /* @__PURE__ */ jsx(ThumbsDown, {
|
|
91
|
-
className: "size-4 text-
|
|
91
|
+
className: "size-4 text-muted-foreground",
|
|
92
92
|
"aria-hidden": "true"
|
|
93
93
|
}),
|
|
94
94
|
onClick,
|
package/dist/index154.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index154.js","names":[],"sources":["../src/components/organisms/ai/AIMessage/AIMessage.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Check, Copy, List, ThumbsDown, ThumbsUp } from 'lucide-react';\nimport * as React from 'react';\nimport { forwardRef, type HTMLAttributes } from 'react';\nimport { Avatar, type AvatarProps } from '@/components/molecules/data/Avatar';\nimport { FileTypeIcon } from '@/components/molecules/files/FileTypeIcon';\nimport type { AIInteractionEvent } from '@/components/organisms/ai/_shared/interaction-context';\nimport { AIInteractionProvider } from '@/components/organisms/ai/_shared/interaction-context';\nimport { MarkdownRenderer } from '@/components/organisms/ai/_shared/markdown-renderer';\nimport { AITypingIndicator } from '@/components/organisms/ai/AITypingIndicator';\nimport { createSafeContext } from '@/lib/create-safe-context';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Message Variants\n// =============================================================================\n\nconst messageVariants = cva('flex items-start w-full', {\n variants: {\n type: {\n received: 'justify-start',\n sent: 'justify-end',\n },\n },\n defaultVariants: {\n type: 'received',\n },\n});\n\nconst messageOverflowWrapClasses =\n 'min-w-0 max-w-full break-words [overflow-wrap:anywhere]';\n\nconst messageBubbleVariants = cva(\n 'flex min-w-0 max-w-[min(720px,100%)] flex-col gap-1 overflow-hidden px-4 py-3 rounded-2xl',\n {\n variants: {\n type: {\n received: 'bg-muted rounded-es-sm',\n sent: 'bg-primary text-primary-foreground rounded-ee-sm',\n },\n },\n defaultVariants: {\n type: 'received',\n },\n },\n);\n\n// =============================================================================\n// Context for Composable API\n// =============================================================================\n\ninterface AIMessageContextValue {\n /** Message type */\n type: 'received' | 'sent';\n /** Whether sender is an AI agent */\n isAgent: boolean;\n /** Whether in compact mode */\n compact: boolean;\n /** Whether AI is typing */\n isTyping: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n}\n\nconst [AIMessageContext, useAIMessageContext] =\n createSafeContext<AIMessageContextValue>('AIMessage');\n\n// =============================================================================\n// AIMessage.Bubble - Bubble wrapper for header + content\n// =============================================================================\n\nexport interface AIMessageBubbleProps {\n /** Children (Header, Content, TypingIndicator) */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageBubble({ children, className }: AIMessageBubbleProps) {\n const ctx = React.useContext(AIMessageContext);\n const type = ctx?.type ?? 'received';\n\n return (\n <div\n data-slot=\"ai-message-bubble\"\n data-message-type={type}\n className={cn(messageBubbleVariants({ type }), className)}\n >\n {children}\n </div>\n );\n}\n\nAIMessageBubble.displayName = 'AIMessage.Bubble';\n\n// =============================================================================\n// Reaction Button (Internal)\n// =============================================================================\n\ninterface ReactionButtonProps {\n icon: React.ReactNode;\n onClick?: () => void;\n active?: boolean;\n count?: number;\n /** Accessible label for the button */\n label?: string;\n className?: string;\n}\n\nfunction ReactionButton({\n icon,\n onClick,\n active,\n count,\n label,\n className,\n}: ReactionButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n aria-pressed={active}\n className={cn(\n 'flex items-center justify-center gap-1 px-2.5 py-2 min-w-11 min-h-11 rounded-full bg-muted hover:bg-accent delight-click-ripple transition-all duration-150 ease-out hover:scale-110 active:scale-95 motion-reduce:hover:scale-100 motion-reduce:active:scale-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n active &&\n 'bg-primary/10 text-primary delight-check-bounce motion-reduce:!animate-none',\n className,\n )}\n >\n {icon}\n {count !== undefined && count > 0 && (\n <span className=\"text-xs font-medium text-muted-foreground\">\n {count}\n </span>\n )}\n </button>\n );\n}\n\n// =============================================================================\n// Emoji Reaction Types\n// =============================================================================\n\nexport interface EmojiReaction {\n emoji: string;\n count: number;\n reacted?: boolean;\n}\n\n// =============================================================================\n// AIMessage.Reactions - Emoji reactions display\n// =============================================================================\n\nexport interface AIMessageReactionsProps {\n /** Emoji reactions to display */\n reactions: EmojiReaction[];\n /** Callback when reaction is clicked */\n onReactionClick?: (emoji: string) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageReactions({\n reactions,\n onReactionClick,\n className,\n}: AIMessageReactionsProps) {\n if (!reactions?.length) return null;\n\n return (\n <div\n data-slot=\"ai-message-reactions\"\n className={cn('flex items-center gap-1', className)}\n >\n {reactions.map((reaction) => (\n <button\n key={reaction.emoji}\n type=\"button\"\n onClick={() => onReactionClick?.(reaction.emoji)}\n aria-label={`React with ${reaction.emoji}${reaction.count > 1 ? `, ${reaction.count} reactions` : ''}`}\n aria-pressed={reaction.reacted}\n className={cn(\n 'flex items-center gap-1 px-2.5 py-1.5 min-h-11 rounded-full bg-muted hover:bg-accent text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n reaction.reacted && 'bg-primary/10',\n )}\n >\n <span aria-hidden=\"true\">{reaction.emoji}</span>\n {reaction.count > 1 && (\n <span className=\"text-xs text-muted-foreground\">\n {reaction.count}\n </span>\n )}\n </button>\n ))}\n </div>\n );\n}\n\nAIMessageReactions.displayName = 'AIMessage.Reactions';\n\n// =============================================================================\n// AIMessage.ThumbsUp - Thumbs up button\n// =============================================================================\n\nexport interface AIMessageThumbsUpProps {\n /** Click handler */\n onClick?: () => void;\n /** Whether active */\n active?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageThumbsUp({\n onClick,\n active,\n className,\n}: AIMessageThumbsUpProps) {\n return (\n <ReactionButton\n icon={\n <ThumbsUp className=\"size-4 text-text-quaternary\" aria-hidden=\"true\" />\n }\n onClick={onClick}\n active={active}\n label=\"Thumbs up\"\n className={className}\n />\n );\n}\n\nAIMessageThumbsUp.displayName = 'AIMessage.ThumbsUp';\n\n// =============================================================================\n// AIMessage.ThumbsDown - Thumbs down button\n// =============================================================================\n\nexport interface AIMessageThumbsDownProps {\n /** Click handler */\n onClick?: () => void;\n /** Whether active */\n active?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageThumbsDown({\n onClick,\n active,\n className,\n}: AIMessageThumbsDownProps) {\n return (\n <ReactionButton\n icon={\n <ThumbsDown\n className=\"size-4 text-text-quaternary\"\n aria-hidden=\"true\"\n />\n }\n onClick={onClick}\n active={active}\n label=\"Thumbs down\"\n className={className}\n />\n );\n}\n\nAIMessageThumbsDown.displayName = 'AIMessage.ThumbsDown';\n\n// =============================================================================\n// AIMessage.CopyButton - Copy button\n// =============================================================================\n\nexport interface AIMessageCopyButtonProps {\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageCopyButton({ onClick, className }: AIMessageCopyButtonProps) {\n const [copied, setCopied] = React.useState(false);\n const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);\n\n React.useEffect(() => () => clearTimeout(timerRef.current), []);\n\n const handleClick = React.useCallback(() => {\n onClick?.();\n setCopied(true);\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setCopied(false), 2000);\n }, [onClick]);\n\n return (\n <ReactionButton\n icon={\n copied ? (\n <Check\n className=\"size-4 text-success delight-bounce-in motion-reduce:animate-none\"\n aria-hidden=\"true\"\n />\n ) : (\n <Copy\n className=\"size-4 text-secondary-foreground\"\n aria-hidden=\"true\"\n />\n )\n }\n onClick={handleClick}\n label={copied ? 'Copied to clipboard' : 'Copy message'}\n className={className}\n />\n );\n}\n\nAIMessageCopyButton.displayName = 'AIMessage.CopyButton';\n\n// =============================================================================\n// AIMessage.ListButton - List/expand button\n// =============================================================================\n\nexport interface AIMessageListButtonProps {\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageListButton({ onClick, className }: AIMessageListButtonProps) {\n return (\n <ReactionButton\n icon={\n <List className=\"size-4 text-secondary-foreground\" aria-hidden=\"true\" />\n }\n onClick={onClick}\n label=\"Show list\"\n className={className}\n />\n );\n}\n\nAIMessageListButton.displayName = 'AIMessage.ListButton';\n\n// =============================================================================\n// AIMessage.Actions - Action toolbar container\n// =============================================================================\n\nexport interface AIMessageActionsProps {\n /** Children (action buttons) */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageActions({ children, className }: AIMessageActionsProps) {\n const ctx = React.useContext(AIMessageContext);\n\n return (\n <div\n data-slot=\"ai-message-actions\"\n role=\"toolbar\"\n aria-label=\"Message actions\"\n className={cn(\n 'flex items-center gap-0.5 rounded-full overflow-hidden',\n className,\n )}\n >\n {children ??\n (ctx && (\n <>\n <AIMessageThumbsUp\n onClick={ctx.onThumbsUp}\n active={ctx.thumbsUpActive}\n />\n <AIMessageThumbsDown\n onClick={ctx.onThumbsDown}\n active={ctx.thumbsDownActive}\n />\n <AIMessageCopyButton onClick={ctx.onCopy} />\n <AIMessageListButton onClick={ctx.onShowList} />\n </>\n ))}\n </div>\n );\n}\n\nAIMessageActions.displayName = 'AIMessage.Actions';\n\n// =============================================================================\n// AIMessage.TypingIndicator - Bouncing dots\n// =============================================================================\n\nexport interface AIMessageTypingIndicatorProps {\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageTypingIndicator({\n className,\n}: AIMessageTypingIndicatorProps) {\n return (\n <div\n data-slot=\"ai-message-typing-indicator\"\n className={cn(\n 'flex items-center gap-1 motion-reduce:animate-none',\n className,\n )}\n >\n <AITypingIndicator size=\"sm\" color=\"var(--muted-foreground)\" />\n </div>\n );\n}\n\nAIMessageTypingIndicator.displayName = 'AIMessage.TypingIndicator';\n\n// =============================================================================\n// Attachment Types\n// =============================================================================\n\nexport interface Attachment {\n id: string;\n name: string;\n size: string;\n /** File extension (e.g., 'pdf', 'jpg', 'doc') */\n extension?: string;\n url?: string;\n thumbnailUrl?: string;\n}\n\n// =============================================================================\n// AIMessage.AttachmentPreview - Single attachment preview\n// =============================================================================\n\nexport interface AIMessageAttachmentPreviewProps {\n /** Attachment data */\n attachment: Attachment;\n /** Message type for styling */\n messageType?: 'received' | 'sent';\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageAttachmentPreview({\n attachment,\n messageType = 'received',\n onClick,\n className,\n}: AIMessageAttachmentPreviewProps) {\n // Derive extension from attachment.extension or extract from filename\n const getExtension = () => {\n if (attachment.extension) {\n return attachment.extension;\n }\n const parts = attachment.name.split('.');\n if (parts.length > 1) {\n return parts[parts.length - 1];\n }\n return '';\n };\n\n const extension = getExtension();\n\n const bubbleClasses =\n messageType === 'sent'\n ? 'bg-primary/10 border border-primary-foreground/20 rounded-2xl'\n : 'bg-muted rounded-2xl';\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-message-attachment-preview\"\n onClick={onClick}\n aria-label={`Attachment: ${attachment.name}, ${attachment.size}`}\n className={cn(\n 'flex items-center gap-3 p-3 min-h-11 hover:bg-accent transition-colors w-full max-w-[320px] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n bubbleClasses,\n className,\n )}\n >\n {attachment.thumbnailUrl ? (\n <img\n src={attachment.thumbnailUrl}\n alt={attachment.name}\n className=\"size-10 rounded-md object-cover\"\n />\n ) : (\n <FileTypeIcon extension={extension} size=\"md\" className=\"shrink-0\" />\n )}\n <div className=\"flex flex-col items-start min-w-0\">\n <span className=\"text-sm font-medium text-secondary-foreground truncate w-full\">\n {attachment.name}\n </span>\n <span className=\"text-sm text-muted-foreground\">{attachment.size}</span>\n </div>\n </button>\n );\n}\n\nAIMessageAttachmentPreview.displayName = 'AIMessage.AttachmentPreview';\n\n// =============================================================================\n// AIMessage.Attachments - Attachments container\n// =============================================================================\n\nexport interface AIMessageAttachmentsProps {\n /** Attachments to display */\n attachments: Attachment[];\n /** Message type for styling */\n messageType?: 'received' | 'sent';\n /** Callback when attachment is clicked */\n onAttachmentClick?: (attachment: Attachment) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageAttachments({\n attachments,\n messageType = 'received',\n onAttachmentClick,\n className,\n}: AIMessageAttachmentsProps) {\n if (!attachments?.length) return null;\n\n return (\n <div\n data-slot=\"ai-message-attachments\"\n className={cn('flex flex-col gap-2 mt-1', className)}\n >\n {attachments.map((attachment) => (\n <AIMessageAttachmentPreview\n key={attachment.id}\n attachment={attachment}\n messageType={messageType}\n onClick={() => onAttachmentClick?.(attachment)}\n />\n ))}\n </div>\n );\n}\n\nAIMessageAttachments.displayName = 'AIMessage.Attachments';\n\n// =============================================================================\n// AIMessage.Avatar - Avatar with agent indicator\n// =============================================================================\n\nexport interface AIMessageAvatarProps extends AvatarProps {\n /** Whether sender is an agent */\n isAgent?: boolean;\n}\n\nfunction AIMessageAvatar({ isAgent, ...props }: AIMessageAvatarProps) {\n return (\n <Avatar\n data-slot=\"ai-message-avatar\"\n {...props}\n size=\"md\"\n indicator={isAgent ? 'verified' : props.indicator}\n />\n );\n}\n\nAIMessageAvatar.displayName = 'AIMessage.Avatar';\n\n// =============================================================================\n// AIMessage.Content - Message content with markdown support\n// =============================================================================\n\nexport interface AIMessageContentProps {\n /** Message content */\n children?: React.ReactNode;\n /** Content as string (alternative to children) */\n content?: string;\n /** Whether to render content as markdown. Defaults to true for received, false for sent. */\n markdown?: boolean;\n /** Callback for interactive component events */\n onInteraction?: (event: AIInteractionEvent) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageContent({\n children,\n content,\n markdown,\n onInteraction,\n className,\n}: AIMessageContentProps) {\n const ctx = React.useContext(AIMessageContext);\n const type = ctx?.type ?? 'received';\n const isSent = type === 'sent';\n\n const shouldRenderMarkdown = markdown ?? type === 'received';\n const textContent = content ?? (typeof children === 'string' ? children : '');\n const variant = isSent ? 'inverted' : 'default';\n\n return (\n <div\n data-slot=\"ai-message-content\"\n className={cn('w-full', messageOverflowWrapClasses, className)}\n >\n {shouldRenderMarkdown && textContent ? (\n <AIInteractionProvider onInteraction={onInteraction}>\n <MarkdownRenderer\n variant={variant}\n className={cn(\n 'text-base',\n isSent ? 'text-primary-foreground' : 'text-foreground',\n )}\n >\n {textContent}\n </MarkdownRenderer>\n </AIInteractionProvider>\n ) : textContent ? (\n <p\n className={cn(\n 'text-sm leading-5 whitespace-pre-wrap',\n messageOverflowWrapClasses,\n isSent ? 'text-primary-foreground' : 'text-foreground',\n )}\n >\n {textContent}\n </p>\n ) : (\n children\n )}\n </div>\n );\n}\n\nAIMessageContent.displayName = 'AIMessage.Content';\n\n// =============================================================================\n// AIMessage.Header - Header with name and timestamp\n// =============================================================================\n\nexport interface AIMessageHeaderProps {\n /** Sender name */\n senderName?: string;\n /** Timestamp string */\n timestamp?: string;\n /** Show avatar slot */\n avatar?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageHeader({\n senderName,\n timestamp,\n avatar,\n className,\n}: AIMessageHeaderProps) {\n const ctx = React.useContext(AIMessageContext);\n const isSent = ctx?.type === 'sent';\n const compact = ctx?.compact ?? false;\n\n if (compact) return null;\n\n return (\n <div\n data-slot=\"ai-message-header\"\n className={cn('flex items-center justify-between w-full', className)}\n >\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n {avatar && <div className=\"shrink-0\">{avatar}</div>}\n <span\n className={cn(\n 'text-sm font-medium truncate',\n isSent ? 'text-primary-foreground' : 'text-secondary-foreground',\n )}\n >\n {isSent ? (senderName ?? 'You') : senderName}\n </span>\n </div>\n {timestamp && (\n <span\n className={cn(\n 'text-xs shrink-0',\n isSent ? 'text-primary-foreground/90' : 'text-muted-foreground',\n )}\n >\n {timestamp}\n </span>\n )}\n </div>\n );\n}\n\nAIMessageHeader.displayName = 'AIMessage.Header';\n\n// =============================================================================\n// AIMessage.Root - Root container for composable API\n// =============================================================================\n\nexport interface AIMessageRootProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Message type */\n type?: 'received' | 'sent';\n /** Whether sender is an AI agent */\n isAgent?: boolean;\n /** Whether in compact mode */\n compact?: boolean;\n /** Whether AI is typing */\n isTyping?: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n /** Children */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nconst AIMessageRoot = forwardRef<HTMLDivElement, AIMessageRootProps>(\n (\n {\n type = 'received',\n isAgent = false,\n compact = false,\n isTyping = false,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const isSent = type === 'sent';\n\n const contextValue = React.useMemo<AIMessageContextValue>(\n () => ({\n type,\n isAgent,\n compact,\n isTyping,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n }),\n [\n type,\n isAgent,\n compact,\n isTyping,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n ],\n );\n\n return (\n <AIMessageContext.Provider value={contextValue}>\n <div\n ref={ref}\n data-slot=\"ai-message\"\n className={cn(\n messageVariants({ type }),\n isSent ? 'pe-2' : 'ps-0',\n isSent ? 'delight-slide-in-end' : 'delight-slide-up',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'flex min-w-0 max-w-full flex-col gap-1.5 items-start',\n isSent && 'items-end',\n )}\n >\n {children}\n </div>\n </div>\n </AIMessageContext.Provider>\n );\n },\n);\n\nAIMessageRoot.displayName = 'AIMessage.Root';\n\n// =============================================================================\n// Main AIMessage Component (Monolithic API)\n// =============================================================================\n\nexport interface AIMessageProps\n extends VariantProps<typeof messageVariants>,\n Omit<HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Message type - 'received' for incoming, 'sent' for outgoing messages */\n type?: 'received' | 'sent';\n /** Message content */\n content?: string;\n /** Sender name */\n senderName?: string;\n /** Timestamp */\n timestamp?: string;\n /** Avatar — pass AvatarProps object or a React element */\n avatar?: AvatarProps | React.ReactElement;\n /** Whether the sender is an AI agent */\n isAgent?: boolean;\n /** Whether message is in compact mode (no avatar on subsequent messages) */\n compact?: boolean;\n /** Whether the AI is currently typing */\n isTyping?: boolean;\n /** Whether to render content as markdown. Defaults to true for received, false for sent. */\n markdown?: boolean;\n /** Callback when an interactive component triggers an interaction */\n onInteraction?: (event: AIInteractionEvent) => void;\n /** Attachments */\n attachments?: Attachment[];\n /** Emoji reactions */\n reactions?: EmojiReaction[];\n /** Whether to show action toolbar (for agent messages) */\n showActions?: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Callback when emoji reaction is clicked */\n onReactionClick?: (emoji: string) => void;\n /** Callback when attachment is clicked */\n onAttachmentClick?: (attachment: Attachment) => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nconst AIMessageDefault = forwardRef<HTMLDivElement, AIMessageProps>(\n (\n {\n type = 'received',\n content,\n senderName,\n timestamp,\n avatar,\n isAgent = false,\n compact = false,\n isTyping = false,\n markdown,\n onInteraction,\n attachments,\n reactions,\n showActions = false,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n onReactionClick,\n onAttachmentClick,\n thumbsUpActive,\n thumbsDownActive,\n className,\n ...props\n },\n ref,\n ) => {\n return (\n <AIMessageRoot\n ref={ref}\n type={type}\n isAgent={isAgent}\n compact={compact}\n isTyping={isTyping}\n onThumbsUp={onThumbsUp}\n onThumbsDown={onThumbsDown}\n onCopy={onCopy}\n onShowList={onShowList}\n thumbsUpActive={thumbsUpActive}\n thumbsDownActive={thumbsDownActive}\n className={className}\n {...props}\n >\n {type !== 'sent' &&\n !compact &&\n avatar &&\n (React.isValidElement(avatar) ? (\n avatar\n ) : (\n <AIMessageAvatar {...(avatar as AvatarProps)} isAgent={isAgent} />\n ))}\n\n <AIMessageBubble>\n {(type !== 'sent' || senderName || timestamp) && (\n <AIMessageHeader senderName={senderName} timestamp={timestamp} />\n )}\n\n {isTyping ? (\n <AIMessageTypingIndicator />\n ) : (\n content && (\n <AIMessageContent\n content={content}\n markdown={markdown}\n onInteraction={onInteraction}\n />\n )\n )}\n </AIMessageBubble>\n\n {!isTyping && attachments && attachments.length > 0 && (\n <AIMessageAttachments\n attachments={attachments}\n messageType={type ?? 'received'}\n onAttachmentClick={onAttachmentClick}\n />\n )}\n\n {!isTyping && (showActions || reactions?.length) && (\n <div className=\"flex items-center gap-1\">\n {showActions && isAgent && <AIMessageActions />}\n {reactions && reactions.length > 0 && (\n <AIMessageReactions\n reactions={reactions}\n onReactionClick={onReactionClick}\n />\n )}\n </div>\n )}\n </AIMessageRoot>\n );\n },\n);\n\nAIMessageDefault.displayName = 'AIMessage';\n\n// =============================================================================\n// Compound Component (Dual-API via Object.assign)\n// =============================================================================\n\n/**\n * AIMessage - Dual-API component for AI chat messages.\n *\n * Supports two usage patterns:\n *\n * 1. **Monolithic API (batteries-included)**:\n * ```tsx\n * <AIMessage\n * content=\"Hello!\"\n * senderName=\"Assistant\"\n * avatar={{ src: '...' }}\n * isAgent\n * showActions\n * />\n * ```\n *\n * 2. **Composable API (full customization)**:\n * ```tsx\n * <AIMessage.Root type=\"received\" isAgent>\n * <AIMessage.Avatar src=\"...\" isAgent />\n * <AIMessage.Bubble>\n * <AIMessage.Header senderName=\"Assistant\" timestamp=\"Now\" />\n * <AIMessage.Content markdown>\n * Hello!\n * </AIMessage.Content>\n * </AIMessage.Bubble>\n * <AIMessage.Actions>\n * <AIMessage.ThumbsUp onClick={...} />\n * <AIMessage.ThumbsDown onClick={...} />\n * <AIMessage.CopyButton onClick={...} />\n * </AIMessage.Actions>\n * </AIMessage.Root>\n * ```\n */\nconst AIMessage = Object.assign(AIMessageDefault, {\n Root: AIMessageRoot,\n Bubble: AIMessageBubble,\n Header: AIMessageHeader,\n Avatar: AIMessageAvatar,\n Content: AIMessageContent,\n Actions: AIMessageActions,\n Reactions: AIMessageReactions,\n Attachments: AIMessageAttachments,\n AttachmentPreview: AIMessageAttachmentPreview,\n TypingIndicator: AIMessageTypingIndicator,\n CopyButton: AIMessageCopyButton,\n ThumbsUp: AIMessageThumbsUp,\n ThumbsDown: AIMessageThumbsDown,\n ListButton: AIMessageListButton,\n});\n\n// =============================================================================\n// Named Exports\n// =============================================================================\n\n// Type exports\nexport type { AIMessageRootProps as AIMessageComposableProps };\nexport {\n AIMessage,\n AIMessageActions,\n AIMessageAttachmentPreview,\n AIMessageAttachments,\n AIMessageAvatar,\n AIMessageBubble,\n AIMessageContent,\n AIMessageCopyButton,\n AIMessageHeader,\n AIMessageListButton,\n AIMessageReactions,\n // Composable subcomponents\n AIMessageRoot,\n AIMessageThumbsDown,\n AIMessageThumbsUp,\n AIMessageTypingIndicator,\n // Variants\n messageBubbleVariants,\n messageVariants,\n // Context hook for custom subcomponents\n useAIMessageContext,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index154.js","names":[],"sources":["../src/components/organisms/ai/AIMessage/AIMessage.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Check, Copy, List, ThumbsDown, ThumbsUp } from 'lucide-react';\nimport * as React from 'react';\nimport { forwardRef, type HTMLAttributes } from 'react';\nimport { Avatar, type AvatarProps } from '@/components/molecules/data/Avatar';\nimport { FileTypeIcon } from '@/components/molecules/files/FileTypeIcon';\nimport type { AIInteractionEvent } from '@/components/organisms/ai/_shared/interaction-context';\nimport { AIInteractionProvider } from '@/components/organisms/ai/_shared/interaction-context';\nimport { MarkdownRenderer } from '@/components/organisms/ai/_shared/markdown-renderer';\nimport { AITypingIndicator } from '@/components/organisms/ai/AITypingIndicator';\nimport { createSafeContext } from '@/lib/create-safe-context';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Message Variants\n// =============================================================================\n\nconst messageVariants = cva('flex items-start w-full', {\n variants: {\n type: {\n received: 'justify-start',\n sent: 'justify-end',\n },\n },\n defaultVariants: {\n type: 'received',\n },\n});\n\nconst messageOverflowWrapClasses =\n 'min-w-0 max-w-full break-words [overflow-wrap:anywhere]';\n\nconst messageBubbleVariants = cva(\n 'flex min-w-0 max-w-[min(720px,100%)] flex-col gap-1 overflow-hidden px-4 py-3 rounded-2xl',\n {\n variants: {\n type: {\n received: 'bg-muted rounded-es-sm',\n sent: 'bg-primary text-primary-foreground rounded-ee-sm',\n },\n },\n defaultVariants: {\n type: 'received',\n },\n },\n);\n\n// =============================================================================\n// Context for Composable API\n// =============================================================================\n\ninterface AIMessageContextValue {\n /** Message type */\n type: 'received' | 'sent';\n /** Whether sender is an AI agent */\n isAgent: boolean;\n /** Whether in compact mode */\n compact: boolean;\n /** Whether AI is typing */\n isTyping: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n}\n\nconst [AIMessageContext, useAIMessageContext] =\n createSafeContext<AIMessageContextValue>('AIMessage');\n\n// =============================================================================\n// AIMessage.Bubble - Bubble wrapper for header + content\n// =============================================================================\n\nexport interface AIMessageBubbleProps {\n /** Children (Header, Content, TypingIndicator) */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageBubble({ children, className }: AIMessageBubbleProps) {\n const ctx = React.useContext(AIMessageContext);\n const type = ctx?.type ?? 'received';\n\n return (\n <div\n data-slot=\"ai-message-bubble\"\n data-message-type={type}\n className={cn(messageBubbleVariants({ type }), className)}\n >\n {children}\n </div>\n );\n}\n\nAIMessageBubble.displayName = 'AIMessage.Bubble';\n\n// =============================================================================\n// Reaction Button (Internal)\n// =============================================================================\n\ninterface ReactionButtonProps {\n icon: React.ReactNode;\n onClick?: () => void;\n active?: boolean;\n count?: number;\n /** Accessible label for the button */\n label?: string;\n className?: string;\n}\n\nfunction ReactionButton({\n icon,\n onClick,\n active,\n count,\n label,\n className,\n}: ReactionButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n aria-pressed={active}\n className={cn(\n 'flex items-center justify-center gap-1 px-2.5 py-2 min-w-11 min-h-11 rounded-full bg-muted hover:bg-accent delight-click-ripple transition-all duration-150 ease-out hover:scale-110 active:scale-95 motion-reduce:hover:scale-100 motion-reduce:active:scale-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n active &&\n 'bg-primary/10 text-primary delight-check-bounce motion-reduce:!animate-none',\n className,\n )}\n >\n {icon}\n {count !== undefined && count > 0 && (\n <span className=\"text-xs font-medium text-muted-foreground\">\n {count}\n </span>\n )}\n </button>\n );\n}\n\n// =============================================================================\n// Emoji Reaction Types\n// =============================================================================\n\nexport interface EmojiReaction {\n emoji: string;\n count: number;\n reacted?: boolean;\n}\n\n// =============================================================================\n// AIMessage.Reactions - Emoji reactions display\n// =============================================================================\n\nexport interface AIMessageReactionsProps {\n /** Emoji reactions to display */\n reactions: EmojiReaction[];\n /** Callback when reaction is clicked */\n onReactionClick?: (emoji: string) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageReactions({\n reactions,\n onReactionClick,\n className,\n}: AIMessageReactionsProps) {\n if (!reactions?.length) return null;\n\n return (\n <div\n data-slot=\"ai-message-reactions\"\n className={cn('flex items-center gap-1', className)}\n >\n {reactions.map((reaction) => (\n <button\n key={reaction.emoji}\n type=\"button\"\n onClick={() => onReactionClick?.(reaction.emoji)}\n aria-label={`React with ${reaction.emoji}${reaction.count > 1 ? `, ${reaction.count} reactions` : ''}`}\n aria-pressed={reaction.reacted}\n className={cn(\n 'flex items-center gap-1 px-2.5 py-1.5 min-h-11 rounded-full bg-muted hover:bg-accent text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n reaction.reacted && 'bg-primary/10',\n )}\n >\n <span aria-hidden=\"true\">{reaction.emoji}</span>\n {reaction.count > 1 && (\n <span className=\"text-xs text-muted-foreground\">\n {reaction.count}\n </span>\n )}\n </button>\n ))}\n </div>\n );\n}\n\nAIMessageReactions.displayName = 'AIMessage.Reactions';\n\n// =============================================================================\n// AIMessage.ThumbsUp - Thumbs up button\n// =============================================================================\n\nexport interface AIMessageThumbsUpProps {\n /** Click handler */\n onClick?: () => void;\n /** Whether active */\n active?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageThumbsUp({\n onClick,\n active,\n className,\n}: AIMessageThumbsUpProps) {\n return (\n <ReactionButton\n icon={\n <ThumbsUp className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n }\n onClick={onClick}\n active={active}\n label=\"Thumbs up\"\n className={className}\n />\n );\n}\n\nAIMessageThumbsUp.displayName = 'AIMessage.ThumbsUp';\n\n// =============================================================================\n// AIMessage.ThumbsDown - Thumbs down button\n// =============================================================================\n\nexport interface AIMessageThumbsDownProps {\n /** Click handler */\n onClick?: () => void;\n /** Whether active */\n active?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageThumbsDown({\n onClick,\n active,\n className,\n}: AIMessageThumbsDownProps) {\n return (\n <ReactionButton\n icon={\n <ThumbsDown\n className=\"size-4 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n }\n onClick={onClick}\n active={active}\n label=\"Thumbs down\"\n className={className}\n />\n );\n}\n\nAIMessageThumbsDown.displayName = 'AIMessage.ThumbsDown';\n\n// =============================================================================\n// AIMessage.CopyButton - Copy button\n// =============================================================================\n\nexport interface AIMessageCopyButtonProps {\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageCopyButton({ onClick, className }: AIMessageCopyButtonProps) {\n const [copied, setCopied] = React.useState(false);\n const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);\n\n React.useEffect(() => () => clearTimeout(timerRef.current), []);\n\n const handleClick = React.useCallback(() => {\n onClick?.();\n setCopied(true);\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setCopied(false), 2000);\n }, [onClick]);\n\n return (\n <ReactionButton\n icon={\n copied ? (\n <Check\n className=\"size-4 text-success delight-bounce-in motion-reduce:animate-none\"\n aria-hidden=\"true\"\n />\n ) : (\n <Copy\n className=\"size-4 text-secondary-foreground\"\n aria-hidden=\"true\"\n />\n )\n }\n onClick={handleClick}\n label={copied ? 'Copied to clipboard' : 'Copy message'}\n className={className}\n />\n );\n}\n\nAIMessageCopyButton.displayName = 'AIMessage.CopyButton';\n\n// =============================================================================\n// AIMessage.ListButton - List/expand button\n// =============================================================================\n\nexport interface AIMessageListButtonProps {\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageListButton({ onClick, className }: AIMessageListButtonProps) {\n return (\n <ReactionButton\n icon={\n <List className=\"size-4 text-secondary-foreground\" aria-hidden=\"true\" />\n }\n onClick={onClick}\n label=\"Show list\"\n className={className}\n />\n );\n}\n\nAIMessageListButton.displayName = 'AIMessage.ListButton';\n\n// =============================================================================\n// AIMessage.Actions - Action toolbar container\n// =============================================================================\n\nexport interface AIMessageActionsProps {\n /** Children (action buttons) */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageActions({ children, className }: AIMessageActionsProps) {\n const ctx = React.useContext(AIMessageContext);\n\n return (\n <div\n data-slot=\"ai-message-actions\"\n role=\"toolbar\"\n aria-label=\"Message actions\"\n className={cn(\n 'flex items-center gap-0.5 rounded-full overflow-hidden',\n className,\n )}\n >\n {children ??\n (ctx && (\n <>\n <AIMessageThumbsUp\n onClick={ctx.onThumbsUp}\n active={ctx.thumbsUpActive}\n />\n <AIMessageThumbsDown\n onClick={ctx.onThumbsDown}\n active={ctx.thumbsDownActive}\n />\n <AIMessageCopyButton onClick={ctx.onCopy} />\n <AIMessageListButton onClick={ctx.onShowList} />\n </>\n ))}\n </div>\n );\n}\n\nAIMessageActions.displayName = 'AIMessage.Actions';\n\n// =============================================================================\n// AIMessage.TypingIndicator - Bouncing dots\n// =============================================================================\n\nexport interface AIMessageTypingIndicatorProps {\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageTypingIndicator({\n className,\n}: AIMessageTypingIndicatorProps) {\n return (\n <div\n data-slot=\"ai-message-typing-indicator\"\n className={cn(\n 'flex items-center gap-1 motion-reduce:animate-none',\n className,\n )}\n >\n <AITypingIndicator size=\"sm\" color=\"var(--muted-foreground)\" />\n </div>\n );\n}\n\nAIMessageTypingIndicator.displayName = 'AIMessage.TypingIndicator';\n\n// =============================================================================\n// Attachment Types\n// =============================================================================\n\nexport interface Attachment {\n id: string;\n name: string;\n size: string;\n /** File extension (e.g., 'pdf', 'jpg', 'doc') */\n extension?: string;\n url?: string;\n thumbnailUrl?: string;\n}\n\n// =============================================================================\n// AIMessage.AttachmentPreview - Single attachment preview\n// =============================================================================\n\nexport interface AIMessageAttachmentPreviewProps {\n /** Attachment data */\n attachment: Attachment;\n /** Message type for styling */\n messageType?: 'received' | 'sent';\n /** Click handler */\n onClick?: () => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageAttachmentPreview({\n attachment,\n messageType = 'received',\n onClick,\n className,\n}: AIMessageAttachmentPreviewProps) {\n // Derive extension from attachment.extension or extract from filename\n const getExtension = () => {\n if (attachment.extension) {\n return attachment.extension;\n }\n const parts = attachment.name.split('.');\n if (parts.length > 1) {\n return parts[parts.length - 1];\n }\n return '';\n };\n\n const extension = getExtension();\n\n const bubbleClasses =\n messageType === 'sent'\n ? 'bg-primary/10 border border-primary-foreground/20 rounded-2xl'\n : 'bg-muted rounded-2xl';\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-message-attachment-preview\"\n onClick={onClick}\n aria-label={`Attachment: ${attachment.name}, ${attachment.size}`}\n className={cn(\n 'flex items-center gap-3 p-3 min-h-11 hover:bg-accent transition-colors w-full max-w-[320px] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring',\n bubbleClasses,\n className,\n )}\n >\n {attachment.thumbnailUrl ? (\n <img\n src={attachment.thumbnailUrl}\n alt={attachment.name}\n className=\"size-10 rounded-md object-cover\"\n />\n ) : (\n <FileTypeIcon extension={extension} size=\"md\" className=\"shrink-0\" />\n )}\n <div className=\"flex flex-col items-start min-w-0\">\n <span className=\"text-sm font-medium text-secondary-foreground truncate w-full\">\n {attachment.name}\n </span>\n <span className=\"text-sm text-muted-foreground\">{attachment.size}</span>\n </div>\n </button>\n );\n}\n\nAIMessageAttachmentPreview.displayName = 'AIMessage.AttachmentPreview';\n\n// =============================================================================\n// AIMessage.Attachments - Attachments container\n// =============================================================================\n\nexport interface AIMessageAttachmentsProps {\n /** Attachments to display */\n attachments: Attachment[];\n /** Message type for styling */\n messageType?: 'received' | 'sent';\n /** Callback when attachment is clicked */\n onAttachmentClick?: (attachment: Attachment) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageAttachments({\n attachments,\n messageType = 'received',\n onAttachmentClick,\n className,\n}: AIMessageAttachmentsProps) {\n if (!attachments?.length) return null;\n\n return (\n <div\n data-slot=\"ai-message-attachments\"\n className={cn('flex flex-col gap-2 mt-1', className)}\n >\n {attachments.map((attachment) => (\n <AIMessageAttachmentPreview\n key={attachment.id}\n attachment={attachment}\n messageType={messageType}\n onClick={() => onAttachmentClick?.(attachment)}\n />\n ))}\n </div>\n );\n}\n\nAIMessageAttachments.displayName = 'AIMessage.Attachments';\n\n// =============================================================================\n// AIMessage.Avatar - Avatar with agent indicator\n// =============================================================================\n\nexport interface AIMessageAvatarProps extends AvatarProps {\n /** Whether sender is an agent */\n isAgent?: boolean;\n}\n\nfunction AIMessageAvatar({ isAgent, ...props }: AIMessageAvatarProps) {\n return (\n <Avatar\n data-slot=\"ai-message-avatar\"\n {...props}\n size=\"md\"\n indicator={isAgent ? 'verified' : props.indicator}\n />\n );\n}\n\nAIMessageAvatar.displayName = 'AIMessage.Avatar';\n\n// =============================================================================\n// AIMessage.Content - Message content with markdown support\n// =============================================================================\n\nexport interface AIMessageContentProps {\n /** Message content */\n children?: React.ReactNode;\n /** Content as string (alternative to children) */\n content?: string;\n /** Whether to render content as markdown. Defaults to true for received, false for sent. */\n markdown?: boolean;\n /** Callback for interactive component events */\n onInteraction?: (event: AIInteractionEvent) => void;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageContent({\n children,\n content,\n markdown,\n onInteraction,\n className,\n}: AIMessageContentProps) {\n const ctx = React.useContext(AIMessageContext);\n const type = ctx?.type ?? 'received';\n const isSent = type === 'sent';\n\n const shouldRenderMarkdown = markdown ?? type === 'received';\n const textContent = content ?? (typeof children === 'string' ? children : '');\n const variant = isSent ? 'inverted' : 'default';\n\n return (\n <div\n data-slot=\"ai-message-content\"\n className={cn('w-full', messageOverflowWrapClasses, className)}\n >\n {shouldRenderMarkdown && textContent ? (\n <AIInteractionProvider onInteraction={onInteraction}>\n <MarkdownRenderer\n variant={variant}\n className={cn(\n 'text-base',\n isSent ? 'text-primary-foreground' : 'text-foreground',\n )}\n >\n {textContent}\n </MarkdownRenderer>\n </AIInteractionProvider>\n ) : textContent ? (\n <p\n className={cn(\n 'text-sm leading-5 whitespace-pre-wrap',\n messageOverflowWrapClasses,\n isSent ? 'text-primary-foreground' : 'text-foreground',\n )}\n >\n {textContent}\n </p>\n ) : (\n children\n )}\n </div>\n );\n}\n\nAIMessageContent.displayName = 'AIMessage.Content';\n\n// =============================================================================\n// AIMessage.Header - Header with name and timestamp\n// =============================================================================\n\nexport interface AIMessageHeaderProps {\n /** Sender name */\n senderName?: string;\n /** Timestamp string */\n timestamp?: string;\n /** Show avatar slot */\n avatar?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIMessageHeader({\n senderName,\n timestamp,\n avatar,\n className,\n}: AIMessageHeaderProps) {\n const ctx = React.useContext(AIMessageContext);\n const isSent = ctx?.type === 'sent';\n const compact = ctx?.compact ?? false;\n\n if (compact) return null;\n\n return (\n <div\n data-slot=\"ai-message-header\"\n className={cn('flex items-center justify-between w-full', className)}\n >\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n {avatar && <div className=\"shrink-0\">{avatar}</div>}\n <span\n className={cn(\n 'text-sm font-medium truncate',\n isSent ? 'text-primary-foreground' : 'text-secondary-foreground',\n )}\n >\n {isSent ? (senderName ?? 'You') : senderName}\n </span>\n </div>\n {timestamp && (\n <span\n className={cn(\n 'text-xs shrink-0',\n isSent ? 'text-primary-foreground/90' : 'text-muted-foreground',\n )}\n >\n {timestamp}\n </span>\n )}\n </div>\n );\n}\n\nAIMessageHeader.displayName = 'AIMessage.Header';\n\n// =============================================================================\n// AIMessage.Root - Root container for composable API\n// =============================================================================\n\nexport interface AIMessageRootProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Message type */\n type?: 'received' | 'sent';\n /** Whether sender is an AI agent */\n isAgent?: boolean;\n /** Whether in compact mode */\n compact?: boolean;\n /** Whether AI is typing */\n isTyping?: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n /** Children */\n children?: React.ReactNode;\n /** Additional class name */\n className?: string;\n}\n\nconst AIMessageRoot = forwardRef<HTMLDivElement, AIMessageRootProps>(\n (\n {\n type = 'received',\n isAgent = false,\n compact = false,\n isTyping = false,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const isSent = type === 'sent';\n\n const contextValue = React.useMemo<AIMessageContextValue>(\n () => ({\n type,\n isAgent,\n compact,\n isTyping,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n }),\n [\n type,\n isAgent,\n compact,\n isTyping,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n thumbsUpActive,\n thumbsDownActive,\n ],\n );\n\n return (\n <AIMessageContext.Provider value={contextValue}>\n <div\n ref={ref}\n data-slot=\"ai-message\"\n className={cn(\n messageVariants({ type }),\n isSent ? 'pe-2' : 'ps-0',\n isSent ? 'delight-slide-in-end' : 'delight-slide-up',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'flex min-w-0 max-w-full flex-col gap-1.5 items-start',\n isSent && 'items-end',\n )}\n >\n {children}\n </div>\n </div>\n </AIMessageContext.Provider>\n );\n },\n);\n\nAIMessageRoot.displayName = 'AIMessage.Root';\n\n// =============================================================================\n// Main AIMessage Component (Monolithic API)\n// =============================================================================\n\nexport interface AIMessageProps\n extends VariantProps<typeof messageVariants>,\n Omit<HTMLAttributes<HTMLDivElement>, 'content'> {\n /** Message type - 'received' for incoming, 'sent' for outgoing messages */\n type?: 'received' | 'sent';\n /** Message content */\n content?: string;\n /** Sender name */\n senderName?: string;\n /** Timestamp */\n timestamp?: string;\n /** Avatar — pass AvatarProps object or a React element */\n avatar?: AvatarProps | React.ReactElement;\n /** Whether the sender is an AI agent */\n isAgent?: boolean;\n /** Whether message is in compact mode (no avatar on subsequent messages) */\n compact?: boolean;\n /** Whether the AI is currently typing */\n isTyping?: boolean;\n /** Whether to render content as markdown. Defaults to true for received, false for sent. */\n markdown?: boolean;\n /** Callback when an interactive component triggers an interaction */\n onInteraction?: (event: AIInteractionEvent) => void;\n /** Attachments */\n attachments?: Attachment[];\n /** Emoji reactions */\n reactions?: EmojiReaction[];\n /** Whether to show action toolbar (for agent messages) */\n showActions?: boolean;\n /** Callback when thumbs up is clicked */\n onThumbsUp?: () => void;\n /** Callback when thumbs down is clicked */\n onThumbsDown?: () => void;\n /** Callback when copy is clicked */\n onCopy?: () => void;\n /** Callback when list/expand is clicked */\n onShowList?: () => void;\n /** Callback when emoji reaction is clicked */\n onReactionClick?: (emoji: string) => void;\n /** Callback when attachment is clicked */\n onAttachmentClick?: (attachment: Attachment) => void;\n /** Whether thumbs up is active */\n thumbsUpActive?: boolean;\n /** Whether thumbs down is active */\n thumbsDownActive?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nconst AIMessageDefault = forwardRef<HTMLDivElement, AIMessageProps>(\n (\n {\n type = 'received',\n content,\n senderName,\n timestamp,\n avatar,\n isAgent = false,\n compact = false,\n isTyping = false,\n markdown,\n onInteraction,\n attachments,\n reactions,\n showActions = false,\n onThumbsUp,\n onThumbsDown,\n onCopy,\n onShowList,\n onReactionClick,\n onAttachmentClick,\n thumbsUpActive,\n thumbsDownActive,\n className,\n ...props\n },\n ref,\n ) => {\n return (\n <AIMessageRoot\n ref={ref}\n type={type}\n isAgent={isAgent}\n compact={compact}\n isTyping={isTyping}\n onThumbsUp={onThumbsUp}\n onThumbsDown={onThumbsDown}\n onCopy={onCopy}\n onShowList={onShowList}\n thumbsUpActive={thumbsUpActive}\n thumbsDownActive={thumbsDownActive}\n className={className}\n {...props}\n >\n {type !== 'sent' &&\n !compact &&\n avatar &&\n (React.isValidElement(avatar) ? (\n avatar\n ) : (\n <AIMessageAvatar {...(avatar as AvatarProps)} isAgent={isAgent} />\n ))}\n\n <AIMessageBubble>\n {(type !== 'sent' || senderName || timestamp) && (\n <AIMessageHeader senderName={senderName} timestamp={timestamp} />\n )}\n\n {isTyping ? (\n <AIMessageTypingIndicator />\n ) : (\n content && (\n <AIMessageContent\n content={content}\n markdown={markdown}\n onInteraction={onInteraction}\n />\n )\n )}\n </AIMessageBubble>\n\n {!isTyping && attachments && attachments.length > 0 && (\n <AIMessageAttachments\n attachments={attachments}\n messageType={type ?? 'received'}\n onAttachmentClick={onAttachmentClick}\n />\n )}\n\n {!isTyping && (showActions || reactions?.length) && (\n <div className=\"flex items-center gap-1\">\n {showActions && isAgent && <AIMessageActions />}\n {reactions && reactions.length > 0 && (\n <AIMessageReactions\n reactions={reactions}\n onReactionClick={onReactionClick}\n />\n )}\n </div>\n )}\n </AIMessageRoot>\n );\n },\n);\n\nAIMessageDefault.displayName = 'AIMessage';\n\n// =============================================================================\n// Compound Component (Dual-API via Object.assign)\n// =============================================================================\n\n/**\n * AIMessage - Dual-API component for AI chat messages.\n *\n * Supports two usage patterns:\n *\n * 1. **Monolithic API (batteries-included)**:\n * ```tsx\n * <AIMessage\n * content=\"Hello!\"\n * senderName=\"Assistant\"\n * avatar={{ src: '...' }}\n * isAgent\n * showActions\n * />\n * ```\n *\n * 2. **Composable API (full customization)**:\n * ```tsx\n * <AIMessage.Root type=\"received\" isAgent>\n * <AIMessage.Avatar src=\"...\" isAgent />\n * <AIMessage.Bubble>\n * <AIMessage.Header senderName=\"Assistant\" timestamp=\"Now\" />\n * <AIMessage.Content markdown>\n * Hello!\n * </AIMessage.Content>\n * </AIMessage.Bubble>\n * <AIMessage.Actions>\n * <AIMessage.ThumbsUp onClick={...} />\n * <AIMessage.ThumbsDown onClick={...} />\n * <AIMessage.CopyButton onClick={...} />\n * </AIMessage.Actions>\n * </AIMessage.Root>\n * ```\n */\nconst AIMessage = Object.assign(AIMessageDefault, {\n Root: AIMessageRoot,\n Bubble: AIMessageBubble,\n Header: AIMessageHeader,\n Avatar: AIMessageAvatar,\n Content: AIMessageContent,\n Actions: AIMessageActions,\n Reactions: AIMessageReactions,\n Attachments: AIMessageAttachments,\n AttachmentPreview: AIMessageAttachmentPreview,\n TypingIndicator: AIMessageTypingIndicator,\n CopyButton: AIMessageCopyButton,\n ThumbsUp: AIMessageThumbsUp,\n ThumbsDown: AIMessageThumbsDown,\n ListButton: AIMessageListButton,\n});\n\n// =============================================================================\n// Named Exports\n// =============================================================================\n\n// Type exports\nexport type { AIMessageRootProps as AIMessageComposableProps };\nexport {\n AIMessage,\n AIMessageActions,\n AIMessageAttachmentPreview,\n AIMessageAttachments,\n AIMessageAvatar,\n AIMessageBubble,\n AIMessageContent,\n AIMessageCopyButton,\n AIMessageHeader,\n AIMessageListButton,\n AIMessageReactions,\n // Composable subcomponents\n AIMessageRoot,\n AIMessageThumbsDown,\n AIMessageThumbsUp,\n AIMessageTypingIndicator,\n // Variants\n messageBubbleVariants,\n messageVariants,\n // Context hook for custom subcomponents\n useAIMessageContext,\n};\n"],"mappings":"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"}
|
package/dist/index166.js
CHANGED
|
@@ -323,7 +323,7 @@ function AIMermaidDiagram({ code, theme = "auto", caption, interactive = false,
|
|
|
323
323
|
})
|
|
324
324
|
}),
|
|
325
325
|
/* @__PURE__ */ jsxs("span", {
|
|
326
|
-
className: "text-xs text-
|
|
326
|
+
className: "text-xs text-secondary-foreground px-1 tabular-nums",
|
|
327
327
|
children: [Math.round(transform.scale * 100), "%"]
|
|
328
328
|
})
|
|
329
329
|
]
|
package/dist/index166.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index166.js","names":[],"sources":["../src/components/organisms/ai/AIMermaidDiagram/AIMermaidDiagram.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Skeleton } from '@/components/atoms/display/Skeleton';\nimport { AICodeBlock } from '@/components/organisms/ai/AICodeBlock';\nimport { cn } from '@/lib/utils';\nimport { getMermaidConfig } from './mermaid-theme';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AIMermaidDiagramProps {\n /** Mermaid diagram definition */\n code: string;\n /** Theme - follows design system */\n theme?: 'light' | 'dark' | 'auto';\n /** Optional caption below diagram */\n caption?: string;\n /** Allow pan/zoom for complex diagrams */\n interactive?: boolean;\n /** Max height before scrolling */\n maxHeight?: number;\n /** Additional class name */\n className?: string;\n}\n\nfunction getFiniteMaxHeight(maxHeight: number | undefined): number | undefined {\n return typeof maxHeight === 'number' &&\n Number.isFinite(maxHeight) &&\n maxHeight >= 0\n ? maxHeight\n : undefined;\n}\n\ntype DiagramTransform = {\n x: number;\n y: number;\n scale: number;\n};\n\ntype DiagramTransformState = {\n code: string;\n transform: DiagramTransform;\n};\n\nconst DEFAULT_TRANSFORM: DiagramTransform = { x: 0, y: 0, scale: 1 };\n\nfunction svgToDataUri(svg: string): string {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n}\n\n// =============================================================================\n// Loading State\n// =============================================================================\n\nfunction DiagramSkeleton({ maxHeight }: { maxHeight?: number }) {\n return (\n <div\n className=\"flex items-center justify-center p-8 rounded-xl border border-border bg-muted\"\n style={{ minHeight: 200, maxHeight }}\n >\n <div className=\"flex flex-col items-center gap-4 w-full max-w-md\">\n <Skeleton className=\"h-4 w-3/4\" />\n <div className=\"flex items-center justify-center gap-4 w-full\">\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n <Skeleton className=\"h-1 w-8\" />\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n <Skeleton className=\"h-1 w-8\" />\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n </div>\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n </div>\n );\n}\n\n// =============================================================================\n// Error State\n// =============================================================================\n\ninterface DiagramErrorProps {\n code: string;\n error: string;\n}\n\nfunction DiagramError({ code, error }: DiagramErrorProps) {\n return (\n <div className=\"space-y-2\">\n <div\n className=\"flex items-center gap-2 px-3 py-2 rounded-lg bg-destructive/20 text-destructive text-sm\"\n role=\"alert\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4 flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"font-medium\">Diagram parse error:</span>\n <span className=\"truncate\">{error}</span>\n </div>\n <AICodeBlock code={code} language=\"mermaid\" showLineNumbers />\n </div>\n );\n}\n\n// =============================================================================\n// Hook to detect theme\n// =============================================================================\n\nfunction getDocumentTheme(\n ownerDocument: Document | undefined,\n): 'light' | 'dark' {\n return ownerDocument?.documentElement.classList.contains('dark')\n ? 'dark'\n : 'light';\n}\n\nfunction useResolvedTheme(\n theme: 'light' | 'dark' | 'auto',\n ownerRef: React.RefObject<HTMLElement | null>,\n): 'light' | 'dark' | undefined {\n const [resolvedTheme, setResolvedTheme] = React.useState<\n 'light' | 'dark' | undefined\n >(() => (theme === 'auto' ? undefined : theme));\n\n React.useEffect(() => {\n if (theme !== 'auto') {\n setResolvedTheme(theme);\n return;\n }\n\n const ownerDocument = ownerRef.current?.ownerDocument;\n const OwnerMutationObserver = ownerDocument?.defaultView?.MutationObserver;\n if (!ownerDocument || !OwnerMutationObserver) {\n setResolvedTheme('light');\n return;\n }\n\n // Observe class changes on document element\n const observer = new OwnerMutationObserver(() => {\n setResolvedTheme(getDocumentTheme(ownerDocument));\n });\n\n observer.observe(ownerDocument.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n\n // Set initial value\n setResolvedTheme(getDocumentTheme(ownerDocument));\n\n return () => observer.disconnect();\n }, [theme, ownerRef]);\n\n return resolvedTheme;\n}\n\n// =============================================================================\n// Main Component\n// =============================================================================\n\n// Counter for unique IDs\nlet diagramIdCounter = 0;\n\nfunction AIMermaidDiagram({\n code,\n theme = 'auto',\n caption,\n interactive = false,\n maxHeight,\n className,\n}: AIMermaidDiagramProps) {\n const safeMaxHeight = getFiniteMaxHeight(maxHeight);\n const [svgContent, setSvgContent] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n const ownerRef = React.useRef<HTMLElement | null>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const resolvedTheme = useResolvedTheme(theme, ownerRef);\n const idRef = React.useRef<string>(`mermaid-diagram-${++diagramIdCounter}`);\n const setOwnerNode = React.useCallback((node: HTMLElement | null) => {\n ownerRef.current = node;\n }, []);\n\n // Pan/zoom state for interactive mode\n const [transformState, setTransformState] =\n React.useState<DiagramTransformState>(() => ({\n code,\n transform: DEFAULT_TRANSFORM,\n }));\n const [isDragging, setIsDragging] = React.useState(false);\n const dragStartRef = React.useRef({ x: 0, y: 0 });\n\n React.useEffect(() => {\n if (!resolvedTheme) {\n return;\n }\n\n const activeTheme = resolvedTheme;\n let cancelled = false;\n\n async function renderDiagram() {\n setIsLoading(true);\n setError(null);\n setSvgContent(null);\n\n try {\n // Dynamically import mermaid\n const mermaid = (await import('mermaid')).default;\n\n // Initialize with theme config\n const config = getMermaidConfig(\n activeTheme,\n ownerRef.current ?? containerRef.current,\n );\n mermaid.initialize(config);\n\n // Generate unique ID for this render\n const id = `${idRef.current}-${Date.now()}`;\n\n // Render the diagram\n // Note: mermaid.render returns sanitized SVG (securityLevel: 'strict')\n const { svg } = await mermaid.render(id, code.trim());\n\n if (!cancelled) {\n setSvgContent(svg);\n setError(null);\n }\n } catch (err) {\n if (!cancelled) {\n const errorMessage =\n err instanceof Error ? err.message : 'Failed to parse diagram';\n setError(errorMessage);\n setSvgContent(null);\n }\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n }\n }\n }\n\n renderDiagram();\n\n return () => {\n cancelled = true;\n };\n }, [code, resolvedTheme]);\n\n const transform =\n transformState.code === code ? transformState.transform : DEFAULT_TRANSFORM;\n\n const setTransformForCode = React.useCallback(\n (\n nextTransform:\n | DiagramTransform\n | ((current: DiagramTransform) => DiagramTransform),\n ) => {\n setTransformState((current) => {\n const currentTransform =\n current.code === code ? current.transform : DEFAULT_TRANSFORM;\n const transform =\n typeof nextTransform === 'function'\n ? nextTransform(currentTransform)\n : nextTransform;\n return { code, transform };\n });\n },\n [code],\n );\n\n // Interactive handlers\n const handleWheel = React.useCallback(\n (e: React.WheelEvent) => {\n if (!interactive) return;\n e.preventDefault();\n\n const delta = e.deltaY > 0 ? 0.9 : 1.1;\n const newScale = Math.min(Math.max(transform.scale * delta, 0.5), 3);\n\n setTransformForCode((prev) => ({\n ...prev,\n scale: newScale,\n }));\n },\n [interactive, setTransformForCode, transform.scale],\n );\n\n const handleMouseDown = React.useCallback(\n (e: React.MouseEvent) => {\n if (!interactive) return;\n setIsDragging(true);\n dragStartRef.current = {\n x: e.clientX - transform.x,\n y: e.clientY - transform.y,\n };\n },\n [interactive, transform.x, transform.y],\n );\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!isDragging || !interactive) return;\n setTransformForCode((prev) => ({\n ...prev,\n x: e.clientX - dragStartRef.current.x,\n y: e.clientY - dragStartRef.current.y,\n }));\n },\n [isDragging, interactive, setTransformForCode],\n );\n\n const handleMouseUp = React.useCallback(() => {\n setIsDragging(false);\n }, []);\n\n const handleReset = React.useCallback(() => {\n setTransformForCode(DEFAULT_TRANSFORM);\n }, [setTransformForCode]);\n\n // Loading state\n if (isLoading) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramSkeleton maxHeight={safeMaxHeight} />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramError code={code} error={error} />\n </div>\n );\n }\n\n // Render SVG\n if (!svgContent) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramSkeleton maxHeight={safeMaxHeight} />\n </div>\n );\n }\n const svgDataUri = svgToDataUri(svgContent);\n\n return (\n <figure\n ref={setOwnerNode}\n data-slot=\"ai-mermaid-diagram\"\n className={cn('space-y-2', className)}\n >\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Interactive mode requires mouse handlers for pan/zoom */}\n <div\n ref={containerRef}\n className={cn(\n 'relative overflow-hidden rounded-xl border border-border bg-muted',\n interactive && 'cursor-grab',\n isDragging && 'cursor-grabbing',\n )}\n style={{ maxHeight: safeMaxHeight }}\n onWheel={handleWheel}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseUp}\n >\n {/* SVG Container */}\n <div\n className={cn(\n 'flex items-center justify-center p-4',\n safeMaxHeight !== undefined && 'overflow-auto',\n )}\n {...(safeMaxHeight !== undefined\n ? { tabIndex: 0, role: 'region', 'aria-label': 'Diagram view' }\n : {})}\n style={\n interactive\n ? {\n transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`,\n transformOrigin: 'center center',\n transition: isDragging ? 'none' : 'transform 0.1s ease-out',\n }\n : undefined\n }\n >\n <img\n src={svgDataUri}\n alt=\"Mermaid diagram\"\n className=\"block max-w-full h-auto\"\n draggable={false}\n />\n </div>\n\n {/* Interactive controls */}\n {interactive && (\n <div className=\"absolute bottom-2 right-2 flex items-center gap-1 bg-card/80 supports-backdrop-filter:backdrop-blur-sm rounded-lg border border-border p-1\">\n <button\n type=\"button\"\n onClick={() =>\n setTransformForCode((prev) => ({\n ...prev,\n scale: Math.min(prev.scale * 1.2, 3),\n }))\n }\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Zoom in\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setTransformForCode((prev) => ({\n ...prev,\n scale: Math.max(prev.scale * 0.8, 0.5),\n }))\n }\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Zoom out\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M4 10a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H4.75A.75.75 0 014 10z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </button>\n <div className=\"w-px h-4 bg-border-secondary mx-0.5\" />\n <button\n type=\"button\"\n onClick={handleReset}\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Reset view\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0v2.43l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </button>\n <span className=\"text-xs text-text-quaternary px-1 tabular-nums\">\n {Math.round(transform.scale * 100)}%\n </span>\n </div>\n )}\n </div>\n\n {/* Caption */}\n {caption && (\n <figcaption className=\"text-sm text-muted-foreground text-center px-4\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n\nAIMermaidDiagram.displayName = 'AIMermaidDiagram';\n\nexport { AIMermaidDiagram };\nexport default AIMermaidDiagram;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index166.js","names":[],"sources":["../src/components/organisms/ai/AIMermaidDiagram/AIMermaidDiagram.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Skeleton } from '@/components/atoms/display/Skeleton';\nimport { AICodeBlock } from '@/components/organisms/ai/AICodeBlock';\nimport { cn } from '@/lib/utils';\nimport { getMermaidConfig } from './mermaid-theme';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface AIMermaidDiagramProps {\n /** Mermaid diagram definition */\n code: string;\n /** Theme - follows design system */\n theme?: 'light' | 'dark' | 'auto';\n /** Optional caption below diagram */\n caption?: string;\n /** Allow pan/zoom for complex diagrams */\n interactive?: boolean;\n /** Max height before scrolling */\n maxHeight?: number;\n /** Additional class name */\n className?: string;\n}\n\nfunction getFiniteMaxHeight(maxHeight: number | undefined): number | undefined {\n return typeof maxHeight === 'number' &&\n Number.isFinite(maxHeight) &&\n maxHeight >= 0\n ? maxHeight\n : undefined;\n}\n\ntype DiagramTransform = {\n x: number;\n y: number;\n scale: number;\n};\n\ntype DiagramTransformState = {\n code: string;\n transform: DiagramTransform;\n};\n\nconst DEFAULT_TRANSFORM: DiagramTransform = { x: 0, y: 0, scale: 1 };\n\nfunction svgToDataUri(svg: string): string {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n}\n\n// =============================================================================\n// Loading State\n// =============================================================================\n\nfunction DiagramSkeleton({ maxHeight }: { maxHeight?: number }) {\n return (\n <div\n className=\"flex items-center justify-center p-8 rounded-xl border border-border bg-muted\"\n style={{ minHeight: 200, maxHeight }}\n >\n <div className=\"flex flex-col items-center gap-4 w-full max-w-md\">\n <Skeleton className=\"h-4 w-3/4\" />\n <div className=\"flex items-center justify-center gap-4 w-full\">\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n <Skeleton className=\"h-1 w-8\" />\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n <Skeleton className=\"h-1 w-8\" />\n <Skeleton className=\"h-16 w-24 rounded-lg\" />\n </div>\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n </div>\n );\n}\n\n// =============================================================================\n// Error State\n// =============================================================================\n\ninterface DiagramErrorProps {\n code: string;\n error: string;\n}\n\nfunction DiagramError({ code, error }: DiagramErrorProps) {\n return (\n <div className=\"space-y-2\">\n <div\n className=\"flex items-center gap-2 px-3 py-2 rounded-lg bg-destructive/20 text-destructive text-sm\"\n role=\"alert\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4 flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\"\n clipRule=\"evenodd\"\n />\n </svg>\n <span className=\"font-medium\">Diagram parse error:</span>\n <span className=\"truncate\">{error}</span>\n </div>\n <AICodeBlock code={code} language=\"mermaid\" showLineNumbers />\n </div>\n );\n}\n\n// =============================================================================\n// Hook to detect theme\n// =============================================================================\n\nfunction getDocumentTheme(\n ownerDocument: Document | undefined,\n): 'light' | 'dark' {\n return ownerDocument?.documentElement.classList.contains('dark')\n ? 'dark'\n : 'light';\n}\n\nfunction useResolvedTheme(\n theme: 'light' | 'dark' | 'auto',\n ownerRef: React.RefObject<HTMLElement | null>,\n): 'light' | 'dark' | undefined {\n const [resolvedTheme, setResolvedTheme] = React.useState<\n 'light' | 'dark' | undefined\n >(() => (theme === 'auto' ? undefined : theme));\n\n React.useEffect(() => {\n if (theme !== 'auto') {\n setResolvedTheme(theme);\n return;\n }\n\n const ownerDocument = ownerRef.current?.ownerDocument;\n const OwnerMutationObserver = ownerDocument?.defaultView?.MutationObserver;\n if (!ownerDocument || !OwnerMutationObserver) {\n setResolvedTheme('light');\n return;\n }\n\n // Observe class changes on document element\n const observer = new OwnerMutationObserver(() => {\n setResolvedTheme(getDocumentTheme(ownerDocument));\n });\n\n observer.observe(ownerDocument.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n\n // Set initial value\n setResolvedTheme(getDocumentTheme(ownerDocument));\n\n return () => observer.disconnect();\n }, [theme, ownerRef]);\n\n return resolvedTheme;\n}\n\n// =============================================================================\n// Main Component\n// =============================================================================\n\n// Counter for unique IDs\nlet diagramIdCounter = 0;\n\nfunction AIMermaidDiagram({\n code,\n theme = 'auto',\n caption,\n interactive = false,\n maxHeight,\n className,\n}: AIMermaidDiagramProps) {\n const safeMaxHeight = getFiniteMaxHeight(maxHeight);\n const [svgContent, setSvgContent] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n const ownerRef = React.useRef<HTMLElement | null>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const resolvedTheme = useResolvedTheme(theme, ownerRef);\n const idRef = React.useRef<string>(`mermaid-diagram-${++diagramIdCounter}`);\n const setOwnerNode = React.useCallback((node: HTMLElement | null) => {\n ownerRef.current = node;\n }, []);\n\n // Pan/zoom state for interactive mode\n const [transformState, setTransformState] =\n React.useState<DiagramTransformState>(() => ({\n code,\n transform: DEFAULT_TRANSFORM,\n }));\n const [isDragging, setIsDragging] = React.useState(false);\n const dragStartRef = React.useRef({ x: 0, y: 0 });\n\n React.useEffect(() => {\n if (!resolvedTheme) {\n return;\n }\n\n const activeTheme = resolvedTheme;\n let cancelled = false;\n\n async function renderDiagram() {\n setIsLoading(true);\n setError(null);\n setSvgContent(null);\n\n try {\n // Dynamically import mermaid\n const mermaid = (await import('mermaid')).default;\n\n // Initialize with theme config\n const config = getMermaidConfig(\n activeTheme,\n ownerRef.current ?? containerRef.current,\n );\n mermaid.initialize(config);\n\n // Generate unique ID for this render\n const id = `${idRef.current}-${Date.now()}`;\n\n // Render the diagram\n // Note: mermaid.render returns sanitized SVG (securityLevel: 'strict')\n const { svg } = await mermaid.render(id, code.trim());\n\n if (!cancelled) {\n setSvgContent(svg);\n setError(null);\n }\n } catch (err) {\n if (!cancelled) {\n const errorMessage =\n err instanceof Error ? err.message : 'Failed to parse diagram';\n setError(errorMessage);\n setSvgContent(null);\n }\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n }\n }\n }\n\n renderDiagram();\n\n return () => {\n cancelled = true;\n };\n }, [code, resolvedTheme]);\n\n const transform =\n transformState.code === code ? transformState.transform : DEFAULT_TRANSFORM;\n\n const setTransformForCode = React.useCallback(\n (\n nextTransform:\n | DiagramTransform\n | ((current: DiagramTransform) => DiagramTransform),\n ) => {\n setTransformState((current) => {\n const currentTransform =\n current.code === code ? current.transform : DEFAULT_TRANSFORM;\n const transform =\n typeof nextTransform === 'function'\n ? nextTransform(currentTransform)\n : nextTransform;\n return { code, transform };\n });\n },\n [code],\n );\n\n // Interactive handlers\n const handleWheel = React.useCallback(\n (e: React.WheelEvent) => {\n if (!interactive) return;\n e.preventDefault();\n\n const delta = e.deltaY > 0 ? 0.9 : 1.1;\n const newScale = Math.min(Math.max(transform.scale * delta, 0.5), 3);\n\n setTransformForCode((prev) => ({\n ...prev,\n scale: newScale,\n }));\n },\n [interactive, setTransformForCode, transform.scale],\n );\n\n const handleMouseDown = React.useCallback(\n (e: React.MouseEvent) => {\n if (!interactive) return;\n setIsDragging(true);\n dragStartRef.current = {\n x: e.clientX - transform.x,\n y: e.clientY - transform.y,\n };\n },\n [interactive, transform.x, transform.y],\n );\n\n const handleMouseMove = React.useCallback(\n (e: React.MouseEvent) => {\n if (!isDragging || !interactive) return;\n setTransformForCode((prev) => ({\n ...prev,\n x: e.clientX - dragStartRef.current.x,\n y: e.clientY - dragStartRef.current.y,\n }));\n },\n [isDragging, interactive, setTransformForCode],\n );\n\n const handleMouseUp = React.useCallback(() => {\n setIsDragging(false);\n }, []);\n\n const handleReset = React.useCallback(() => {\n setTransformForCode(DEFAULT_TRANSFORM);\n }, [setTransformForCode]);\n\n // Loading state\n if (isLoading) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramSkeleton maxHeight={safeMaxHeight} />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramError code={code} error={error} />\n </div>\n );\n }\n\n // Render SVG\n if (!svgContent) {\n return (\n <div ref={setOwnerNode} data-slot=\"ai-mermaid-diagram-status\">\n <DiagramSkeleton maxHeight={safeMaxHeight} />\n </div>\n );\n }\n const svgDataUri = svgToDataUri(svgContent);\n\n return (\n <figure\n ref={setOwnerNode}\n data-slot=\"ai-mermaid-diagram\"\n className={cn('space-y-2', className)}\n >\n {/* biome-ignore lint/a11y/noStaticElementInteractions: Interactive mode requires mouse handlers for pan/zoom */}\n <div\n ref={containerRef}\n className={cn(\n 'relative overflow-hidden rounded-xl border border-border bg-muted',\n interactive && 'cursor-grab',\n isDragging && 'cursor-grabbing',\n )}\n style={{ maxHeight: safeMaxHeight }}\n onWheel={handleWheel}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseUp}\n >\n {/* SVG Container */}\n <div\n className={cn(\n 'flex items-center justify-center p-4',\n safeMaxHeight !== undefined && 'overflow-auto',\n )}\n {...(safeMaxHeight !== undefined\n ? { tabIndex: 0, role: 'region', 'aria-label': 'Diagram view' }\n : {})}\n style={\n interactive\n ? {\n transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`,\n transformOrigin: 'center center',\n transition: isDragging ? 'none' : 'transform 0.1s ease-out',\n }\n : undefined\n }\n >\n <img\n src={svgDataUri}\n alt=\"Mermaid diagram\"\n className=\"block max-w-full h-auto\"\n draggable={false}\n />\n </div>\n\n {/* Interactive controls */}\n {interactive && (\n <div className=\"absolute bottom-2 right-2 flex items-center gap-1 bg-card/80 supports-backdrop-filter:backdrop-blur-sm rounded-lg border border-border p-1\">\n <button\n type=\"button\"\n onClick={() =>\n setTransformForCode((prev) => ({\n ...prev,\n scale: Math.min(prev.scale * 1.2, 3),\n }))\n }\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Zoom in\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setTransformForCode((prev) => ({\n ...prev,\n scale: Math.max(prev.scale * 0.8, 0.5),\n }))\n }\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Zoom out\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M4 10a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H4.75A.75.75 0 014 10z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </button>\n <div className=\"w-px h-4 bg-border-secondary mx-0.5\" />\n <button\n type=\"button\"\n onClick={handleReset}\n className=\"p-1.5 rounded hover:bg-muted text-secondary-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring\"\n aria-label=\"Reset view\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0v2.43l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </button>\n <span className=\"text-xs text-secondary-foreground px-1 tabular-nums\">\n {Math.round(transform.scale * 100)}%\n </span>\n </div>\n )}\n </div>\n\n {/* Caption */}\n {caption && (\n <figcaption className=\"text-sm text-muted-foreground text-center px-4\">\n {caption}\n </figcaption>\n )}\n </figure>\n );\n}\n\nAIMermaidDiagram.displayName = 'AIMermaidDiagram';\n\nexport { AIMermaidDiagram };\nexport default AIMermaidDiagram;\n"],"mappings":"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"}
|
package/dist/index168.js
CHANGED
|
@@ -91,7 +91,7 @@ function AIPromptSuggestionCard({ title, description, icon, onClick, featured =
|
|
|
91
91
|
className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors",
|
|
92
92
|
children: title
|
|
93
93
|
}), description && /* @__PURE__ */ jsx("p", {
|
|
94
|
-
className: "text-xs text-
|
|
94
|
+
className: "text-xs text-secondary-foreground line-clamp-2",
|
|
95
95
|
children: description
|
|
96
96
|
})]
|
|
97
97
|
})]
|
package/dist/index168.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index168.js","names":[],"sources":["../src/components/organisms/ai/AIPromptSuggestion/AIPromptSuggestion.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { ArrowRight, Sparkles } from 'lucide-react';\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Variants\n// =============================================================================\n\nconst promptSuggestionVariants = cva(\n 'group inline-flex items-center gap-2 transition-all cursor-pointer',\n {\n variants: {\n variant: {\n default:\n 'px-4 py-2.5 rounded-full border border-border bg-background hover:border-border hover:shadow-sm text-secondary-foreground hover:text-foreground',\n outline:\n 'px-4 py-2.5 rounded-full border border-border bg-transparent hover:bg-muted text-secondary-foreground hover:text-foreground',\n ghost:\n 'px-3 py-2 rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground',\n card: 'flex-col items-start p-4 rounded-xl border border-border bg-background hover:border-primary hover:shadow-md',\n highlight:\n 'text-primary hover:text-primary underline underline-offset-2',\n },\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n },\n },\n);\n\n// =============================================================================\n// Main AIPromptSuggestion Component\n// =============================================================================\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction getSuggestionLimit(max: number | undefined): number | null {\n return typeof max === 'number' && Number.isFinite(max) && max > 0\n ? Math.floor(max)\n : null;\n}\n\ninterface HighlightedTextPart {\n key: string;\n text: string;\n highlighted: boolean;\n}\n\nfunction getHighlightedTextParts(\n text: string,\n highlight: string,\n): HighlightedTextPart[] {\n const escapedHighlight = escapeRegExp(highlight);\n const parts = text.split(new RegExp(`(${escapedHighlight})`, 'gi'));\n const normalizedHighlight = highlight.toLowerCase();\n let offset = 0;\n\n return parts.map((part) => {\n const partOffset = offset;\n offset += part.length;\n\n return {\n key: `${partOffset}:${part.toLowerCase() === normalizedHighlight ? 'highlight' : 'plain'}:${part}`,\n text: part,\n highlighted: part.toLowerCase() === normalizedHighlight,\n };\n });\n}\n\nexport interface AIPromptSuggestionProps\n extends VariantProps<typeof promptSuggestionVariants> {\n /** Suggestion text */\n children: string;\n /** Click handler */\n onClick?: () => void;\n /** Optional icon */\n icon?: React.ReactNode;\n /** Show arrow icon on hover */\n showArrow?: boolean;\n /** Highlight specific text within the suggestion */\n highlight?: string;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestion({\n children,\n onClick,\n icon,\n showArrow = false,\n highlight,\n variant,\n size,\n className,\n}: AIPromptSuggestionProps) {\n const renderedText = React.useMemo(() => {\n if (!highlight) {\n return <span>{children}</span>;\n }\n\n const parts = getHighlightedTextParts(children, highlight);\n\n return (\n <span>\n {parts.map((part) =>\n part.highlighted ? (\n <span key={part.key} className=\"text-primary font-medium\">\n {part.text}\n </span>\n ) : (\n <span key={part.key}>{part.text}</span>\n ),\n )}\n </span>\n );\n }, [children, highlight]);\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-prompt-suggestion\"\n onClick={onClick}\n className={cn(\n promptSuggestionVariants({ variant, size }),\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n className,\n )}\n >\n {icon && (\n <span className=\"shrink-0\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n {renderedText}\n {showArrow && variant !== 'highlight' && (\n <ArrowRight\n className=\"size-4 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all motion-reduce:transition-none\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n}\n\nAIPromptSuggestion.displayName = 'AIPromptSuggestion';\n\n// =============================================================================\n// Prompt Suggestion Card (Enhanced variant)\n// =============================================================================\n\nexport interface AIPromptSuggestionCardProps {\n /** Card title */\n title: string;\n /** Card description */\n description?: string;\n /** Icon to display */\n icon?: React.ReactNode;\n /** Click handler */\n onClick?: () => void;\n /** Whether the card is featured/highlighted */\n featured?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestionCard({\n title,\n description,\n icon,\n onClick,\n featured = false,\n className,\n}: AIPromptSuggestionCardProps) {\n return (\n <button\n type=\"button\"\n data-slot=\"ai-prompt-suggestion-card\"\n onClick={onClick}\n className={cn(\n 'group flex flex-col items-start gap-2 p-4 rounded-xl border transition-all text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n featured\n ? 'border-primary bg-primary/10 hover:border-primary hover:shadow-md'\n : 'border-border bg-background hover:border-border hover:shadow-md',\n className,\n )}\n >\n {icon && (\n <div\n className={cn(\n 'size-8 rounded-lg flex items-center justify-center',\n featured\n ? 'bg-primary/20 text-primary'\n : 'bg-accent text-muted-foreground',\n )}\n >\n {icon}\n </div>\n )}\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-medium text-foreground group-hover:text-primary transition-colors\">\n {title}\n </h4>\n {description && (\n <p className=\"text-xs text-text-quaternary line-clamp-2\">\n {description}\n </p>\n )}\n </div>\n </button>\n );\n}\n\nAIPromptSuggestionCard.displayName = 'AIPromptSuggestionCard';\n\n// =============================================================================\n// Prompt Suggestion List\n// =============================================================================\n\nexport interface AIPromptSuggestionListProps {\n /** Array of suggestions */\n suggestions: Array<{\n text: string;\n icon?: React.ReactNode;\n }>;\n /** Click handler (receives suggestion text) */\n onSelect?: (text: string) => void;\n /** Variant for all suggestions */\n variant?: AIPromptSuggestionProps['variant'];\n /** Layout direction */\n direction?: 'horizontal' | 'vertical';\n /** Maximum suggestions to show */\n max?: number;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestionList({\n suggestions,\n onSelect,\n variant = 'default',\n direction = 'horizontal',\n max,\n className,\n}: AIPromptSuggestionListProps) {\n const limit = getSuggestionLimit(max);\n const visibleSuggestions =\n limit === null ? suggestions : suggestions.slice(0, limit);\n\n return (\n <section\n data-slot=\"ai-prompt-suggestion-list\"\n aria-label=\"Prompt suggestions\"\n className={cn(\n 'flex gap-2',\n direction === 'horizontal' ? 'flex-wrap' : 'flex-col',\n className,\n )}\n >\n {visibleSuggestions.map((suggestion) => (\n <AIPromptSuggestion\n key={suggestion.text}\n variant={variant}\n icon={suggestion.icon}\n onClick={() => onSelect?.(suggestion.text)}\n >\n {suggestion.text}\n </AIPromptSuggestion>\n ))}\n </section>\n );\n}\n\nAIPromptSuggestionList.displayName = 'AIPromptSuggestionList';\n\n// =============================================================================\n// Quick Actions Grid\n// =============================================================================\n\nexport interface QuickAction {\n id: string;\n title: string;\n description?: string;\n icon?: React.ReactNode;\n prompt: string;\n}\n\nexport interface AIQuickActionsProps {\n /** Array of quick actions */\n actions: QuickAction[];\n /** Click handler (receives action prompt) */\n onSelect?: (prompt: string) => void;\n /** Visual variant */\n variant?: 'card' | 'pill';\n /** Number of columns (only for card variant) */\n columns?: 2 | 3 | 4;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIQuickActions({\n actions,\n onSelect,\n variant = 'card',\n columns = 2,\n className,\n}: AIQuickActionsProps) {\n const gridCols = {\n 2: 'grid-cols-2',\n 3: 'grid-cols-3',\n 4: 'grid-cols-4',\n };\n\n if (variant === 'pill') {\n return (\n <div className={cn('flex flex-wrap gap-2', className)}>\n {actions.map((action) => (\n <AIPromptSuggestion\n key={action.id}\n icon={action.icon || <Sparkles className=\"size-4\" />}\n onClick={() => onSelect?.(action.prompt)}\n >\n {action.title}\n </AIPromptSuggestion>\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('grid gap-3', gridCols[columns], className)}>\n {actions.map((action) => (\n <AIPromptSuggestionCard\n key={action.id}\n title={action.title}\n description={action.description}\n icon={action.icon || <Sparkles className=\"size-4\" />}\n onClick={() => onSelect?.(action.prompt)}\n />\n ))}\n </div>\n );\n}\n\nAIQuickActions.displayName = 'AIQuickActions';\n\nexport {\n AIPromptSuggestion,\n AIPromptSuggestionCard,\n AIPromptSuggestionList,\n AIQuickActions,\n promptSuggestionVariants,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index168.js","names":[],"sources":["../src/components/organisms/ai/AIPromptSuggestion/AIPromptSuggestion.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { ArrowRight, Sparkles } from 'lucide-react';\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\n\n// =============================================================================\n// Variants\n// =============================================================================\n\nconst promptSuggestionVariants = cva(\n 'group inline-flex items-center gap-2 transition-all cursor-pointer',\n {\n variants: {\n variant: {\n default:\n 'px-4 py-2.5 rounded-full border border-border bg-background hover:border-border hover:shadow-sm text-secondary-foreground hover:text-foreground',\n outline:\n 'px-4 py-2.5 rounded-full border border-border bg-transparent hover:bg-muted text-secondary-foreground hover:text-foreground',\n ghost:\n 'px-3 py-2 rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground',\n card: 'flex-col items-start p-4 rounded-xl border border-border bg-background hover:border-primary hover:shadow-md',\n highlight:\n 'text-primary hover:text-primary underline underline-offset-2',\n },\n size: {\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-base',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'md',\n },\n },\n);\n\n// =============================================================================\n// Main AIPromptSuggestion Component\n// =============================================================================\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction getSuggestionLimit(max: number | undefined): number | null {\n return typeof max === 'number' && Number.isFinite(max) && max > 0\n ? Math.floor(max)\n : null;\n}\n\ninterface HighlightedTextPart {\n key: string;\n text: string;\n highlighted: boolean;\n}\n\nfunction getHighlightedTextParts(\n text: string,\n highlight: string,\n): HighlightedTextPart[] {\n const escapedHighlight = escapeRegExp(highlight);\n const parts = text.split(new RegExp(`(${escapedHighlight})`, 'gi'));\n const normalizedHighlight = highlight.toLowerCase();\n let offset = 0;\n\n return parts.map((part) => {\n const partOffset = offset;\n offset += part.length;\n\n return {\n key: `${partOffset}:${part.toLowerCase() === normalizedHighlight ? 'highlight' : 'plain'}:${part}`,\n text: part,\n highlighted: part.toLowerCase() === normalizedHighlight,\n };\n });\n}\n\nexport interface AIPromptSuggestionProps\n extends VariantProps<typeof promptSuggestionVariants> {\n /** Suggestion text */\n children: string;\n /** Click handler */\n onClick?: () => void;\n /** Optional icon */\n icon?: React.ReactNode;\n /** Show arrow icon on hover */\n showArrow?: boolean;\n /** Highlight specific text within the suggestion */\n highlight?: string;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestion({\n children,\n onClick,\n icon,\n showArrow = false,\n highlight,\n variant,\n size,\n className,\n}: AIPromptSuggestionProps) {\n const renderedText = React.useMemo(() => {\n if (!highlight) {\n return <span>{children}</span>;\n }\n\n const parts = getHighlightedTextParts(children, highlight);\n\n return (\n <span>\n {parts.map((part) =>\n part.highlighted ? (\n <span key={part.key} className=\"text-primary font-medium\">\n {part.text}\n </span>\n ) : (\n <span key={part.key}>{part.text}</span>\n ),\n )}\n </span>\n );\n }, [children, highlight]);\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-prompt-suggestion\"\n onClick={onClick}\n className={cn(\n promptSuggestionVariants({ variant, size }),\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n className,\n )}\n >\n {icon && (\n <span className=\"shrink-0\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n {renderedText}\n {showArrow && variant !== 'highlight' && (\n <ArrowRight\n className=\"size-4 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all motion-reduce:transition-none\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n}\n\nAIPromptSuggestion.displayName = 'AIPromptSuggestion';\n\n// =============================================================================\n// Prompt Suggestion Card (Enhanced variant)\n// =============================================================================\n\nexport interface AIPromptSuggestionCardProps {\n /** Card title */\n title: string;\n /** Card description */\n description?: string;\n /** Icon to display */\n icon?: React.ReactNode;\n /** Click handler */\n onClick?: () => void;\n /** Whether the card is featured/highlighted */\n featured?: boolean;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestionCard({\n title,\n description,\n icon,\n onClick,\n featured = false,\n className,\n}: AIPromptSuggestionCardProps) {\n return (\n <button\n type=\"button\"\n data-slot=\"ai-prompt-suggestion-card\"\n onClick={onClick}\n className={cn(\n 'group flex flex-col items-start gap-2 p-4 rounded-xl border transition-all text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n featured\n ? 'border-primary bg-primary/10 hover:border-primary hover:shadow-md'\n : 'border-border bg-background hover:border-border hover:shadow-md',\n className,\n )}\n >\n {icon && (\n <div\n className={cn(\n 'size-8 rounded-lg flex items-center justify-center',\n featured\n ? 'bg-primary/20 text-primary'\n : 'bg-accent text-muted-foreground',\n )}\n >\n {icon}\n </div>\n )}\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-medium text-foreground group-hover:text-primary transition-colors\">\n {title}\n </h4>\n {description && (\n <p className=\"text-xs text-secondary-foreground line-clamp-2\">\n {description}\n </p>\n )}\n </div>\n </button>\n );\n}\n\nAIPromptSuggestionCard.displayName = 'AIPromptSuggestionCard';\n\n// =============================================================================\n// Prompt Suggestion List\n// =============================================================================\n\nexport interface AIPromptSuggestionListProps {\n /** Array of suggestions */\n suggestions: Array<{\n text: string;\n icon?: React.ReactNode;\n }>;\n /** Click handler (receives suggestion text) */\n onSelect?: (text: string) => void;\n /** Variant for all suggestions */\n variant?: AIPromptSuggestionProps['variant'];\n /** Layout direction */\n direction?: 'horizontal' | 'vertical';\n /** Maximum suggestions to show */\n max?: number;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIPromptSuggestionList({\n suggestions,\n onSelect,\n variant = 'default',\n direction = 'horizontal',\n max,\n className,\n}: AIPromptSuggestionListProps) {\n const limit = getSuggestionLimit(max);\n const visibleSuggestions =\n limit === null ? suggestions : suggestions.slice(0, limit);\n\n return (\n <section\n data-slot=\"ai-prompt-suggestion-list\"\n aria-label=\"Prompt suggestions\"\n className={cn(\n 'flex gap-2',\n direction === 'horizontal' ? 'flex-wrap' : 'flex-col',\n className,\n )}\n >\n {visibleSuggestions.map((suggestion) => (\n <AIPromptSuggestion\n key={suggestion.text}\n variant={variant}\n icon={suggestion.icon}\n onClick={() => onSelect?.(suggestion.text)}\n >\n {suggestion.text}\n </AIPromptSuggestion>\n ))}\n </section>\n );\n}\n\nAIPromptSuggestionList.displayName = 'AIPromptSuggestionList';\n\n// =============================================================================\n// Quick Actions Grid\n// =============================================================================\n\nexport interface QuickAction {\n id: string;\n title: string;\n description?: string;\n icon?: React.ReactNode;\n prompt: string;\n}\n\nexport interface AIQuickActionsProps {\n /** Array of quick actions */\n actions: QuickAction[];\n /** Click handler (receives action prompt) */\n onSelect?: (prompt: string) => void;\n /** Visual variant */\n variant?: 'card' | 'pill';\n /** Number of columns (only for card variant) */\n columns?: 2 | 3 | 4;\n /** Additional class name */\n className?: string;\n}\n\nfunction AIQuickActions({\n actions,\n onSelect,\n variant = 'card',\n columns = 2,\n className,\n}: AIQuickActionsProps) {\n const gridCols = {\n 2: 'grid-cols-2',\n 3: 'grid-cols-3',\n 4: 'grid-cols-4',\n };\n\n if (variant === 'pill') {\n return (\n <div className={cn('flex flex-wrap gap-2', className)}>\n {actions.map((action) => (\n <AIPromptSuggestion\n key={action.id}\n icon={action.icon || <Sparkles className=\"size-4\" />}\n onClick={() => onSelect?.(action.prompt)}\n >\n {action.title}\n </AIPromptSuggestion>\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('grid gap-3', gridCols[columns], className)}>\n {actions.map((action) => (\n <AIPromptSuggestionCard\n key={action.id}\n title={action.title}\n description={action.description}\n icon={action.icon || <Sparkles className=\"size-4\" />}\n onClick={() => onSelect?.(action.prompt)}\n />\n ))}\n </div>\n );\n}\n\nAIQuickActions.displayName = 'AIQuickActions';\n\nexport {\n AIPromptSuggestion,\n AIPromptSuggestionCard,\n AIPromptSuggestionList,\n AIQuickActions,\n promptSuggestionVariants,\n};\n"],"mappings":";;;;;;AASA,IAAM,2BAA2B,IAC/B,sEACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,SACE;GACF,OACE;GACF,MAAM;GACN,WACE;EACJ;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAMA,SAAS,aAAa,OAAuB;CAC3C,OAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,mBAAmB,KAAwC;CAClE,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,MAAM,IAC5D,KAAK,MAAM,GAAG,IACd;AACN;AAQA,SAAS,wBACP,MACA,WACuB;CACvB,MAAM,mBAAmB,aAAa,SAAS;CAC/C,MAAM,QAAQ,KAAK,MAAM,IAAI,OAAO,IAAI,iBAAiB,IAAI,IAAI,CAAC;CAClE,MAAM,sBAAsB,UAAU,YAAY;CAClD,IAAI,SAAS;CAEb,OAAO,MAAM,KAAK,SAAS;EACzB,MAAM,aAAa;EACnB,UAAU,KAAK;EAEf,OAAO;GACL,KAAK,GAAG,WAAW,GAAG,KAAK,YAAY,MAAM,sBAAsB,cAAc,QAAQ,GAAG;GAC5F,MAAM;GACN,aAAa,KAAK,YAAY,MAAM;EACtC;CACF,CAAC;AACH;AAkBA,SAAS,mBAAmB,EAC1B,UACA,SACA,MACA,YAAY,OACZ,WACA,SACA,MACA,aAC0B;CAC1B,MAAM,eAAe,QAAM,cAAc;EACvC,IAAI,CAAC,WACH,OAAO,oBAAC,QAAD,EAAO,SAAe,CAAA;EAK/B,OACE,oBAAC,QAAD,EAAA,UAHY,wBAAwB,UAAU,SAI3C,CAAA,CAAM,KAAK,SACV,KAAK,cACH,oBAAC,QAAD;GAAqB,WAAU;aAC5B,KAAK;EACF,GAFK,KAAK,GAEV,IAEN,oBAAC,QAAD,EAAA,UAAsB,KAAK,KAAW,GAA3B,KAAK,GAAsB,CAE1C,EACI,CAAA;CAEV,GAAG,CAAC,UAAU,SAAS,CAAC;CAExB,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACD;EACT,WAAW,GACT,yBAAyB;GAAE;GAAS;EAAK,CAAC,GAC1C,qFACA,SACF;YARF;GAUG,QACC,oBAAC,QAAD;IAAM,WAAU;IAAW,eAAY;cACpC;GACG,CAAA;GAEP;GACA,aAAa,YAAY,eACxB,oBAAC,YAAD;IACE,WAAU;IACV,eAAY;GACb,CAAA;EAEG;;AAEZ;AAEA,mBAAmB,cAAc;AAqBjC,SAAS,uBAAuB,EAC9B,OACA,aACA,MACA,SACA,WAAW,OACX,aAC8B;CAC9B,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACD;EACT,WAAW,GACT,0KACA,WACI,sEACA,mEACJ,SACF;YAVF,CAYG,QACC,oBAAC,OAAD;GACE,WAAW,GACT,sDACA,WACI,+BACA,iCACN;aAEC;EACE,CAAA,GAEP,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,MAAD;IAAI,WAAU;cACX;GACC,CAAA,GACH,eACC,oBAAC,KAAD;IAAG,WAAU;cACV;GACA,CAAA,CAEF;IACC;;AAEZ;AAEA,uBAAuB,cAAc;AAwBrC,SAAS,uBAAuB,EAC9B,aACA,UACA,UAAU,WACV,YAAY,cACZ,KACA,aAC8B;CAC9B,MAAM,QAAQ,mBAAmB,GAAG;CACpC,MAAM,qBACJ,UAAU,OAAO,cAAc,YAAY,MAAM,GAAG,KAAK;CAE3D,OACE,oBAAC,WAAD;EACE,aAAU;EACV,cAAW;EACX,WAAW,GACT,cACA,cAAc,eAAe,cAAc,YAC3C,SACF;YAEC,mBAAmB,KAAK,eACvB,oBAAC,oBAAD;GAEW;GACT,MAAM,WAAW;GACjB,eAAe,WAAW,WAAW,IAAI;aAExC,WAAW;EACM,GANb,WAAW,IAME,CACrB;CACM,CAAA;AAEb;AAEA,uBAAuB,cAAc;AA2BrC,SAAS,eAAe,EACtB,SACA,UACA,UAAU,QACV,UAAU,GACV,aACsB;CACtB,MAAM,WAAW;EACf,GAAG;EACH,GAAG;EACH,GAAG;CACL;CAEA,IAAI,YAAY,QACd,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,wBAAwB,SAAS;YACjD,QAAQ,KAAK,WACZ,oBAAC,oBAAD;GAEE,MAAM,OAAO,QAAQ,oBAAC,UAAD,EAAU,WAAU,SAAU,CAAA;GACnD,eAAe,WAAW,OAAO,MAAM;aAEtC,OAAO;EACU,GALb,OAAO,EAKM,CACrB;CACE,CAAA;CAIT,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,cAAc,SAAS,UAAU,SAAS;YAC1D,QAAQ,KAAK,WACZ,oBAAC,wBAAD;GAEE,OAAO,OAAO;GACd,aAAa,OAAO;GACpB,MAAM,OAAO,QAAQ,oBAAC,UAAD,EAAU,WAAU,SAAU,CAAA;GACnD,eAAe,WAAW,OAAO,MAAM;EACxC,GALM,OAAO,EAKb,CACF;CACE,CAAA;AAET;AAEA,eAAe,cAAc"}
|
package/dist/index169.js
CHANGED
|
@@ -83,7 +83,7 @@ function AIReasoningTrigger({ title = "Reasoning", isOpen, onToggle, isStreaming
|
|
|
83
83
|
className: "flex items-center gap-2",
|
|
84
84
|
children: [
|
|
85
85
|
/* @__PURE__ */ jsx(Lightbulb, {
|
|
86
|
-
className: cn("size-4", isStreaming ? "text-primary" : "text-
|
|
86
|
+
className: cn("size-4", isStreaming ? "text-primary" : "text-muted-foreground"),
|
|
87
87
|
"aria-hidden": "true"
|
|
88
88
|
}),
|
|
89
89
|
/* @__PURE__ */ jsx("span", {
|