1
- import { Bot , Copy , Download } from 'lucide-react'
1
+ import { cn } from '../lib/utils'
2
+ import type { Message } from '../mcp/client'
3
+ import { formatTimestamp } from '../lib/utils'
4
+ import { Bot , Copy } from 'lucide-react'
5
+ import { MarkdownContent } from './MarkdownContent'
6
+
2
7
import { toast } from 'sonner'
3
- import { useState } from 'react'
4
- import { cn } from '@/lib/utils'
5
- import type { Message } from '@/mcp/client'
6
- import { formatTimestamp } from '@/lib/utils'
7
- import { MarkdownContent } from '@/components/MarkdownContent'
8
- import { copyToClipboard } from '@/lib/utils/clipboard'
9
- import {
10
- isImageFile ,
11
- createAnnotatedFileUrl ,
12
- } from '@/lib/utils/code-interpreter'
8
+ import { copyToClipboard } from '../lib/utils/clipboard'
13
9
14
10
export interface BotMessageProps {
15
11
message : Message
16
12
isLoading ?: boolean
17
- fileAnnotations ?: Array < {
18
- type : string
19
- container_id : string
20
- file_id : string
21
- filename : string
22
- } >
23
13
}
24
14
25
- export function BotMessage ( {
26
- message,
27
- isLoading,
28
- fileAnnotations = [ ] ,
29
- } : BotMessageProps ) {
30
- const [ imageErrors , setImageErrors ] = useState < Set < string > > ( new Set ( ) )
31
-
15
+ export function BotMessage ( { message, isLoading } : BotMessageProps ) {
32
16
const handleCopy = async ( ) => {
33
- const copied = await copyToClipboard ( processedContent )
17
+ const copied = await copyToClipboard ( message . content )
34
18
35
19
if ( copied ) {
36
20
toast . success ( 'Copied message to clipboard' )
37
21
}
38
22
}
39
23
40
- const handleImageError = ( fileId : string ) => {
41
- setImageErrors ( ( prev ) => new Set ( prev ) . add ( fileId ) )
42
- }
43
-
44
- // Use message content directly; sandbox URL replacement is not needed
45
- const processedContent = message . content
46
-
47
- // Separate image and non-image files
48
- const imageFiles = fileAnnotations . filter ( ( annotation ) =>
49
- isImageFile ( annotation . filename ) ,
50
- )
51
- const otherFiles = fileAnnotations . filter (
52
- ( annotation ) => ! isImageFile ( annotation . filename ) ,
53
- )
54
-
55
24
return (
56
25
< div
57
26
className = { cn (
@@ -78,95 +47,11 @@ export function BotMessage({
78
47
'rounded-2xl px-4 py-2 text-sm w-full bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-100' ,
79
48
) }
80
49
>
81
- < div data-raw-markdown = { processedContent } >
50
+ < div data-raw-markdown = { message . content } >
82
51
< div className = "prose prose-sm dark:prose-invert max-w-none break-words break-all whitespace-pre-wrap" >
83
- < MarkdownContent content = { processedContent } />
52
+ < MarkdownContent content = { message . content } />
84
53
</ div >
85
54
</ div >
86
-
87
- { /* Image Gallery */ }
88
- { imageFiles . length > 0 && (
89
- < div className = "mt-4 space-y-3" >
90
- { imageFiles . map ( ( annotation ) => {
91
- const hasError = imageErrors . has ( annotation . file_id )
92
- const imageUrl = createAnnotatedFileUrl ( annotation )
93
-
94
- // Generate accessible alt text based on filename
95
- const getAltText = ( filename : string ) => {
96
- const name = filename . replace ( / \. [ ^ / . ] + $ / , '' ) // Remove extension
97
- return `Generated visualization: ${ name . replace ( / _ / g, ' ' ) } `
98
- }
99
-
100
- return (
101
- < div key = { annotation . file_id } className = "relative group" >
102
- { hasError ? (
103
- < div className = "flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 rounded border border-dashed border-gray-300 dark:border-gray-600" >
104
- < div className = "text-center" >
105
- < p className = "text-sm text-gray-500 dark:text-gray-400" >
106
- Failed to load image
107
- </ p >
108
- < a
109
- href = { createAnnotatedFileUrl ( annotation ) }
110
- download = { annotation . filename }
111
- className = "mt-2 text-xs text-blue-600 dark:text-blue-400 hover:underline"
112
- >
113
- Download file instead
114
- </ a >
115
- </ div >
116
- </ div >
117
- ) : (
118
- < >
119
- < img
120
- src = { imageUrl }
121
- alt = { getAltText ( annotation . filename ) }
122
- className = "max-w-full h-auto rounded border border-gray-200 dark:border-gray-700 cursor-pointer"
123
- onError = { ( ) => handleImageError ( annotation . file_id ) }
124
- onClick = { ( ) => window . open ( imageUrl , '_blank' ) }
125
- />
126
- < a
127
- href = { createAnnotatedFileUrl ( annotation ) }
128
- download = { annotation . filename }
129
- className = "absolute top-2 right-2 p-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-full shadow-sm opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white dark:hover:bg-gray-800"
130
- title = "Download image"
131
- >
132
- < Download className = "h-4 w-4 text-gray-600 dark:text-gray-300" />
133
- </ a >
134
- </ >
135
- ) }
136
- </ div >
137
- )
138
- } ) }
139
- </ div >
140
- ) }
141
-
142
- { /* Other Files */ }
143
- { otherFiles . length > 0 && (
144
- < div className = "mt-4 space-y-2" >
145
- < p className = "text-xs text-gray-500 dark:text-gray-400 font-medium" >
146
- Attachments:
147
- </ p >
148
- < div className = "space-y-1" >
149
- { otherFiles . map ( ( annotation ) => (
150
- < div
151
- key = { annotation . file_id }
152
- className = "flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-700"
153
- >
154
- < span className = "text-sm text-gray-700 dark:text-gray-300 truncate" >
155
- { annotation . filename }
156
- </ span >
157
- < a
158
- href = { createAnnotatedFileUrl ( annotation ) }
159
- download = { annotation . filename }
160
- className = "flex items-center gap-1 px-2 py-1 text-xs text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded"
161
- >
162
- < Download className = "h-3 w-3" />
163
- Download
164
- </ a >
165
- </ div >
166
- ) ) }
167
- </ div >
168
- </ div >
169
- ) }
170
55
</ div >
171
56
< div className = "flex gap-1 items-center text-xs text-gray-500 dark:text-gray-400 space-x-1" >
172
57
< time dateTime = { message . timestamp . toISOString ( ) } >
0 commit comments