@djangocfg/ui-tools 2.1.335 → 2.1.336
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/README.md +68 -2
- package/dist/ChatRoot-IIYQEWUU.mjs +5 -0
- package/dist/ChatRoot-IIYQEWUU.mjs.map +1 -0
- package/dist/ChatRoot-PNNGQCYF.css +7 -0
- package/dist/ChatRoot-PNNGQCYF.css.map +1 -0
- package/dist/ChatRoot-UUKTYM4N.cjs +14 -0
- package/dist/ChatRoot-UUKTYM4N.cjs.map +1 -0
- package/dist/{CronScheduler.client-3O3VU4CI.mjs → CronScheduler.client-DLMXCPAJ.mjs} +4 -4
- package/dist/{CronScheduler.client-3O3VU4CI.mjs.map → CronScheduler.client-DLMXCPAJ.mjs.map} +1 -1
- package/dist/{CronScheduler.client-A4GO6YBY.cjs → CronScheduler.client-WEJF4PWQ.cjs} +14 -14
- package/dist/{CronScheduler.client-A4GO6YBY.cjs.map → CronScheduler.client-WEJF4PWQ.cjs.map} +1 -1
- package/dist/{DocsLayout-XLDB6CJ2.cjs → DocsLayout-N5ZJZPBY.cjs} +200 -199
- package/dist/DocsLayout-N5ZJZPBY.cjs.map +1 -0
- package/dist/{DocsLayout-CTJINVBM.mjs → DocsLayout-VFPPNKSQ.mjs} +7 -6
- package/dist/DocsLayout-VFPPNKSQ.mjs.map +1 -0
- package/dist/JsonSchemaForm-DD7CLRIG.cjs +13 -0
- package/dist/{JsonSchemaForm-6WMS4CIY.cjs.map → JsonSchemaForm-DD7CLRIG.cjs.map} +1 -1
- package/dist/JsonSchemaForm-XKUIVELK.mjs +4 -0
- package/dist/{JsonSchemaForm-KX4JT3M4.mjs.map → JsonSchemaForm-XKUIVELK.mjs.map} +1 -1
- package/dist/JsonTree-55625VVH.mjs +5 -0
- package/dist/{JsonTree-F27RMYSI.cjs.map → JsonTree-55625VVH.mjs.map} +1 -1
- package/dist/JsonTree-DCM5QGWF.cjs +11 -0
- package/dist/{JsonTree-QTJYSHCV.mjs.map → JsonTree-DCM5QGWF.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-6WVWDO75.cjs → LottiePlayer.client-2S7ISJ2S.cjs} +6 -6
- package/dist/{LottiePlayer.client-6WVWDO75.cjs.map → LottiePlayer.client-2S7ISJ2S.cjs.map} +1 -1
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs → LottiePlayer.client-5LDSSJWS.mjs} +4 -4
- package/dist/{LottiePlayer.client-B4I6WNZM.mjs.map → LottiePlayer.client-5LDSSJWS.mjs.map} +1 -1
- package/dist/{MapContainer-RYG4HPH4.cjs → MapContainer-76YL2JXL.cjs} +8 -8
- package/dist/{MapContainer-RYG4HPH4.cjs.map → MapContainer-76YL2JXL.cjs.map} +1 -1
- package/dist/{MapContainer-GXQLP5WY.mjs → MapContainer-7HXBI3OH.mjs} +3 -3
- package/dist/{MapContainer-GXQLP5WY.mjs.map → MapContainer-7HXBI3OH.mjs.map} +1 -1
- package/dist/{Mermaid.client-SXRRI2YW.mjs → Mermaid.client-NL4SVR7F.mjs} +4 -4
- package/dist/{Mermaid.client-SXRRI2YW.mjs.map → Mermaid.client-NL4SVR7F.mjs.map} +1 -1
- package/dist/{Mermaid.client-W76R5AKJ.cjs → Mermaid.client-NNTI6DFX.cjs} +26 -26
- package/dist/{Mermaid.client-W76R5AKJ.cjs.map → Mermaid.client-NNTI6DFX.cjs.map} +1 -1
- package/dist/Player-BRV7XTWR.mjs +4 -0
- package/dist/{Player-M3GC3VPE.mjs.map → Player-BRV7XTWR.mjs.map} +1 -1
- package/dist/Player-PM7F7DD7.cjs +13 -0
- package/dist/{Player-ZL2X5LGG.cjs.map → Player-PM7F7DD7.cjs.map} +1 -1
- package/dist/{PrettyCode.client-RPDIE5CH.cjs → PrettyCode.client-KOHDVPPN.cjs} +13 -13
- package/dist/{PrettyCode.client-RPDIE5CH.cjs.map → PrettyCode.client-KOHDVPPN.cjs.map} +1 -1
- package/dist/{PrettyCode.client-SPMTQEG4.mjs → PrettyCode.client-ZGYGKE7G.mjs} +4 -4
- package/dist/{PrettyCode.client-SPMTQEG4.mjs.map → PrettyCode.client-ZGYGKE7G.mjs.map} +1 -1
- package/dist/TreeRoot-N72OYKXU.cjs +19 -0
- package/dist/{TreeRoot-A3J65L6F.mjs.map → TreeRoot-N72OYKXU.cjs.map} +1 -1
- package/dist/TreeRoot-VGAIXCUA.mjs +4 -0
- package/dist/{TreeRoot-DSK5JILT.cjs.map → TreeRoot-VGAIXCUA.mjs.map} +1 -1
- package/dist/chunk-2ZLKZ5VR.mjs +631 -0
- package/dist/chunk-2ZLKZ5VR.mjs.map +1 -0
- package/dist/{chunk-LFWQ36LJ.mjs → chunk-5G5YBFS6.mjs} +4 -4
- package/dist/{chunk-LFWQ36LJ.mjs.map → chunk-5G5YBFS6.mjs.map} +1 -1
- package/dist/{chunk-IHAY6FO6.cjs → chunk-5I5QNGUG.cjs} +17 -17
- package/dist/{chunk-IHAY6FO6.cjs.map → chunk-5I5QNGUG.cjs.map} +1 -1
- package/dist/{chunk-F2CMIIOH.cjs → chunk-76NNDZH6.cjs} +42 -42
- package/dist/{chunk-F2CMIIOH.cjs.map → chunk-76NNDZH6.cjs.map} +1 -1
- package/dist/chunk-B5AWZOHJ.cjs +649 -0
- package/dist/chunk-B5AWZOHJ.cjs.map +1 -0
- package/dist/{chunk-KR6B3LVY.mjs → chunk-B6IR5KSC.mjs} +3 -3
- package/dist/{chunk-KR6B3LVY.mjs.map → chunk-B6IR5KSC.mjs.map} +1 -1
- package/dist/{chunk-5LBDYFWH.mjs → chunk-C6GXVH5J.mjs} +3 -3
- package/dist/{chunk-5LBDYFWH.mjs.map → chunk-C6GXVH5J.mjs.map} +1 -1
- package/dist/{chunk-NRKD4F5X.cjs → chunk-FEN5S772.cjs} +36 -36
- package/dist/{chunk-NRKD4F5X.cjs.map → chunk-FEN5S772.cjs.map} +1 -1
- package/dist/{chunk-2SMCH62O.cjs → chunk-FP2RLYQZ.cjs} +11 -11
- package/dist/{chunk-2SMCH62O.cjs.map → chunk-FP2RLYQZ.cjs.map} +1 -1
- package/dist/{chunk-MOME6KYD.mjs → chunk-G5IEC7SR.mjs} +3 -3
- package/dist/{chunk-MOME6KYD.mjs.map → chunk-G5IEC7SR.mjs.map} +1 -1
- package/dist/{chunk-SE5IERVH.mjs → chunk-GYIO7W7M.mjs} +3 -3
- package/dist/{chunk-SE5IERVH.mjs.map → chunk-GYIO7W7M.mjs.map} +1 -1
- package/dist/{chunk-3Z3A7FHA.cjs → chunk-IEEAENLX.cjs} +48 -48
- package/dist/{chunk-3Z3A7FHA.cjs.map → chunk-IEEAENLX.cjs.map} +1 -1
- package/dist/{chunk-DFTVB66S.cjs → chunk-KNDLV4PI.cjs} +85 -85
- package/dist/{chunk-DFTVB66S.cjs.map → chunk-KNDLV4PI.cjs.map} +1 -1
- package/dist/{chunk-SSUOENAZ.mjs → chunk-KNEQRUBA.mjs} +3 -3
- package/dist/{chunk-SSUOENAZ.mjs.map → chunk-KNEQRUBA.mjs.map} +1 -1
- package/dist/chunk-KRETIZU6.mjs +2218 -0
- package/dist/chunk-KRETIZU6.mjs.map +1 -0
- package/dist/{chunk-CGILA3WO.mjs → chunk-N2XQF2OL.mjs} +5 -3
- package/dist/{chunk-CGILA3WO.mjs.map → chunk-N2XQF2OL.mjs.map} +1 -1
- package/dist/{chunk-EUADAUBQ.mjs → chunk-N4MZYNR4.mjs} +4 -4
- package/dist/{chunk-EUADAUBQ.mjs.map → chunk-N4MZYNR4.mjs.map} +1 -1
- package/dist/chunk-NRXYYO5V.cjs +2257 -0
- package/dist/chunk-NRXYYO5V.cjs.map +1 -0
- package/dist/{chunk-GGKGH5PM.mjs → chunk-OBRSGM64.mjs} +4 -4
- package/dist/{chunk-GGKGH5PM.mjs.map → chunk-OBRSGM64.mjs.map} +1 -1
- package/dist/{chunk-6JTB2X72.mjs → chunk-ODO4GMW7.mjs} +3 -3
- package/dist/{chunk-6JTB2X72.mjs.map → chunk-ODO4GMW7.mjs.map} +1 -1
- package/dist/{chunk-WGEGR3DF.cjs → chunk-OLISEQHS.cjs} +5 -2
- package/dist/{chunk-WGEGR3DF.cjs.map → chunk-OLISEQHS.cjs.map} +1 -1
- package/dist/{chunk-PZKAH7WQ.mjs → chunk-PVAX67JG.mjs} +3 -3
- package/dist/{chunk-PZKAH7WQ.mjs.map → chunk-PVAX67JG.mjs.map} +1 -1
- package/dist/{chunk-PRPG2T2E.cjs → chunk-QJ6GTUCO.cjs} +6 -6
- package/dist/{chunk-PRPG2T2E.cjs.map → chunk-QJ6GTUCO.cjs.map} +1 -1
- package/dist/chunk-QW4RBGHN.cjs +961 -0
- package/dist/chunk-QW4RBGHN.cjs.map +1 -0
- package/dist/{chunk-33AMWFBZ.cjs → chunk-SGP7V2UW.cjs} +15 -15
- package/dist/{chunk-33AMWFBZ.cjs.map → chunk-SGP7V2UW.cjs.map} +1 -1
- package/dist/{chunk-FX2QFYWF.mjs → chunk-VWQ5WOIL.mjs} +3 -3
- package/dist/{chunk-FX2QFYWF.mjs.map → chunk-VWQ5WOIL.mjs.map} +1 -1
- package/dist/{chunk-ZLQHUZDU.cjs → chunk-YDPDTOSP.cjs} +139 -139
- package/dist/{chunk-ZLQHUZDU.cjs.map → chunk-YDPDTOSP.cjs.map} +1 -1
- package/dist/{chunk-77HQWEQ6.cjs → chunk-YW5IVWHQ.cjs} +33 -33
- package/dist/{chunk-77HQWEQ6.cjs.map → chunk-YW5IVWHQ.cjs.map} +1 -1
- package/dist/{chunk-YXBOAGIM.cjs → chunk-YXZ6GU7H.cjs} +7 -7
- package/dist/{chunk-YXBOAGIM.cjs.map → chunk-YXZ6GU7H.cjs.map} +1 -1
- package/dist/{chunk-62Y65TGK.mjs → chunk-ZUFTH5IR.mjs} +8 -631
- package/dist/chunk-ZUFTH5IR.mjs.map +1 -0
- package/dist/components-EHOGXATG.cjs +22 -0
- package/dist/{components-5UXYNAKR.cjs.map → components-EHOGXATG.cjs.map} +1 -1
- package/dist/components-MQ6DR7TX.cjs +26 -0
- package/dist/{components-CFXOEVPN.mjs.map → components-MQ6DR7TX.cjs.map} +1 -1
- package/dist/components-XRX7QGLB.mjs +5 -0
- package/dist/{components-WYEZL5TE.cjs.map → components-XRX7QGLB.mjs.map} +1 -1
- package/dist/components-YATKRWLH.mjs +5 -0
- package/dist/{components-ZAGG2PBO.mjs.map → components-YATKRWLH.mjs.map} +1 -1
- package/dist/file-icon/index.cjs +6 -6
- package/dist/file-icon/index.mjs +1 -1
- package/dist/index.cjs +735 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +972 -39
- package/dist/index.d.ts +972 -39
- package/dist/index.mjs +387 -31
- package/dist/index.mjs.map +1 -1
- package/dist/tree/index.cjs +38 -38
- package/dist/tree/index.d.cts +2 -2
- package/dist/tree/index.d.ts +2 -2
- package/dist/tree/index.mjs +3 -3
- package/package.json +6 -6
- package/src/index.ts +5 -0
- package/src/stories/index.ts +3 -1
- package/src/tools/Chat/Chat.story.tsx +1006 -0
- package/src/tools/Chat/README.md +528 -0
- package/src/tools/Chat/components/Attachments.tsx +192 -0
- package/src/tools/Chat/components/ChatRoot.tsx +201 -0
- package/src/tools/Chat/components/Composer.tsx +134 -0
- package/src/tools/Chat/components/EmptyState.tsx +47 -0
- package/src/tools/Chat/components/ErrorBanner.tsx +47 -0
- package/src/tools/Chat/components/JumpToLatest.tsx +30 -0
- package/src/tools/Chat/components/MessageActions.tsx +72 -0
- package/src/tools/Chat/components/MessageBubble.tsx +228 -0
- package/src/tools/Chat/components/MessageList.tsx +82 -0
- package/src/tools/Chat/components/Sources.tsx +55 -0
- package/src/tools/Chat/components/StreamingIndicator.tsx +29 -0
- package/src/tools/Chat/components/ToolCalls.tsx +172 -0
- package/src/tools/Chat/components/index.ts +24 -0
- package/src/tools/Chat/config.ts +55 -0
- package/src/tools/Chat/context/ChatProvider.tsx +122 -0
- package/src/tools/Chat/context/index.ts +9 -0
- package/src/tools/Chat/core/audio/audioBus.ts +172 -0
- package/src/tools/Chat/core/audio/index.ts +8 -0
- package/src/tools/Chat/core/audio/preferences.ts +68 -0
- package/src/tools/Chat/core/audio/types.ts +49 -0
- package/src/tools/Chat/core/ids.ts +16 -0
- package/src/tools/Chat/core/index.ts +5 -0
- package/src/tools/Chat/core/markdown.ts +56 -0
- package/src/tools/Chat/core/payload-dispatch.ts +54 -0
- package/src/tools/Chat/core/persona.ts +35 -0
- package/src/tools/Chat/core/reducer.ts +335 -0
- package/src/tools/Chat/core/transport/http.ts +167 -0
- package/src/tools/Chat/core/transport/index.ts +13 -0
- package/src/tools/Chat/core/transport/mock.ts +134 -0
- package/src/tools/Chat/core/transport/sse.ts +116 -0
- package/src/tools/Chat/core/transport/types.ts +24 -0
- package/src/tools/Chat/hooks/index.ts +26 -0
- package/src/tools/Chat/hooks/useChat.ts +440 -0
- package/src/tools/Chat/hooks/useChatAudio.ts +191 -0
- package/src/tools/Chat/hooks/useChatComposer.ts +227 -0
- package/src/tools/Chat/hooks/useChatHistory.ts +59 -0
- package/src/tools/Chat/hooks/useChatLayout.ts +111 -0
- package/src/tools/Chat/hooks/useChatLightbox.ts +34 -0
- package/src/tools/Chat/hooks/useChatScroll.ts +132 -0
- package/src/tools/Chat/index.ts +158 -0
- package/src/tools/Chat/lazy.tsx +14 -0
- package/src/tools/Chat/types.ts +237 -0
- package/src/tools/Chat/utils/collectImageAttachments.ts +13 -0
- package/src/tools/Map/README.md +384 -0
- package/dist/DocsLayout-CTJINVBM.mjs.map +0 -1
- package/dist/DocsLayout-XLDB6CJ2.cjs.map +0 -1
- package/dist/JsonSchemaForm-6WMS4CIY.cjs +0 -13
- package/dist/JsonSchemaForm-KX4JT3M4.mjs +0 -4
- package/dist/JsonTree-F27RMYSI.cjs +0 -11
- package/dist/JsonTree-QTJYSHCV.mjs +0 -5
- package/dist/Player-M3GC3VPE.mjs +0 -4
- package/dist/Player-ZL2X5LGG.cjs +0 -13
- package/dist/TreeRoot-A3J65L6F.mjs +0 -4
- package/dist/TreeRoot-DSK5JILT.cjs +0 -19
- package/dist/chunk-62Y65TGK.mjs.map +0 -1
- package/dist/chunk-TKSFZHCG.cjs +0 -1597
- package/dist/chunk-TKSFZHCG.cjs.map +0 -1
- package/dist/components-5UXYNAKR.cjs +0 -22
- package/dist/components-CFXOEVPN.mjs +0 -5
- package/dist/components-WYEZL5TE.cjs +0 -26
- package/dist/components-ZAGG2PBO.mjs +0 -5
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Copy, Pencil, RefreshCw, Trash } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
|
+
|
|
7
|
+
import type { ChatRole } from '../types';
|
|
8
|
+
|
|
9
|
+
export interface MessageActionsProps {
|
|
10
|
+
role: ChatRole;
|
|
11
|
+
onCopy?: () => void;
|
|
12
|
+
onRegenerate?: () => void;
|
|
13
|
+
onEdit?: () => void;
|
|
14
|
+
onDelete?: () => void;
|
|
15
|
+
hideOn?: Array<ChatRole>;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function MessageActions({
|
|
20
|
+
role,
|
|
21
|
+
onCopy,
|
|
22
|
+
onRegenerate,
|
|
23
|
+
onEdit,
|
|
24
|
+
onDelete,
|
|
25
|
+
hideOn,
|
|
26
|
+
className,
|
|
27
|
+
}: MessageActionsProps) {
|
|
28
|
+
if (hideOn?.includes(role)) return null;
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={cn(
|
|
32
|
+
'mt-1 flex items-center gap-0.5 opacity-0 transition-opacity group-hover/msg:opacity-100 focus-within:opacity-100',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
>
|
|
36
|
+
{onCopy ? <ActionButton onClick={onCopy} label="Copy" icon={Copy} /> : null}
|
|
37
|
+
{onRegenerate && role === 'assistant' ? (
|
|
38
|
+
<ActionButton onClick={onRegenerate} label="Regenerate" icon={RefreshCw} />
|
|
39
|
+
) : null}
|
|
40
|
+
{onEdit && role === 'user' ? (
|
|
41
|
+
<ActionButton onClick={onEdit} label="Edit" icon={Pencil} />
|
|
42
|
+
) : null}
|
|
43
|
+
{onDelete ? <ActionButton onClick={onDelete} label="Delete" icon={Trash} destructive /> : null}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ActionButton({
|
|
49
|
+
onClick,
|
|
50
|
+
label,
|
|
51
|
+
icon: Icon,
|
|
52
|
+
destructive,
|
|
53
|
+
}: {
|
|
54
|
+
onClick: () => void;
|
|
55
|
+
label: string;
|
|
56
|
+
icon: typeof Copy;
|
|
57
|
+
destructive?: boolean;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
onClick={onClick}
|
|
63
|
+
aria-label={label}
|
|
64
|
+
className={cn(
|
|
65
|
+
'rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
66
|
+
destructive && 'hover:bg-destructive/15 hover:text-destructive',
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<Icon aria-hidden className="size-3" />
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo, type ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
|
+
|
|
8
|
+
import { MarkdownMessage } from '../../../components/markdown';
|
|
9
|
+
import type {
|
|
10
|
+
ChatAssistantContext,
|
|
11
|
+
ChatAttachment,
|
|
12
|
+
ChatMessage,
|
|
13
|
+
ChatSource,
|
|
14
|
+
ChatToolCall,
|
|
15
|
+
ChatUserContext,
|
|
16
|
+
} from '../types';
|
|
17
|
+
import { resolvePersona, deriveInitials } from '../core/persona';
|
|
18
|
+
import { useChatContextOptional } from '../context';
|
|
19
|
+
import { StreamingIndicator } from './StreamingIndicator';
|
|
20
|
+
import { Sources } from './Sources';
|
|
21
|
+
import { ToolCalls } from './ToolCalls';
|
|
22
|
+
import {
|
|
23
|
+
AttachmentsGrid,
|
|
24
|
+
AttachmentsList,
|
|
25
|
+
type AttachmentRendererMap,
|
|
26
|
+
} from './Attachments';
|
|
27
|
+
import { MessageActions } from './MessageActions';
|
|
28
|
+
import type { ToolCallsProps } from './ToolCalls';
|
|
29
|
+
|
|
30
|
+
export interface MessageBubbleProps {
|
|
31
|
+
message: ChatMessage;
|
|
32
|
+
isUser?: boolean;
|
|
33
|
+
showAvatar?: boolean;
|
|
34
|
+
/** Override avatar URL (skips persona resolution). */
|
|
35
|
+
avatarSrc?: string;
|
|
36
|
+
/** Override avatar fallback (skips persona resolution). */
|
|
37
|
+
avatarFallback?: ReactNode;
|
|
38
|
+
/** Personas — when provided, take precedence over context. */
|
|
39
|
+
user?: ChatUserContext;
|
|
40
|
+
assistant?: ChatAssistantContext;
|
|
41
|
+
showTimestamp?: boolean;
|
|
42
|
+
showActions?: boolean;
|
|
43
|
+
isCompact?: boolean;
|
|
44
|
+
className?: string;
|
|
45
|
+
beforeContent?: ReactNode;
|
|
46
|
+
afterContent?: ReactNode;
|
|
47
|
+
toolCallsRenderer?: (calls: ChatToolCall[]) => ReactNode;
|
|
48
|
+
/** Forwarded to the default `<ToolCalls>` when `toolCallsRenderer` is not set. */
|
|
49
|
+
toolCallsProps?: Omit<ToolCallsProps, 'calls'>;
|
|
50
|
+
sourcesRenderer?: (sources: ChatSource[]) => ReactNode;
|
|
51
|
+
attachmentsRenderer?: (atts: ChatAttachment[]) => ReactNode;
|
|
52
|
+
/** Per-type attachment renderers forwarded to default `<Attachments>`. */
|
|
53
|
+
attachmentRenderers?: AttachmentRendererMap;
|
|
54
|
+
/** Click handler for attachment tiles (e.g. open lightbox). */
|
|
55
|
+
onAttachmentOpen?: (a: ChatAttachment) => void;
|
|
56
|
+
onCopy?: () => void;
|
|
57
|
+
onRegenerate?: () => void;
|
|
58
|
+
onEdit?: () => void;
|
|
59
|
+
onDelete?: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const MessageBubbleInner = ({
|
|
63
|
+
message,
|
|
64
|
+
isUser: isUserProp,
|
|
65
|
+
showAvatar = true,
|
|
66
|
+
avatarSrc,
|
|
67
|
+
avatarFallback,
|
|
68
|
+
user,
|
|
69
|
+
assistant,
|
|
70
|
+
showTimestamp = false,
|
|
71
|
+
showActions = true,
|
|
72
|
+
isCompact = false,
|
|
73
|
+
className,
|
|
74
|
+
beforeContent,
|
|
75
|
+
afterContent,
|
|
76
|
+
toolCallsRenderer,
|
|
77
|
+
toolCallsProps,
|
|
78
|
+
sourcesRenderer,
|
|
79
|
+
attachmentsRenderer,
|
|
80
|
+
attachmentRenderers,
|
|
81
|
+
onAttachmentOpen,
|
|
82
|
+
onCopy,
|
|
83
|
+
onRegenerate,
|
|
84
|
+
onEdit,
|
|
85
|
+
onDelete,
|
|
86
|
+
}: MessageBubbleProps) => {
|
|
87
|
+
const isUser = isUserProp ?? message.role === 'user';
|
|
88
|
+
const isStreaming = !!message.isStreaming;
|
|
89
|
+
const isErr = !!message.isError;
|
|
90
|
+
|
|
91
|
+
const ctx = useChatContextOptional();
|
|
92
|
+
const persona = resolvePersona(
|
|
93
|
+
message,
|
|
94
|
+
user ?? ctx?.config.user,
|
|
95
|
+
assistant ?? ctx?.config.assistant,
|
|
96
|
+
);
|
|
97
|
+
const initials = deriveInitials(persona, message.role);
|
|
98
|
+
const personaName = persona.name ?? (isUser ? 'You' : 'Assistant');
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
role="article"
|
|
103
|
+
aria-label={`${personaName} said: ${message.content.slice(0, 80)}`}
|
|
104
|
+
aria-busy={isStreaming || undefined}
|
|
105
|
+
data-role={message.role}
|
|
106
|
+
className={cn(
|
|
107
|
+
'group/msg flex gap-2.5 px-2.5 py-2',
|
|
108
|
+
isUser ? 'flex-row-reverse' : 'flex-row',
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{showAvatar ? (
|
|
113
|
+
<Avatar
|
|
114
|
+
className="size-7 shrink-0"
|
|
115
|
+
title={persona.description ?? personaName}
|
|
116
|
+
>
|
|
117
|
+
{avatarSrc || persona.avatarUrl ? (
|
|
118
|
+
<AvatarImage src={avatarSrc ?? persona.avatarUrl} alt={personaName} />
|
|
119
|
+
) : null}
|
|
120
|
+
<AvatarFallback className="text-[10px]">
|
|
121
|
+
{avatarFallback ?? initials}
|
|
122
|
+
</AvatarFallback>
|
|
123
|
+
</Avatar>
|
|
124
|
+
) : null}
|
|
125
|
+
|
|
126
|
+
<div className={cn('min-w-0 flex-1', isUser && 'flex flex-col items-end')}>
|
|
127
|
+
{beforeContent}
|
|
128
|
+
{message.attachments?.length
|
|
129
|
+
? attachmentsRenderer
|
|
130
|
+
? attachmentsRenderer(message.attachments)
|
|
131
|
+
: (
|
|
132
|
+
<div className="mb-1.5 w-full">
|
|
133
|
+
{attachmentRenderers ? (
|
|
134
|
+
<AttachmentsList
|
|
135
|
+
attachments={message.attachments}
|
|
136
|
+
renderers={attachmentRenderers}
|
|
137
|
+
onClick={onAttachmentOpen}
|
|
138
|
+
className={isUser ? 'items-end' : undefined}
|
|
139
|
+
/>
|
|
140
|
+
) : (
|
|
141
|
+
<AttachmentsGrid
|
|
142
|
+
attachments={message.attachments}
|
|
143
|
+
onClick={onAttachmentOpen}
|
|
144
|
+
className={isUser ? 'justify-end' : undefined}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
: null}
|
|
150
|
+
|
|
151
|
+
<div
|
|
152
|
+
className={cn(
|
|
153
|
+
'inline-block max-w-full rounded-2xl px-3.5 py-2 text-sm',
|
|
154
|
+
isUser
|
|
155
|
+
? 'bg-primary text-primary-foreground rounded-tr-md'
|
|
156
|
+
: isErr
|
|
157
|
+
? 'bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30'
|
|
158
|
+
: 'bg-muted text-foreground rounded-tl-md',
|
|
159
|
+
)}
|
|
160
|
+
>
|
|
161
|
+
{isStreaming && message.toolActivity ? (
|
|
162
|
+
<div className="mb-1.5">
|
|
163
|
+
<StreamingIndicator label={message.toolActivity} />
|
|
164
|
+
</div>
|
|
165
|
+
) : null}
|
|
166
|
+
|
|
167
|
+
{message.content || !isStreaming ? (
|
|
168
|
+
<MarkdownMessage
|
|
169
|
+
content={message.content || (isErr ? '*Failed to generate a response.*' : '')}
|
|
170
|
+
isUser={isUser}
|
|
171
|
+
isCompact={isCompact}
|
|
172
|
+
plainText={isStreaming}
|
|
173
|
+
/>
|
|
174
|
+
) : (
|
|
175
|
+
<StreamingIndicator />
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{message.toolCalls?.length
|
|
180
|
+
? toolCallsRenderer
|
|
181
|
+
? toolCallsRenderer(message.toolCalls)
|
|
182
|
+
: <ToolCalls calls={message.toolCalls} {...toolCallsProps} />
|
|
183
|
+
: null}
|
|
184
|
+
|
|
185
|
+
{message.sources?.length && !isStreaming
|
|
186
|
+
? sourcesRenderer
|
|
187
|
+
? sourcesRenderer(message.sources)
|
|
188
|
+
: <Sources sources={message.sources} />
|
|
189
|
+
: null}
|
|
190
|
+
|
|
191
|
+
{showActions && !isStreaming ? (
|
|
192
|
+
<MessageActions
|
|
193
|
+
role={message.role}
|
|
194
|
+
onCopy={onCopy}
|
|
195
|
+
onRegenerate={onRegenerate}
|
|
196
|
+
onEdit={onEdit}
|
|
197
|
+
onDelete={onDelete}
|
|
198
|
+
/>
|
|
199
|
+
) : null}
|
|
200
|
+
|
|
201
|
+
{showTimestamp ? (
|
|
202
|
+
<div className="mt-1 text-[10px] text-muted-foreground">
|
|
203
|
+
{new Date(message.createdAt).toLocaleTimeString()}
|
|
204
|
+
</div>
|
|
205
|
+
) : null}
|
|
206
|
+
|
|
207
|
+
{afterContent}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const MessageBubble = memo(MessageBubbleInner, (prev, next) => {
|
|
214
|
+
const a = prev.message;
|
|
215
|
+
const b = next.message;
|
|
216
|
+
return (
|
|
217
|
+
a.id === b.id &&
|
|
218
|
+
a.content === b.content &&
|
|
219
|
+
a.isStreaming === b.isStreaming &&
|
|
220
|
+
a.isError === b.isError &&
|
|
221
|
+
(a.version ?? 0) === (b.version ?? 0) &&
|
|
222
|
+
a.toolActivity === b.toolActivity &&
|
|
223
|
+
a.toolCalls === b.toolCalls &&
|
|
224
|
+
a.sources === b.sources &&
|
|
225
|
+
a.attachments === b.attachments
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
MessageBubble.displayName = 'MessageBubble';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type RefObject, type ReactNode, forwardRef, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
|
+
import { Spinner } from '@djangocfg/ui-core/components';
|
|
7
|
+
|
|
8
|
+
import type { ChatMessage } from '../types';
|
|
9
|
+
import { useChatContextOptional } from '../context';
|
|
10
|
+
import { MessageBubble } from './MessageBubble';
|
|
11
|
+
|
|
12
|
+
export interface MessageListProps {
|
|
13
|
+
messages?: ChatMessage[];
|
|
14
|
+
renderItem?: (m: ChatMessage, i: number) => ReactNode;
|
|
15
|
+
renderEmpty?: () => ReactNode;
|
|
16
|
+
isLoadingMore?: boolean;
|
|
17
|
+
topSentinelRef?: RefObject<HTMLDivElement | null>;
|
|
18
|
+
bottomRef?: RefObject<HTMLDivElement | null>;
|
|
19
|
+
className?: string;
|
|
20
|
+
itemClassName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const MessageList = forwardRef<HTMLDivElement, MessageListProps>(function MessageList(
|
|
24
|
+
{
|
|
25
|
+
messages: messagesProp,
|
|
26
|
+
renderItem,
|
|
27
|
+
renderEmpty,
|
|
28
|
+
isLoadingMore: isLoadingMoreProp,
|
|
29
|
+
topSentinelRef,
|
|
30
|
+
bottomRef,
|
|
31
|
+
className,
|
|
32
|
+
itemClassName,
|
|
33
|
+
},
|
|
34
|
+
ref,
|
|
35
|
+
) {
|
|
36
|
+
const ctx = useChatContextOptional();
|
|
37
|
+
const messages = messagesProp ?? ctx?.messages ?? [];
|
|
38
|
+
const isLoadingMore = isLoadingMoreProp ?? ctx?.isLoadingMore ?? false;
|
|
39
|
+
|
|
40
|
+
const defaultRenderItem = useCallback(
|
|
41
|
+
(m: ChatMessage) => (
|
|
42
|
+
<div className={itemClassName} key={m.id}>
|
|
43
|
+
<MessageBubble
|
|
44
|
+
message={m}
|
|
45
|
+
onCopy={() => copy(m.content)}
|
|
46
|
+
onRegenerate={ctx ? () => void ctx.regenerate(m.id) : undefined}
|
|
47
|
+
onDelete={ctx ? () => ctx.deleteMessage(m.id) : undefined}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
),
|
|
51
|
+
[itemClassName, ctx],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const itemRenderer = renderItem ?? defaultRenderItem;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
ref={ref}
|
|
59
|
+
role="log"
|
|
60
|
+
aria-live="polite"
|
|
61
|
+
aria-atomic="false"
|
|
62
|
+
className={cn('flex-1 overflow-y-auto', className)}
|
|
63
|
+
>
|
|
64
|
+
<div ref={topSentinelRef} aria-hidden />
|
|
65
|
+
{isLoadingMore ? (
|
|
66
|
+
<div className="flex justify-center py-2">
|
|
67
|
+
<Spinner className="size-4 text-muted-foreground" />
|
|
68
|
+
</div>
|
|
69
|
+
) : null}
|
|
70
|
+
{messages.length === 0
|
|
71
|
+
? renderEmpty?.() ?? null
|
|
72
|
+
: messages.map((m, i) => itemRenderer(m, i))}
|
|
73
|
+
<div ref={bottomRef} aria-hidden />
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function copy(text: string) {
|
|
79
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
80
|
+
void navigator.clipboard.writeText(text);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ExternalLink } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
|
+
|
|
7
|
+
import type { ChatSource } from '../types';
|
|
8
|
+
|
|
9
|
+
export interface SourcesProps {
|
|
10
|
+
sources: ChatSource[];
|
|
11
|
+
layout?: 'inline' | 'grid';
|
|
12
|
+
maxVisible?: number;
|
|
13
|
+
onClick?: (source: ChatSource) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Sources({ sources, layout = 'inline', maxVisible, onClick, className }: SourcesProps) {
|
|
18
|
+
if (!sources?.length) return null;
|
|
19
|
+
const visible = maxVisible ? sources.slice(0, maxVisible) : sources;
|
|
20
|
+
const remaining = maxVisible ? Math.max(0, sources.length - maxVisible) : 0;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={cn(
|
|
25
|
+
'mt-2 flex flex-wrap gap-1.5',
|
|
26
|
+
layout === 'grid' && 'grid grid-cols-2',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{visible.map((s, i) => {
|
|
31
|
+
const handle = onClick ? () => onClick(s) : undefined;
|
|
32
|
+
const Tag = handle ? 'button' : 'a';
|
|
33
|
+
const props = handle
|
|
34
|
+
? ({ type: 'button', onClick: handle } as const)
|
|
35
|
+
: ({ href: s.url, target: '_blank', rel: 'noopener noreferrer' } as const);
|
|
36
|
+
return (
|
|
37
|
+
<Tag
|
|
38
|
+
key={`${s.url}-${i}`}
|
|
39
|
+
{...props}
|
|
40
|
+
className="inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background/60 px-2 py-1 text-xs text-foreground/80 hover:bg-accent hover:text-foreground"
|
|
41
|
+
title={s.snippet ?? s.title}
|
|
42
|
+
>
|
|
43
|
+
<span className="truncate">{s.title || s.url}</span>
|
|
44
|
+
<ExternalLink aria-hidden className="size-3 shrink-0 opacity-60" />
|
|
45
|
+
</Tag>
|
|
46
|
+
);
|
|
47
|
+
})}
|
|
48
|
+
{remaining > 0 ? (
|
|
49
|
+
<span className="inline-flex items-center rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground">
|
|
50
|
+
+{remaining}
|
|
51
|
+
</span>
|
|
52
|
+
) : null}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
4
|
+
|
|
5
|
+
export interface StreamingIndicatorProps {
|
|
6
|
+
variant?: 'dots' | 'pulse';
|
|
7
|
+
label?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function StreamingIndicator({ variant = 'dots', label, className }: StreamingIndicatorProps) {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
className={cn('inline-flex items-center gap-1.5 text-xs text-muted-foreground', className)}
|
|
15
|
+
aria-live="off"
|
|
16
|
+
>
|
|
17
|
+
{variant === 'dots' ? (
|
|
18
|
+
<span className="inline-flex gap-0.5" aria-hidden>
|
|
19
|
+
<span className="size-1 animate-bounce rounded-full bg-current [animation-delay:-0.2s]" />
|
|
20
|
+
<span className="size-1 animate-bounce rounded-full bg-current [animation-delay:-0.1s]" />
|
|
21
|
+
<span className="size-1 animate-bounce rounded-full bg-current" />
|
|
22
|
+
</span>
|
|
23
|
+
) : (
|
|
24
|
+
<span className="inline-block size-1.5 animate-pulse rounded-full bg-current" aria-hidden />
|
|
25
|
+
)}
|
|
26
|
+
{label ? <span className="italic">{label}</span> : null}
|
|
27
|
+
</span>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
7
|
+
|
|
8
|
+
import type { ChatToolCall } from '../types';
|
|
9
|
+
|
|
10
|
+
export type ToolPayloadKind = 'input' | 'output' | 'streaming';
|
|
11
|
+
|
|
12
|
+
export interface ToolCallsProps {
|
|
13
|
+
calls: ChatToolCall[];
|
|
14
|
+
/** Open every panel up-front. Default: false (panels are closed). */
|
|
15
|
+
defaultExpanded?: boolean;
|
|
16
|
+
/** Auto-open while a tool is running, then auto-close on completion.
|
|
17
|
+
* User toggles after that are remembered. Default: true. */
|
|
18
|
+
expandWhileStreaming?: boolean;
|
|
19
|
+
/** Override how the tool input payload is rendered. Receives the raw value. */
|
|
20
|
+
renderInput?: (input: unknown, call: ChatToolCall) => ReactNode;
|
|
21
|
+
/** Override how the tool output payload is rendered. */
|
|
22
|
+
renderOutput?: (output: unknown, call: ChatToolCall) => ReactNode;
|
|
23
|
+
/** Override how the live `streamingText` is rendered. */
|
|
24
|
+
renderStreaming?: (text: string, call: ChatToolCall) => ReactNode;
|
|
25
|
+
/** Single override for all three; specific renderers above take precedence. */
|
|
26
|
+
renderPayload?: (value: unknown, kind: ToolPayloadKind, call: ChatToolCall) => ReactNode;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ToolCalls({
|
|
31
|
+
calls,
|
|
32
|
+
defaultExpanded = false,
|
|
33
|
+
expandWhileStreaming = true,
|
|
34
|
+
renderInput,
|
|
35
|
+
renderOutput,
|
|
36
|
+
renderStreaming,
|
|
37
|
+
renderPayload,
|
|
38
|
+
className,
|
|
39
|
+
}: ToolCallsProps) {
|
|
40
|
+
if (!calls?.length) return null;
|
|
41
|
+
return (
|
|
42
|
+
<div className={cn('mt-2 space-y-1.5', className)}>
|
|
43
|
+
{calls.map((call) => (
|
|
44
|
+
<ToolCallItem
|
|
45
|
+
key={call.id}
|
|
46
|
+
call={call}
|
|
47
|
+
defaultExpanded={defaultExpanded}
|
|
48
|
+
expandWhileStreaming={expandWhileStreaming}
|
|
49
|
+
renderInput={renderInput}
|
|
50
|
+
renderOutput={renderOutput}
|
|
51
|
+
renderStreaming={renderStreaming}
|
|
52
|
+
renderPayload={renderPayload}
|
|
53
|
+
/>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ItemProps {
|
|
60
|
+
call: ChatToolCall;
|
|
61
|
+
defaultExpanded: boolean;
|
|
62
|
+
expandWhileStreaming: boolean;
|
|
63
|
+
renderInput?: ToolCallsProps['renderInput'];
|
|
64
|
+
renderOutput?: ToolCallsProps['renderOutput'];
|
|
65
|
+
renderStreaming?: ToolCallsProps['renderStreaming'];
|
|
66
|
+
renderPayload?: ToolCallsProps['renderPayload'];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ToolCallItem({
|
|
70
|
+
call,
|
|
71
|
+
defaultExpanded,
|
|
72
|
+
expandWhileStreaming,
|
|
73
|
+
renderInput,
|
|
74
|
+
renderOutput,
|
|
75
|
+
renderStreaming,
|
|
76
|
+
renderPayload,
|
|
77
|
+
}: ItemProps) {
|
|
78
|
+
const isRunning = call.status === 'running';
|
|
79
|
+
const initialOpen = defaultExpanded || (expandWhileStreaming && isRunning);
|
|
80
|
+
const [open, setOpen] = useState(initialOpen);
|
|
81
|
+
// Remember manual interaction so completion doesn't override it.
|
|
82
|
+
const userToggledRef = useRef(false);
|
|
83
|
+
const wasRunningRef = useRef(isRunning);
|
|
84
|
+
|
|
85
|
+
// Auto-collapse on running → completed transition, unless user has interacted.
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (wasRunningRef.current && !isRunning) {
|
|
88
|
+
if (!userToggledRef.current && !defaultExpanded) {
|
|
89
|
+
setOpen(false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
wasRunningRef.current = isRunning;
|
|
93
|
+
}, [isRunning, defaultExpanded]);
|
|
94
|
+
|
|
95
|
+
const handleToggle = () => {
|
|
96
|
+
userToggledRef.current = true;
|
|
97
|
+
setOpen((v) => !v);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const Icon = open ? ChevronDown : ChevronRight;
|
|
101
|
+
const statusColor =
|
|
102
|
+
call.status === 'success'
|
|
103
|
+
? 'text-emerald-500'
|
|
104
|
+
: call.status === 'error'
|
|
105
|
+
? 'text-destructive'
|
|
106
|
+
: call.status === 'cancelled'
|
|
107
|
+
? 'text-muted-foreground'
|
|
108
|
+
: 'text-amber-500';
|
|
109
|
+
|
|
110
|
+
const renderValue = (value: unknown, kind: ToolPayloadKind): ReactNode => {
|
|
111
|
+
if (kind === 'input' && renderInput) return renderInput(value, call);
|
|
112
|
+
if (kind === 'output' && renderOutput) return renderOutput(value, call);
|
|
113
|
+
if (kind === 'streaming' && renderStreaming)
|
|
114
|
+
return renderStreaming(typeof value === 'string' ? value : String(value), call);
|
|
115
|
+
if (renderPayload) return renderPayload(value, kind, call);
|
|
116
|
+
return <DefaultPayload value={value} kind={kind} />;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className="overflow-hidden rounded-md border border-border bg-muted/30">
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
onClick={handleToggle}
|
|
124
|
+
aria-expanded={open}
|
|
125
|
+
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-muted/60"
|
|
126
|
+
>
|
|
127
|
+
<Icon aria-hidden className="size-3 shrink-0 text-muted-foreground" />
|
|
128
|
+
{isRunning ? (
|
|
129
|
+
<Loader2 aria-hidden className="size-3 shrink-0 animate-spin text-amber-500" />
|
|
130
|
+
) : (
|
|
131
|
+
<span className={cn('size-2 shrink-0 rounded-full', statusColor.replace('text-', 'bg-'))} />
|
|
132
|
+
)}
|
|
133
|
+
<span className="font-mono text-foreground">{call.name}</span>
|
|
134
|
+
<span className={cn('ml-auto', statusColor)}>{call.status}</span>
|
|
135
|
+
</button>
|
|
136
|
+
{open ? (
|
|
137
|
+
<div className="space-y-1 border-t border-border px-2 py-1.5 text-[11px]">
|
|
138
|
+
{call.input != null ? renderValue(call.input, 'input') : null}
|
|
139
|
+
{call.streamingText != null
|
|
140
|
+
? renderValue(call.streamingText, 'streaming')
|
|
141
|
+
: call.output !== undefined
|
|
142
|
+
? renderValue(call.output, 'output')
|
|
143
|
+
: null}
|
|
144
|
+
</div>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function DefaultPayload({ value, kind }: { value: unknown; kind: ToolPayloadKind }) {
|
|
151
|
+
const isStreamingOrString = kind === 'streaming' || typeof value === 'string';
|
|
152
|
+
const muted = kind === 'input';
|
|
153
|
+
return (
|
|
154
|
+
<pre
|
|
155
|
+
className={cn(
|
|
156
|
+
'overflow-auto rounded bg-background/60 p-1.5 font-mono',
|
|
157
|
+
kind === 'input' ? 'max-h-32' : 'max-h-48',
|
|
158
|
+
muted ? 'text-muted-foreground' : 'text-foreground/90',
|
|
159
|
+
)}
|
|
160
|
+
>
|
|
161
|
+
{isStreamingOrString ? String(value) : safeStringify(value)}
|
|
162
|
+
</pre>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function safeStringify(value: unknown): string {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.stringify(value, null, 2);
|
|
169
|
+
} catch {
|
|
170
|
+
return String(value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { ChatRoot, type ChatRootProps } from './ChatRoot';
|
|
4
|
+
export { MessageList, type MessageListProps } from './MessageList';
|
|
5
|
+
export { MessageBubble, type MessageBubbleProps } from './MessageBubble';
|
|
6
|
+
export { MessageActions, type MessageActionsProps } from './MessageActions';
|
|
7
|
+
export { Composer, type ComposerProps } from './Composer';
|
|
8
|
+
export { Sources, type SourcesProps } from './Sources';
|
|
9
|
+
export { ToolCalls, type ToolCallsProps, type ToolPayloadKind } from './ToolCalls';
|
|
10
|
+
export {
|
|
11
|
+
Attachments,
|
|
12
|
+
AttachmentsGrid,
|
|
13
|
+
AttachmentsList,
|
|
14
|
+
type AttachmentsProps,
|
|
15
|
+
type AttachmentsGridProps,
|
|
16
|
+
type AttachmentsListProps,
|
|
17
|
+
type AttachmentRenderer,
|
|
18
|
+
type AttachmentRendererArgs,
|
|
19
|
+
type AttachmentRendererMap,
|
|
20
|
+
} from './Attachments';
|
|
21
|
+
export { EmptyState, type EmptyStateProps } from './EmptyState';
|
|
22
|
+
export { ErrorBanner, type ErrorBannerProps } from './ErrorBanner';
|
|
23
|
+
export { JumpToLatest, type JumpToLatestProps } from './JumpToLatest';
|
|
24
|
+
export { StreamingIndicator, type StreamingIndicatorProps } from './StreamingIndicator';
|