@windrun-huaiin/third-ui 15.1.1 → 16.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +1 -1
  2. package/dist/ai/ai-chat-composer.d.ts +2 -0
  3. package/dist/ai/ai-chat-composer.js +47 -0
  4. package/dist/ai/ai-chat-composer.mjs +45 -0
  5. package/dist/ai/ai-markdown.d.ts +2 -0
  6. package/dist/ai/ai-markdown.js +36 -0
  7. package/dist/ai/ai-markdown.mjs +34 -0
  8. package/dist/ai/ai-message-actions.d.ts +2 -0
  9. package/dist/ai/ai-message-actions.js +14 -0
  10. package/dist/ai/ai-message-actions.mjs +12 -0
  11. package/dist/ai/ai-message-bubble.d.ts +2 -0
  12. package/dist/ai/ai-message-bubble.js +66 -0
  13. package/dist/ai/ai-message-bubble.mjs +64 -0
  14. package/dist/ai/ai-message-content.d.ts +2 -0
  15. package/dist/ai/ai-message-content.js +63 -0
  16. package/dist/ai/ai-message-content.mjs +61 -0
  17. package/dist/ai/ai-message-list.d.ts +2 -0
  18. package/dist/ai/ai-message-list.js +24 -0
  19. package/dist/ai/ai-message-list.mjs +22 -0
  20. package/dist/ai/ai-message-meta.d.ts +2 -0
  21. package/dist/ai/ai-message-meta.js +38 -0
  22. package/dist/ai/ai-message-meta.mjs +36 -0
  23. package/dist/ai/ai-status-indicator.d.ts +2 -0
  24. package/dist/ai/ai-status-indicator.js +51 -0
  25. package/dist/ai/ai-status-indicator.mjs +49 -0
  26. package/dist/ai/index.d.ts +11 -0
  27. package/dist/ai/index.js +33 -0
  28. package/dist/ai/index.mjs +11 -0
  29. package/dist/ai/types.d.ts +110 -0
  30. package/dist/ai/use-ai-conversation.d.ts +13 -0
  31. package/dist/ai/use-ai-conversation.js +276 -0
  32. package/dist/ai/use-ai-conversation.mjs +274 -0
  33. package/dist/clerk/clerk-organization-client.js +2 -2
  34. package/dist/clerk/clerk-organization-client.mjs +2 -2
  35. package/dist/clerk/clerk-page-generator.d.ts +1 -1
  36. package/dist/clerk/clerk-user-client.js +2 -2
  37. package/dist/clerk/clerk-user-client.mjs +2 -2
  38. package/dist/clerk/fingerprint/fingerprint-provider.js +9 -9
  39. package/dist/clerk/fingerprint/fingerprint-provider.mjs +9 -9
  40. package/dist/fuma/base/custom-header.js +4 -4
  41. package/dist/fuma/base/custom-header.mjs +4 -4
  42. package/dist/fuma/mdx/banner.js +3 -3
  43. package/dist/fuma/mdx/banner.mjs +3 -3
  44. package/dist/fuma/mdx/fuma-github-info.js +3 -3
  45. package/dist/fuma/mdx/fuma-github-info.mjs +3 -3
  46. package/dist/fuma/mdx/gradient-button.js +3 -3
  47. package/dist/fuma/mdx/gradient-button.mjs +3 -3
  48. package/dist/fuma/mdx/index.d.ts +1 -0
  49. package/dist/fuma/mdx/index.js +2 -0
  50. package/dist/fuma/mdx/index.mjs +1 -0
  51. package/dist/fuma/mdx/markdown-component-map.d.ts +3 -0
  52. package/dist/fuma/mdx/markdown-component-map.js +73 -0
  53. package/dist/fuma/mdx/markdown-component-map.mjs +71 -0
  54. package/dist/fuma/mdx/mermaid.d.ts +2 -1
  55. package/dist/fuma/mdx/mermaid.js +130 -6
  56. package/dist/fuma/mdx/mermaid.mjs +130 -6
  57. package/dist/fuma/mdx/toc-base.js +4 -4
  58. package/dist/fuma/mdx/toc-base.mjs +4 -4
  59. package/dist/fuma/mdx/trophy-card.js +2 -2
  60. package/dist/fuma/mdx/trophy-card.mjs +2 -2
  61. package/dist/fuma/mdx/zia-card.js +3 -3
  62. package/dist/fuma/mdx/zia-card.mjs +3 -3
  63. package/dist/fuma/mdx/zia-file.js +3 -3
  64. package/dist/fuma/mdx/zia-file.mjs +3 -3
  65. package/dist/main/ads-alert-dialog.js +2 -2
  66. package/dist/main/ads-alert-dialog.mjs +2 -2
  67. package/dist/main/credit/credit-nav-button.js +2 -2
  68. package/dist/main/credit/credit-nav-button.mjs +2 -2
  69. package/dist/main/credit/credit-overview-client.js +4 -4
  70. package/dist/main/credit/credit-overview-client.mjs +4 -4
  71. package/dist/main/footer.js +2 -2
  72. package/dist/main/footer.mjs +2 -2
  73. package/dist/main/go-to-top.js +2 -2
  74. package/dist/main/go-to-top.mjs +2 -2
  75. package/dist/main/index.d.ts +1 -0
  76. package/dist/main/index.js +2 -0
  77. package/dist/main/index.mjs +1 -0
  78. package/dist/main/info-tooltip.d.ts +8 -0
  79. package/dist/main/info-tooltip.js +48 -0
  80. package/dist/main/info-tooltip.mjs +46 -0
  81. package/dist/main/pill-select/x-pill-select.js +2 -2
  82. package/dist/main/pill-select/x-pill-select.mjs +2 -2
  83. package/dist/main/pill-select/x-token-input.js +2 -2
  84. package/dist/main/pill-select/x-token-input.mjs +2 -2
  85. package/dist/main/x-button.js +3 -3
  86. package/dist/main/x-button.mjs +3 -3
  87. package/package.json +16 -3
  88. package/src/ai/ai-chat-composer.tsx +187 -0
  89. package/src/ai/ai-markdown.tsx +45 -0
  90. package/src/ai/ai-message-actions.tsx +16 -0
  91. package/src/ai/ai-message-bubble.tsx +138 -0
  92. package/src/ai/ai-message-content.tsx +149 -0
  93. package/src/ai/ai-message-list.tsx +59 -0
  94. package/src/ai/ai-message-meta.tsx +56 -0
  95. package/src/ai/ai-status-indicator.tsx +61 -0
  96. package/src/ai/index.ts +13 -0
  97. package/src/ai/types.ts +131 -0
  98. package/src/ai/use-ai-conversation.ts +422 -0
  99. package/src/clerk/clerk-organization-client.tsx +5 -5
  100. package/src/clerk/clerk-page-generator.tsx +1 -1
  101. package/src/clerk/clerk-user-client.tsx +4 -4
  102. package/src/clerk/fingerprint/fingerprint-provider.tsx +34 -22
  103. package/src/fuma/base/custom-header.tsx +5 -5
  104. package/src/fuma/mdx/banner.tsx +3 -3
  105. package/src/fuma/mdx/fuma-github-info.tsx +4 -4
  106. package/src/fuma/mdx/gradient-button.tsx +3 -3
  107. package/src/fuma/mdx/index.ts +2 -1
  108. package/src/fuma/mdx/markdown-component-map.tsx +174 -0
  109. package/src/fuma/mdx/mermaid.tsx +145 -10
  110. package/src/fuma/mdx/toc-base.tsx +5 -5
  111. package/src/fuma/mdx/trophy-card.tsx +2 -2
  112. package/src/fuma/mdx/zia-card.tsx +3 -3
  113. package/src/fuma/mdx/zia-file.tsx +3 -3
  114. package/src/main/ads-alert-dialog.tsx +5 -5
  115. package/src/main/credit/credit-nav-button.tsx +3 -3
  116. package/src/main/credit/credit-overview-client.tsx +15 -7
  117. package/src/main/features.tsx +5 -3
  118. package/src/main/footer.tsx +4 -5
  119. package/src/main/go-to-top.tsx +2 -2
  120. package/src/main/index.ts +2 -0
  121. package/src/main/info-tooltip.tsx +99 -0
  122. package/src/main/language-detector.tsx +4 -4
  123. package/src/main/pill-select/x-pill-select.tsx +2 -2
  124. package/src/main/pill-select/x-token-input.tsx +2 -2
  125. package/src/main/x-button.tsx +4 -4
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 D8ger
3
+ Copyright (c) 2026 D8ger
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,2 @@
1
+ import type { AIChatComposerProps } from './types';
2
+ export declare function AIChatComposer({ value, onChange, onSubmit, onStop, disabled, isStreaming, placeholder, className, leftSlot, attachments, helper, submitLabel, stopLabel, minHeight, maxHeight, submitOnEnter, shellClassName, textareaClassName, submitControl, stopControl, textareaRef: externalTextareaRef, secondaryActions, actionLayout, }: AIChatComposerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+ var React = require('react');
7
+
8
+ function resizeTextarea(textarea, minHeight, maxHeight) {
9
+ textarea.style.height = 'auto';
10
+ const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
11
+ textarea.style.height = `${nextHeight}px`;
12
+ textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden';
13
+ }
14
+ function AIChatComposer({ value, onChange, onSubmit, onStop, disabled = false, isStreaming = false, placeholder = 'Ask anything...', className, leftSlot, attachments, helper, submitLabel = 'Send', stopLabel = 'Stop', minHeight = 52, maxHeight = 220, submitOnEnter = true, shellClassName, textareaClassName, submitControl, stopControl, textareaRef: externalTextareaRef, secondaryActions, actionLayout = 'inline', }) {
15
+ const internalTextareaRef = React.useRef(null);
16
+ const textareaRef = externalTextareaRef !== null && externalTextareaRef !== void 0 ? externalTextareaRef : internalTextareaRef;
17
+ React.useEffect(() => {
18
+ if (!textareaRef.current) {
19
+ return;
20
+ }
21
+ resizeTextarea(textareaRef.current, minHeight, maxHeight);
22
+ }, [actionLayout, maxHeight, minHeight, value]);
23
+ const handleKeyDown = (event) => {
24
+ if (!submitOnEnter || event.nativeEvent.isComposing) {
25
+ return;
26
+ }
27
+ if (event.key === 'Enter' && !event.shiftKey) {
28
+ event.preventDefault();
29
+ if (isStreaming && onStop) {
30
+ onStop();
31
+ return;
32
+ }
33
+ if (!disabled && value.trim()) {
34
+ onSubmit();
35
+ }
36
+ }
37
+ };
38
+ const primaryAction = isStreaming && onStop
39
+ ? (stopControl !== null && stopControl !== void 0 ? stopControl : (jsxRuntime.jsx("button", { type: "button", onClick: onStop, className: "inline-flex h-10 items-center justify-center rounded-2xl border border-border px-4 text-sm text-foreground transition hover:bg-muted", children: stopLabel })))
40
+ : (submitControl !== null && submitControl !== void 0 ? submitControl : (jsxRuntime.jsx("button", { type: "button", onClick: onSubmit, disabled: disabled || isStreaming || value.trim().length === 0, className: "inline-flex h-10 items-center justify-center rounded-2xl bg-foreground px-4 text-sm text-background transition disabled:cursor-not-allowed disabled:opacity-50", children: submitLabel })));
41
+ if (actionLayout === 'stacked') {
42
+ return (jsxRuntime.jsxs("div", { className: utils.cn('space-y-3', className), children: [attachments ? jsxRuntime.jsx("div", { children: attachments }) : null, jsxRuntime.jsxs("div", { className: utils.cn('rounded-3xl border border-border bg-background px-3 py-3', shellClassName), children: [jsxRuntime.jsxs("div", { className: "flex items-end gap-3", children: [jsxRuntime.jsx("div", { className: "flex shrink-0 items-center", children: leftSlot }), jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: jsxRuntime.jsx("textarea", { ref: textareaRef, rows: 1, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: utils.cn('block w-full resize-none border-0 bg-transparent px-0 py-2 text-sm leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 box-border', textareaClassName), style: { minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` } }) })] }), jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between gap-3 border-t border-border/70 pt-3", children: [jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: secondaryActions }), jsxRuntime.jsx("div", { className: "flex shrink-0 items-center gap-2", children: primaryAction })] })] }), helper ? jsxRuntime.jsx("div", { children: helper }) : null] }));
43
+ }
44
+ return (jsxRuntime.jsxs("div", { className: utils.cn('space-y-3', className), children: [attachments ? jsxRuntime.jsx("div", { children: attachments }) : null, jsxRuntime.jsxs("div", { className: utils.cn('flex items-end gap-3 rounded-3xl border border-border bg-background px-3 py-3', shellClassName), children: [jsxRuntime.jsx("div", { className: "flex shrink-0 items-center", children: leftSlot }), jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: jsxRuntime.jsx("textarea", { ref: textareaRef, rows: 1, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: utils.cn('block w-full resize-none border-0 bg-transparent px-0 py-2 text-sm leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 box-border', textareaClassName), style: { minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` } }) }), jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [secondaryActions, primaryAction] })] }), helper ? jsxRuntime.jsx("div", { children: helper }) : null] }));
45
+ }
46
+
47
+ exports.AIChatComposer = AIChatComposer;
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+ import { useRef, useEffect } from 'react';
5
+
6
+ function resizeTextarea(textarea, minHeight, maxHeight) {
7
+ textarea.style.height = 'auto';
8
+ const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
9
+ textarea.style.height = `${nextHeight}px`;
10
+ textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden';
11
+ }
12
+ function AIChatComposer({ value, onChange, onSubmit, onStop, disabled = false, isStreaming = false, placeholder = 'Ask anything...', className, leftSlot, attachments, helper, submitLabel = 'Send', stopLabel = 'Stop', minHeight = 52, maxHeight = 220, submitOnEnter = true, shellClassName, textareaClassName, submitControl, stopControl, textareaRef: externalTextareaRef, secondaryActions, actionLayout = 'inline', }) {
13
+ const internalTextareaRef = useRef(null);
14
+ const textareaRef = externalTextareaRef !== null && externalTextareaRef !== void 0 ? externalTextareaRef : internalTextareaRef;
15
+ useEffect(() => {
16
+ if (!textareaRef.current) {
17
+ return;
18
+ }
19
+ resizeTextarea(textareaRef.current, minHeight, maxHeight);
20
+ }, [actionLayout, maxHeight, minHeight, value]);
21
+ const handleKeyDown = (event) => {
22
+ if (!submitOnEnter || event.nativeEvent.isComposing) {
23
+ return;
24
+ }
25
+ if (event.key === 'Enter' && !event.shiftKey) {
26
+ event.preventDefault();
27
+ if (isStreaming && onStop) {
28
+ onStop();
29
+ return;
30
+ }
31
+ if (!disabled && value.trim()) {
32
+ onSubmit();
33
+ }
34
+ }
35
+ };
36
+ const primaryAction = isStreaming && onStop
37
+ ? (stopControl !== null && stopControl !== void 0 ? stopControl : (jsx("button", { type: "button", onClick: onStop, className: "inline-flex h-10 items-center justify-center rounded-2xl border border-border px-4 text-sm text-foreground transition hover:bg-muted", children: stopLabel })))
38
+ : (submitControl !== null && submitControl !== void 0 ? submitControl : (jsx("button", { type: "button", onClick: onSubmit, disabled: disabled || isStreaming || value.trim().length === 0, className: "inline-flex h-10 items-center justify-center rounded-2xl bg-foreground px-4 text-sm text-background transition disabled:cursor-not-allowed disabled:opacity-50", children: submitLabel })));
39
+ if (actionLayout === 'stacked') {
40
+ return (jsxs("div", { className: cn('space-y-3', className), children: [attachments ? jsx("div", { children: attachments }) : null, jsxs("div", { className: cn('rounded-3xl border border-border bg-background px-3 py-3', shellClassName), children: [jsxs("div", { className: "flex items-end gap-3", children: [jsx("div", { className: "flex shrink-0 items-center", children: leftSlot }), jsx("div", { className: "min-w-0 flex-1", children: jsx("textarea", { ref: textareaRef, rows: 1, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: cn('block w-full resize-none border-0 bg-transparent px-0 py-2 text-sm leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 box-border', textareaClassName), style: { minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` } }) })] }), jsxs("div", { className: "mt-3 flex items-center justify-between gap-3 border-t border-border/70 pt-3", children: [jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: secondaryActions }), jsx("div", { className: "flex shrink-0 items-center gap-2", children: primaryAction })] })] }), helper ? jsx("div", { children: helper }) : null] }));
41
+ }
42
+ return (jsxs("div", { className: cn('space-y-3', className), children: [attachments ? jsx("div", { children: attachments }) : null, jsxs("div", { className: cn('flex items-end gap-3 rounded-3xl border border-border bg-background px-3 py-3', shellClassName), children: [jsx("div", { className: "flex shrink-0 items-center", children: leftSlot }), jsx("div", { className: "min-w-0 flex-1", children: jsx("textarea", { ref: textareaRef, rows: 1, value: value, onChange: (event) => onChange(event.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, className: cn('block w-full resize-none border-0 bg-transparent px-0 py-2 text-sm leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 box-border', textareaClassName), style: { minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` } }) }), jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [secondaryActions, primaryAction] })] }), helper ? jsx("div", { children: helper }) : null] }));
43
+ }
44
+
45
+ export { AIChatComposer };
@@ -0,0 +1,2 @@
1
+ import type { AIMarkdownProps } from './types';
2
+ export declare function AIMarkdown({ content, className, components }: AIMarkdownProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+ var hastUtilToJsxRuntime = require('hast-util-to-jsx-runtime');
7
+ var markdownComponentMap = require('../fuma/mdx/markdown-component-map.js');
8
+ var React = require('react');
9
+ var remarkGfm = require('remark-gfm');
10
+ var remarkParse = require('remark-parse');
11
+ var remarkRehype = require('remark-rehype');
12
+ var unified = require('unified');
13
+
14
+ const processor = unified.unified()
15
+ .use(remarkParse)
16
+ .use(remarkGfm)
17
+ .use(remarkRehype);
18
+ const defaultComponents = markdownComponentMap.baseMarkdownComponents;
19
+ function AIMarkdown({ content, className, components }) {
20
+ const tree = React.useMemo(() => {
21
+ return processor.runSync(processor.parse(content));
22
+ }, [content]);
23
+ const element = React.useMemo(() => {
24
+ return hastUtilToJsxRuntime.toJsxRuntime(tree, {
25
+ Fragment: jsxRuntime.Fragment,
26
+ jsx: jsxRuntime.jsx,
27
+ jsxs: jsxRuntime.jsxs,
28
+ components: Object.assign(Object.assign({}, defaultComponents), (components !== null && components !== void 0 ? components : {})),
29
+ elementAttributeNameCase: 'html',
30
+ stylePropertyNameCase: 'css',
31
+ });
32
+ }, [components, tree]);
33
+ return (jsxRuntime.jsx("div", { className: utils.cn('space-y-4 text-sm text-inherit', className), children: element }));
34
+ }
35
+
36
+ exports.AIMarkdown = AIMarkdown;
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+ import { toJsxRuntime } from 'hast-util-to-jsx-runtime';
5
+ import { baseMarkdownComponents } from '../fuma/mdx/markdown-component-map.mjs';
6
+ import { useMemo } from 'react';
7
+ import remarkGfm from 'remark-gfm';
8
+ import remarkParse from 'remark-parse';
9
+ import remarkRehype from 'remark-rehype';
10
+ import { unified } from 'unified';
11
+
12
+ const processor = unified()
13
+ .use(remarkParse)
14
+ .use(remarkGfm)
15
+ .use(remarkRehype);
16
+ const defaultComponents = baseMarkdownComponents;
17
+ function AIMarkdown({ content, className, components }) {
18
+ const tree = useMemo(() => {
19
+ return processor.runSync(processor.parse(content));
20
+ }, [content]);
21
+ const element = useMemo(() => {
22
+ return toJsxRuntime(tree, {
23
+ Fragment,
24
+ jsx,
25
+ jsxs,
26
+ components: Object.assign(Object.assign({}, defaultComponents), (components !== null && components !== void 0 ? components : {})),
27
+ elementAttributeNameCase: 'html',
28
+ stylePropertyNameCase: 'css',
29
+ });
30
+ }, [components, tree]);
31
+ return (jsx("div", { className: cn('space-y-4 text-sm text-inherit', className), children: element }));
32
+ }
33
+
34
+ export { AIMarkdown };
@@ -0,0 +1,2 @@
1
+ import type { AIMessageActionsProps } from './types';
2
+ export declare function AIMessageActions({ className, children }: AIMessageActionsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+
7
+ function AIMessageActions({ className, children }) {
8
+ if (!children) {
9
+ return null;
10
+ }
11
+ return (jsxRuntime.jsx("div", { className: utils.cn('flex flex-wrap items-center justify-end gap-2', className), children: children }));
12
+ }
13
+
14
+ exports.AIMessageActions = AIMessageActions;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+
5
+ function AIMessageActions({ className, children }) {
6
+ if (!children) {
7
+ return null;
8
+ }
9
+ return (jsx("div", { className: cn('flex flex-wrap items-center justify-end gap-2', className), children: children }));
10
+ }
11
+
12
+ export { AIMessageActions };
@@ -0,0 +1,2 @@
1
+ import type { AIMessageBubbleProps } from './types';
2
+ export declare function AIMessageBubble({ message, className, cardClassName, contentClassName, footerClassName, maxWidthClassName, showRoleLabel, markdownComponents, showFooter, renderContent, renderMeta, renderActions, }: AIMessageBubbleProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+ var React = require('react');
7
+ var aiMessageActions = require('./ai-message-actions.js');
8
+ var aiMessageContent = require('./ai-message-content.js');
9
+ var aiMessageMeta = require('./ai-message-meta.js');
10
+
11
+ function getRoleLabel(role) {
12
+ switch (role) {
13
+ case 'assistant':
14
+ return 'Assistant';
15
+ case 'system':
16
+ return 'System';
17
+ case 'tool':
18
+ return 'Tool';
19
+ default:
20
+ return 'You';
21
+ }
22
+ }
23
+ function AIMessageBubble({ message, className, cardClassName, contentClassName, footerClassName, maxWidthClassName, showRoleLabel = false, markdownComponents, showFooter = true, renderContent, renderMeta, renderActions, }) {
24
+ const isUser = message.role === 'user';
25
+ const contentWrapperRef = React.useRef(null);
26
+ const [isCompactSingleLine, setIsCompactSingleLine] = React.useState(false);
27
+ const content = renderContent
28
+ ? renderContent(message)
29
+ : jsxRuntime.jsx(aiMessageContent.AIMessageContent, { message: message, className: contentClassName, markdownComponents: markdownComponents });
30
+ const meta = renderMeta ? renderMeta(message) : jsxRuntime.jsx(aiMessageMeta.AIMessageMeta, { message: message });
31
+ const actions = renderActions ? renderActions(message) : null;
32
+ const hasFooter = Boolean(meta) || Boolean(actions);
33
+ const isTextOnlyMessage = message.parts.length > 0
34
+ ? message.parts.every((part) => part.type === 'text')
35
+ : Boolean(message.errorMessage);
36
+ React.useEffect(() => {
37
+ if (!isTextOnlyMessage || !contentWrapperRef.current) {
38
+ setIsCompactSingleLine(false);
39
+ return;
40
+ }
41
+ const element = contentWrapperRef.current;
42
+ const measure = () => {
43
+ const computedStyle = window.getComputedStyle(element);
44
+ const lineHeight = Number.parseFloat(computedStyle.lineHeight);
45
+ if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
46
+ setIsCompactSingleLine(false);
47
+ return;
48
+ }
49
+ const nextIsSingleLine = element.scrollHeight <= lineHeight * 1.75;
50
+ setIsCompactSingleLine(nextIsSingleLine);
51
+ };
52
+ measure();
53
+ const observer = new ResizeObserver(() => {
54
+ measure();
55
+ });
56
+ observer.observe(element);
57
+ return () => {
58
+ observer.disconnect();
59
+ };
60
+ }, [isTextOnlyMessage, message.errorMessage, message.id, message.parts]);
61
+ return (jsxRuntime.jsx("div", { className: utils.cn('flex w-full', isUser ? 'justify-end' : 'justify-start', className), children: jsxRuntime.jsxs("article", { className: utils.cn('min-h-12 min-w-30 max-w-full rounded-3xl border px-4 py-3 sm:min-h-13 sm:min-w-36', isUser ? 'w-fit' : 'w-full', maxWidthClassName !== null && maxWidthClassName !== void 0 ? maxWidthClassName : 'max-w-[92%] sm:max-w-[82%]', isUser
62
+ ? 'border-foreground/10 bg-foreground text-background'
63
+ : 'border-border bg-background text-foreground', cardClassName), children: [showRoleLabel ? (jsxRuntime.jsx("div", { className: "mb-2 flex items-center gap-3", children: jsxRuntime.jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] opacity-60", children: getRoleLabel(message.role) }) })) : null, jsxRuntime.jsx("div", { ref: contentWrapperRef, className: utils.cn('min-w-0', isTextOnlyMessage && isCompactSingleLine && 'flex justify-center'), children: content }), showFooter && hasFooter ? (jsxRuntime.jsxs("div", { className: utils.cn('mt-3 flex flex-wrap items-center justify-between gap-3 border-t border-border/70 pt-3', footerClassName), children: [jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: meta }), actions ? jsxRuntime.jsx(aiMessageActions.AIMessageActions, { children: actions }) : null] })) : null] }) }));
64
+ }
65
+
66
+ exports.AIMessageBubble = AIMessageBubble;
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+ import { useRef, useState, useEffect } from 'react';
5
+ import { AIMessageActions } from './ai-message-actions.mjs';
6
+ import { AIMessageContent } from './ai-message-content.mjs';
7
+ import { AIMessageMeta } from './ai-message-meta.mjs';
8
+
9
+ function getRoleLabel(role) {
10
+ switch (role) {
11
+ case 'assistant':
12
+ return 'Assistant';
13
+ case 'system':
14
+ return 'System';
15
+ case 'tool':
16
+ return 'Tool';
17
+ default:
18
+ return 'You';
19
+ }
20
+ }
21
+ function AIMessageBubble({ message, className, cardClassName, contentClassName, footerClassName, maxWidthClassName, showRoleLabel = false, markdownComponents, showFooter = true, renderContent, renderMeta, renderActions, }) {
22
+ const isUser = message.role === 'user';
23
+ const contentWrapperRef = useRef(null);
24
+ const [isCompactSingleLine, setIsCompactSingleLine] = useState(false);
25
+ const content = renderContent
26
+ ? renderContent(message)
27
+ : jsx(AIMessageContent, { message: message, className: contentClassName, markdownComponents: markdownComponents });
28
+ const meta = renderMeta ? renderMeta(message) : jsx(AIMessageMeta, { message: message });
29
+ const actions = renderActions ? renderActions(message) : null;
30
+ const hasFooter = Boolean(meta) || Boolean(actions);
31
+ const isTextOnlyMessage = message.parts.length > 0
32
+ ? message.parts.every((part) => part.type === 'text')
33
+ : Boolean(message.errorMessage);
34
+ useEffect(() => {
35
+ if (!isTextOnlyMessage || !contentWrapperRef.current) {
36
+ setIsCompactSingleLine(false);
37
+ return;
38
+ }
39
+ const element = contentWrapperRef.current;
40
+ const measure = () => {
41
+ const computedStyle = window.getComputedStyle(element);
42
+ const lineHeight = Number.parseFloat(computedStyle.lineHeight);
43
+ if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
44
+ setIsCompactSingleLine(false);
45
+ return;
46
+ }
47
+ const nextIsSingleLine = element.scrollHeight <= lineHeight * 1.75;
48
+ setIsCompactSingleLine(nextIsSingleLine);
49
+ };
50
+ measure();
51
+ const observer = new ResizeObserver(() => {
52
+ measure();
53
+ });
54
+ observer.observe(element);
55
+ return () => {
56
+ observer.disconnect();
57
+ };
58
+ }, [isTextOnlyMessage, message.errorMessage, message.id, message.parts]);
59
+ return (jsx("div", { className: cn('flex w-full', isUser ? 'justify-end' : 'justify-start', className), children: jsxs("article", { className: cn('min-h-12 min-w-30 max-w-full rounded-3xl border px-4 py-3 sm:min-h-13 sm:min-w-36', isUser ? 'w-fit' : 'w-full', maxWidthClassName !== null && maxWidthClassName !== void 0 ? maxWidthClassName : 'max-w-[92%] sm:max-w-[82%]', isUser
60
+ ? 'border-foreground/10 bg-foreground text-background'
61
+ : 'border-border bg-background text-foreground', cardClassName), children: [showRoleLabel ? (jsx("div", { className: "mb-2 flex items-center gap-3", children: jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.14em] opacity-60", children: getRoleLabel(message.role) }) })) : null, jsx("div", { ref: contentWrapperRef, className: cn('min-w-0', isTextOnlyMessage && isCompactSingleLine && 'flex justify-center'), children: content }), showFooter && hasFooter ? (jsxs("div", { className: cn('mt-3 flex flex-wrap items-center justify-between gap-3 border-t border-border/70 pt-3', footerClassName), children: [jsx("div", { className: "min-w-0 flex-1", children: meta }), actions ? jsx(AIMessageActions, { children: actions }) : null] })) : null] }) }));
62
+ }
63
+
64
+ export { AIMessageBubble };
@@ -0,0 +1,2 @@
1
+ import type { AIMessageContentProps } from './types';
2
+ export declare function AIMessageContent({ message, className, markdownComponents, }: AIMessageContentProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var lib = require('@windrun-huaiin/base-ui/lib');
6
+ var utils = require('@windrun-huaiin/lib/utils');
7
+ var trophyCard = require('../fuma/mdx/trophy-card.js');
8
+ var aiMarkdown = require('./ai-markdown.js');
9
+
10
+ function hasRenderablePart(part) {
11
+ if (part.type === 'text') {
12
+ return part.text.trim().length > 0;
13
+ }
14
+ return true;
15
+ }
16
+ function getEmptyAssistantFallback(message) {
17
+ if (message.status === 'streaming') {
18
+ return null;
19
+ }
20
+ if (message.status === 'timeout') {
21
+ return 'No response received. The request timed out before the model returned any content.';
22
+ }
23
+ if (message.status === 'request_aborted' || message.status === 'stopped') {
24
+ return 'Response stopped before any content was returned.';
25
+ }
26
+ if (message.status === 'failed') {
27
+ return message.errorMessage || 'The model did not return any visible content.';
28
+ }
29
+ if (message.errorMessage) {
30
+ return message.errorMessage;
31
+ }
32
+ return 'The model returned no visible content.';
33
+ }
34
+ function renderPart(part, index, markdownComponents) {
35
+ if (part.type === 'text') {
36
+ return (jsxRuntime.jsx(aiMarkdown.AIMarkdown, { content: part.text, components: markdownComponents }, `text-${index}`));
37
+ }
38
+ if (part.type === 'image') {
39
+ return (jsxRuntime.jsxs("div", { className: "rounded-2xl border border-dashed border-border/70 px-3 py-2 text-xs text-muted-foreground", children: ["Image part reserved: ", part.alt || part.url] }, `image-${index}`));
40
+ }
41
+ if (part.type === 'trophy_card') {
42
+ return (jsxRuntime.jsx("div", { className: "rounded-2xl bg-muted/35 p-1 text-foreground", children: jsxRuntime.jsx(trophyCard.TrophyCard, { title: part.title, children: part.description ? (jsxRuntime.jsx("div", { className: "mt-2", children: jsxRuntime.jsx(aiMarkdown.AIMarkdown, { content: part.description, components: markdownComponents, className: "space-y-3 text-sm text-inherit" }) })) : null }) }, `trophy-card-${index}`));
43
+ }
44
+ return (jsxRuntime.jsxs("div", { className: "rounded-2xl border border-dashed border-border/70 px-3 py-2 text-xs text-muted-foreground", children: ["File part reserved: ", part.name || part.url] }, `file-${index}`));
45
+ }
46
+ function AIMessageContent({ message, className, markdownComponents, }) {
47
+ const parts = message.parts.filter(hasRenderablePart);
48
+ if (message.role === 'assistant' &&
49
+ message.status === 'streaming' &&
50
+ parts.length === 0) {
51
+ return (jsxRuntime.jsxs("div", { className: utils.cn('flex items-center gap-2 text-sm text-muted-foreground', lib.themeIconColor, className), children: [jsxRuntime.jsx("span", { children: "AI is thinking" }), jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", "aria-hidden": "true", children: [jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:0ms]" }), jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:180ms]" }), jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:360ms]" })] })] }));
52
+ }
53
+ if (parts.length === 0) {
54
+ const fallbackText = getEmptyAssistantFallback(message);
55
+ if (!fallbackText) {
56
+ return null;
57
+ }
58
+ return (jsxRuntime.jsx(aiMarkdown.AIMarkdown, { content: fallbackText, components: markdownComponents, className: utils.cn('space-y-3 text-sm text-inherit', className) }));
59
+ }
60
+ return (jsxRuntime.jsx("div", { className: utils.cn('space-y-3', className), children: parts.map((part, index) => renderPart(part, index, markdownComponents)) }));
61
+ }
62
+
63
+ exports.AIMessageContent = AIMessageContent;
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { themeIconColor } from '@windrun-huaiin/base-ui/lib';
4
+ import { cn } from '@windrun-huaiin/lib/utils';
5
+ import { TrophyCard } from '../fuma/mdx/trophy-card.mjs';
6
+ import { AIMarkdown } from './ai-markdown.mjs';
7
+
8
+ function hasRenderablePart(part) {
9
+ if (part.type === 'text') {
10
+ return part.text.trim().length > 0;
11
+ }
12
+ return true;
13
+ }
14
+ function getEmptyAssistantFallback(message) {
15
+ if (message.status === 'streaming') {
16
+ return null;
17
+ }
18
+ if (message.status === 'timeout') {
19
+ return 'No response received. The request timed out before the model returned any content.';
20
+ }
21
+ if (message.status === 'request_aborted' || message.status === 'stopped') {
22
+ return 'Response stopped before any content was returned.';
23
+ }
24
+ if (message.status === 'failed') {
25
+ return message.errorMessage || 'The model did not return any visible content.';
26
+ }
27
+ if (message.errorMessage) {
28
+ return message.errorMessage;
29
+ }
30
+ return 'The model returned no visible content.';
31
+ }
32
+ function renderPart(part, index, markdownComponents) {
33
+ if (part.type === 'text') {
34
+ return (jsx(AIMarkdown, { content: part.text, components: markdownComponents }, `text-${index}`));
35
+ }
36
+ if (part.type === 'image') {
37
+ return (jsxs("div", { className: "rounded-2xl border border-dashed border-border/70 px-3 py-2 text-xs text-muted-foreground", children: ["Image part reserved: ", part.alt || part.url] }, `image-${index}`));
38
+ }
39
+ if (part.type === 'trophy_card') {
40
+ return (jsx("div", { className: "rounded-2xl bg-muted/35 p-1 text-foreground", children: jsx(TrophyCard, { title: part.title, children: part.description ? (jsx("div", { className: "mt-2", children: jsx(AIMarkdown, { content: part.description, components: markdownComponents, className: "space-y-3 text-sm text-inherit" }) })) : null }) }, `trophy-card-${index}`));
41
+ }
42
+ return (jsxs("div", { className: "rounded-2xl border border-dashed border-border/70 px-3 py-2 text-xs text-muted-foreground", children: ["File part reserved: ", part.name || part.url] }, `file-${index}`));
43
+ }
44
+ function AIMessageContent({ message, className, markdownComponents, }) {
45
+ const parts = message.parts.filter(hasRenderablePart);
46
+ if (message.role === 'assistant' &&
47
+ message.status === 'streaming' &&
48
+ parts.length === 0) {
49
+ return (jsxs("div", { className: cn('flex items-center gap-2 text-sm text-muted-foreground', themeIconColor, className), children: [jsx("span", { children: "AI is thinking" }), jsxs("span", { className: "inline-flex items-center gap-1", "aria-hidden": "true", children: [jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:0ms]" }), jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:180ms]" }), jsx("span", { className: "size-1.5 rounded-full bg-current animate-pulse [animation-delay:360ms]" })] })] }));
50
+ }
51
+ if (parts.length === 0) {
52
+ const fallbackText = getEmptyAssistantFallback(message);
53
+ if (!fallbackText) {
54
+ return null;
55
+ }
56
+ return (jsx(AIMarkdown, { content: fallbackText, components: markdownComponents, className: cn('space-y-3 text-sm text-inherit', className) }));
57
+ }
58
+ return (jsx("div", { className: cn('space-y-3', className), children: parts.map((part, index) => renderPart(part, index, markdownComponents)) }));
59
+ }
60
+
61
+ export { AIMessageContent };
@@ -0,0 +1,2 @@
1
+ import type { AIMessageListProps } from './types';
2
+ export declare function AIMessageList({ messages, className, contentClassName, emptyText, emptyState, autoScroll, scrollBehavior, renderMessage, }: AIMessageListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+ var React = require('react');
7
+ var aiMessageBubble = require('./ai-message-bubble.js');
8
+
9
+ function AIMessageList({ messages, className, contentClassName, emptyText = 'No messages yet.', emptyState, autoScroll = true, scrollBehavior = 'smooth', renderMessage, }) {
10
+ const bottomRef = React.useRef(null);
11
+ React.useEffect(() => {
12
+ var _a;
13
+ if (!autoScroll) {
14
+ return;
15
+ }
16
+ (_a = bottomRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: scrollBehavior, block: 'end' });
17
+ }, [autoScroll, messages, scrollBehavior]);
18
+ const content = messages.length === 0
19
+ ? (emptyState !== null && emptyState !== void 0 ? emptyState : (jsxRuntime.jsx("div", { className: "rounded-2xl border border-dashed border-border p-6 text-sm text-muted-foreground", children: emptyText })))
20
+ : (messages.map((message) => (jsxRuntime.jsx("div", { children: renderMessage ? renderMessage(message) : jsxRuntime.jsx(aiMessageBubble.AIMessageBubble, { message: message }) }, message.id))));
21
+ return (jsxRuntime.jsx("div", { className: utils.cn('min-h-0 flex-1 overflow-y-auto', className), children: jsxRuntime.jsxs("div", { className: utils.cn('mx-auto flex min-h-full w-full max-w-5xl flex-col gap-5 px-1', messages.length === 0 ? 'justify-center' : 'justify-end', contentClassName), children: [content, jsxRuntime.jsx("div", { ref: bottomRef })] }) }));
22
+ }
23
+
24
+ exports.AIMessageList = AIMessageList;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+ import { useRef, useEffect } from 'react';
5
+ import { AIMessageBubble } from './ai-message-bubble.mjs';
6
+
7
+ function AIMessageList({ messages, className, contentClassName, emptyText = 'No messages yet.', emptyState, autoScroll = true, scrollBehavior = 'smooth', renderMessage, }) {
8
+ const bottomRef = useRef(null);
9
+ useEffect(() => {
10
+ var _a;
11
+ if (!autoScroll) {
12
+ return;
13
+ }
14
+ (_a = bottomRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: scrollBehavior, block: 'end' });
15
+ }, [autoScroll, messages, scrollBehavior]);
16
+ const content = messages.length === 0
17
+ ? (emptyState !== null && emptyState !== void 0 ? emptyState : (jsx("div", { className: "rounded-2xl border border-dashed border-border p-6 text-sm text-muted-foreground", children: emptyText })))
18
+ : (messages.map((message) => (jsx("div", { children: renderMessage ? renderMessage(message) : jsx(AIMessageBubble, { message: message }) }, message.id))));
19
+ return (jsx("div", { className: cn('min-h-0 flex-1 overflow-y-auto', className), children: jsxs("div", { className: cn('mx-auto flex min-h-full w-full max-w-5xl flex-col gap-5 px-1', messages.length === 0 ? 'justify-center' : 'justify-end', contentClassName), children: [content, jsx("div", { ref: bottomRef })] }) }));
20
+ }
21
+
22
+ export { AIMessageList };
@@ -0,0 +1,2 @@
1
+ import type { AIMessageMetaProps } from './types';
2
+ export declare function AIMessageMeta({ message, className, showTime, showStatus, showRuntime, showFailureReason, }: AIMessageMetaProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var utils = require('@windrun-huaiin/lib/utils');
6
+ var aiStatusIndicator = require('./ai-status-indicator.js');
7
+
8
+ function getRuntimeMetadata(message) {
9
+ var _a;
10
+ const metadata = (_a = message.metadata) === null || _a === void 0 ? void 0 : _a.aiRuntime;
11
+ if (!metadata || typeof metadata !== 'object') {
12
+ return {};
13
+ }
14
+ return metadata;
15
+ }
16
+ function formatDuration(durationMs) {
17
+ if (durationMs === undefined || Number.isNaN(durationMs)) {
18
+ return null;
19
+ }
20
+ if (durationMs < 1000) {
21
+ return `${durationMs}ms`;
22
+ }
23
+ return `${(durationMs / 1000).toFixed(2)}s`;
24
+ }
25
+ function formatTime(createdAt) {
26
+ return new Intl.DateTimeFormat(undefined, {
27
+ hour: '2-digit',
28
+ minute: '2-digit',
29
+ }).format(createdAt);
30
+ }
31
+ function AIMessageMeta({ message, className, showTime = true, showStatus = true, showRuntime = true, showFailureReason = true, }) {
32
+ const runtime = getRuntimeMetadata(message);
33
+ const firstToken = formatDuration(runtime.firstTokenMs);
34
+ const total = formatDuration(runtime.totalMs);
35
+ return (jsxRuntime.jsxs("div", { className: utils.cn('flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground', className), children: [showTime ? jsxRuntime.jsx("span", { children: formatTime(message.createdAt) }) : null, showStatus ? jsxRuntime.jsx(aiStatusIndicator.AIStatusIndicator, { message: message }) : null, showRuntime && firstToken ? jsxRuntime.jsxs("span", { children: ["First Token ", firstToken] }) : null, showRuntime && total ? jsxRuntime.jsxs("span", { children: ["Total ", total] }) : null, showFailureReason && message.failureReason ? jsxRuntime.jsxs("span", { children: ["Reason ", message.failureReason] }) : null] }));
36
+ }
37
+
38
+ exports.AIMessageMeta = AIMessageMeta;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { cn } from '@windrun-huaiin/lib/utils';
4
+ import { AIStatusIndicator } from './ai-status-indicator.mjs';
5
+
6
+ function getRuntimeMetadata(message) {
7
+ var _a;
8
+ const metadata = (_a = message.metadata) === null || _a === void 0 ? void 0 : _a.aiRuntime;
9
+ if (!metadata || typeof metadata !== 'object') {
10
+ return {};
11
+ }
12
+ return metadata;
13
+ }
14
+ function formatDuration(durationMs) {
15
+ if (durationMs === undefined || Number.isNaN(durationMs)) {
16
+ return null;
17
+ }
18
+ if (durationMs < 1000) {
19
+ return `${durationMs}ms`;
20
+ }
21
+ return `${(durationMs / 1000).toFixed(2)}s`;
22
+ }
23
+ function formatTime(createdAt) {
24
+ return new Intl.DateTimeFormat(undefined, {
25
+ hour: '2-digit',
26
+ minute: '2-digit',
27
+ }).format(createdAt);
28
+ }
29
+ function AIMessageMeta({ message, className, showTime = true, showStatus = true, showRuntime = true, showFailureReason = true, }) {
30
+ const runtime = getRuntimeMetadata(message);
31
+ const firstToken = formatDuration(runtime.firstTokenMs);
32
+ const total = formatDuration(runtime.totalMs);
33
+ return (jsxs("div", { className: cn('flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground', className), children: [showTime ? jsx("span", { children: formatTime(message.createdAt) }) : null, showStatus ? jsx(AIStatusIndicator, { message: message }) : null, showRuntime && firstToken ? jsxs("span", { children: ["First Token ", firstToken] }) : null, showRuntime && total ? jsxs("span", { children: ["Total ", total] }) : null, showFailureReason && message.failureReason ? jsxs("span", { children: ["Reason ", message.failureReason] }) : null] }));
34
+ }
35
+
36
+ export { AIMessageMeta };
@@ -0,0 +1,2 @@
1
+ import type { AIStatusIndicatorProps } from './types';
2
+ export declare function AIStatusIndicator({ message, className }: AIStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;