@@ -15,8 +15,6 @@ import {
1515 MessageToolbar ,
1616 MessageActions ,
1717 MessageAction ,
18- MessageAttachment ,
19- MessageAttachments ,
2018} from '@/src/components/ai-elements/message'
2119import { Loader } from '@/src/components/ai-elements/loader'
2220import {
@@ -54,13 +52,57 @@ import {
5452 ActivityIcon ,
5553 NetworkIcon ,
5654} from 'lucide-react'
57- import { useState , useCallback , useMemo , Fragment } from 'react'
58- import type { UIMessage , FileUIPart } from 'ai'
55+ import { useState , useCallback , useMemo , Fragment , memo } from 'react'
56+ import type {
57+ UIMessage ,
58+ DynamicToolUIPart ,
59+ TextUIPart ,
60+ ReasoningUIPart ,
61+ ToolUIPart ,
62+ TextStreamPart ,
63+ TextPart ,
64+ ToolResultPart ,
65+ ReasoningOutput ,
66+ UIDataPartSchemas ,
67+ UIMessageChunk ,
68+ UIMessagePart ,
69+ DataContent ,
70+ FinishReason ,
71+ FileUIPart ,
72+ Tool ,
73+ DataUIPart ,
74+ SourceDocumentUIPart ,
75+ SourceUrlUIPart ,
76+ StepResult ,
77+ PrepareStepResult ,
78+ StepStartUIPart ,
79+ InferSchema ,
80+ InferUIDataParts ,
81+ InferAgentUIMessage ,
82+ InferToolInput ,
83+ InferUIMessageChunk ,
84+ InferToolOutput ,
85+ InferUITool ,
86+ InferUITools ,
87+ InferGenerateOutput ,
88+ InferStreamOutput ,
89+ } from 'ai'
5990import {
60- isTextUIPart ,
61- isReasoningUIPart ,
62- isToolOrDynamicToolUIPart ,
91+ safeValidateUIMessages ,
92+ getToolName ,
93+ getStaticToolName ,
94+ getTextFromDataUrl ,
95+ isDataUIPart ,
6396 isFileUIPart ,
97+ isReasoningUIPart ,
98+ isTextUIPart ,
99+ isToolUIPart ,
100+ isStaticToolUIPart ,
101+ isDeepEqualData ,
102+ InvalidResponseDataError ,
103+ InvalidMessageRoleError ,
104+ InvalidArgumentError ,
105+ generateId
64106} from 'ai'
65107import type { BundledLanguage } from 'shiki'
66108import { Button } from '@/ui/button'
@@ -84,6 +126,58 @@ type MastraDataPart =
84126 | NetworkDataPart
85127 | { type : `data-${string } `; id ?: string ; data : unknown }
86128
129+ interface ChatMessagesProps {
130+ messages : UIMessage [ ]
131+ status : string
132+ error : Error | undefined
133+ onSuggestionClick : ( suggestion : string ) => void
134+ onCopyMessage ?: ( messageId : string , content : string ) => void
135+ onRegenerate ?: ( messageId : string ) => void
136+ }
137+
138+ interface SourceDocument {
139+ title ?: string
140+ url ?: string
141+ description ?: string
142+ sourceDocument ?: string
143+ }
144+
145+
146+ function isSourceUrlPart ( part : UIMessage [ 'parts' ] [ number ] ) : part is SourceUrlUIPart {
147+ return part . type === 'source-url'
148+ }
149+
150+ function isSourceDocumentPart (
151+ part : UIMessage [ 'parts' ] [ number ]
152+ ) : part is SourceDocumentUIPart {
153+ return part . type === 'source-document'
154+ }
155+
156+
157+ // Extract sources from message parts
158+ const getSourcesFromParts = ( parts : UIMessage [ 'parts' ] ) : SourceDocument [ ] => {
159+ const sources : SourceDocument [ ] = [ ]
160+ for ( const part of parts ) {
161+ if ( isSourceUrlPart ( part ) ) {
162+ sources . push ( {
163+ title : part . title ,
164+ url : part . url ,
165+ description : part . url ,
166+ } )
167+ continue
168+ }
169+
170+ if ( isSourceDocumentPart ( part ) ) {
171+ sources . push ( {
172+ title : part . title ,
173+ description : part . filename ?? part . mediaType ,
174+ } )
175+ }
176+ }
177+ return sources
178+ }
179+
180+
87181/**
88182 * Type guard to check for type property
89183 */
@@ -207,8 +301,8 @@ function WorkflowDataSection({ part }: { part: WorkflowDataPart }) {
207301 workflowData . status === 'success' || ( workflowData . status as string ) === 'completed'
208302 ? 'default'
209303 : workflowData . status === 'failed'
210- ? 'destructive'
211- : 'secondary'
304+ ? 'destructive'
305+ : 'secondary'
212306 }
213307 className = "text-xs"
214308 >
@@ -243,8 +337,8 @@ function WorkflowDataSection({ part }: { part: WorkflowDataPart }) {
243337 ? 'default'
244338 : stepData . status ===
245339 'failed'
246- ? 'destructive'
247- : 'secondary'
340+ ? 'destructive'
341+ : 'secondary'
248342 }
249343 className = "text-xs"
250344 >
@@ -364,8 +458,8 @@ function NetworkDataSection({ part }: { part: NetworkDataPart }) {
364458 step . status === 'success' || ( step . status as string ) === 'completed'
365459 ? 'default'
366460 : step . status === 'failed'
367- ? 'destructive'
368- : 'secondary'
461+ ? 'destructive'
462+ : 'secondary'
369463 }
370464 className = "text-xs"
371465 >
@@ -726,7 +820,7 @@ function MessageItem({
726820 const tools : ToolInvocationState [ ] = [ ]
727821
728822 for ( const p of parts ) {
729- if ( isToolOrDynamicToolUIPart ( p ) ) {
823+ if ( isToolUIPart ( p ) ) {
730824 tools . push ( p as ToolInvocationState )
731825 }
732826 }
@@ -778,8 +872,8 @@ function MessageItem({
778872 statusText === 'in-progress'
779873 ? 'in-progress'
780874 : statusText . trim ( ) . length > 0
781- ? 'done'
782- : ''
875+ ? 'done'
876+ : ''
783877 return {
784878 message : messageText ,
785879 status : normalizedStatus ,
@@ -904,14 +998,20 @@ function MessageItem({
904998 < MessageContent >
905999 { /* User file attachments */ }
9061000 { isUser && fileParts && fileParts . length > 0 && (
907- < MessageAttachments >
1001+ < div className = "my-2 flex flex-wrap gap-2" >
9081002 { fileParts . map ( ( file , idx ) => (
909- < MessageAttachment
1003+ < div
9101004 key = { `file-${ idx } ` }
911- data = { file }
912- />
1005+ className = "flex items-center gap-2 rounded-lg border bg-muted/20 px-3 py-1.5 text-xs font-medium"
1006+ >
1007+ < span className = "truncate max-w-50" >
1008+ { file . filename ??
1009+ file . mediaType ??
1010+ `File ${ idx + 1 } ` }
1011+ </ span >
1012+ </ div >
9131013 ) ) }
914- </ MessageAttachments >
1014+ </ div >
9151015 ) }
9161016
9171017 { /* Non-image files with inline preview controls */ }
0 commit comments