11import { useMemo } from 'react'
2- import { FileText } from 'lucide-react'
32import { useThrottledValue } from '@/hooks/use-throttled-value'
4-
5- function StreamingIndicator ( ) {
6- return < span className = 'inline-block h-[14px] w-[6px] animate-pulse bg-current opacity-70' />
7- }
8-
9- interface ChatAttachment {
10- id : string
11- name : string
12- type : string
13- dataUrl : string
14- size ?: number
15- }
3+ import {
4+ ChatMessageAttachments ,
5+ } from '@/app/workspace/[workspaceId]/home/components'
6+ import type { ChatMessageAttachment } from '@/app/workspace/[workspaceId]/home/types'
167
178interface ChatMessageProps {
189 message : {
@@ -21,45 +12,14 @@ interface ChatMessageProps {
2112 timestamp : string | Date
2213 type : 'user' | 'workflow'
2314 isStreaming ?: boolean
24- attachments ?: ChatAttachment [ ]
15+ attachments ?: ChatMessageAttachment [ ]
2516 }
2617}
2718
2819const MAX_WORD_LENGTH = 25
2920
30- /**
31- * Formats file size in human-readable format
32- */
33- const formatFileSize = ( bytes ?: number ) : string => {
34- if ( ! bytes || bytes === 0 ) return ''
35- const sizes = [ 'B' , 'KB' , 'MB' , 'GB' ]
36- const i = Math . floor ( Math . log ( bytes ) / Math . log ( 1024 ) )
37- return `${ Math . round ( ( bytes / 1024 ** i ) * 10 ) / 10 } ${ sizes [ i ] } `
38- }
39-
40- /**
41- * Opens image attachment in new window
42- */
43- const openImageInNewWindow = ( dataUrl : string , fileName : string ) => {
44- const newWindow = window . open ( '' , '_blank' )
45- if ( ! newWindow ) return
46-
47- newWindow . document . write ( `
48- <!DOCTYPE html>
49- <html>
50- <head>
51- <title>${ fileName } </title>
52- <style>
53- body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #000; }
54- img { max-width: 100%; max-height: 100vh; object-fit: contain; }
55- </style>
56- </head>
57- <body>
58- <img src="${ dataUrl } " alt="${ fileName } " />
59- </body>
60- </html>
61- ` )
62- newWindow . document . close ( )
21+ function StreamingIndicator ( ) {
22+ return < span className = 'inline-block h-[14px] w-[6px] animate-pulse bg-current opacity-70' />
6323}
6424
6525/**
@@ -108,54 +68,12 @@ export function ChatMessage({ message }: ChatMessageProps) {
10868 const throttled = useThrottledValue ( rawContent )
10969 const formattedContent = message . type === 'user' ? rawContent : throttled
11070
111- const handleAttachmentClick = ( attachment : ChatAttachment ) => {
112- const validDataUrl = attachment . dataUrl ?. trim ( )
113- if ( validDataUrl ?. startsWith ( 'data:' ) ) {
114- openImageInNewWindow ( validDataUrl , attachment . name )
115- }
116- }
117-
11871 if ( message . type === 'user' ) {
11972 const hasAttachments = message . attachments && message . attachments . length > 0
12073 return (
12174 < div className = 'w-full max-w-full overflow-hidden opacity-100 transition-opacity duration-200' >
12275 { hasAttachments && (
123- < div className = 'mb-[4px] flex flex-wrap gap-[4px]' >
124- { message . attachments ! . map ( ( attachment ) => {
125- const hasValidDataUrl =
126- attachment . dataUrl ?. trim ( ) && attachment . dataUrl . startsWith ( 'data:' )
127- const canDisplayAsImage = attachment . type . startsWith ( 'image/' ) && hasValidDataUrl
128-
129- return (
130- < div
131- key = { attachment . id }
132- className = { `flex max-w-[150px] items-center gap-[5px] rounded-[6px] bg-[var(--surface-2)] px-[5px] py-[3px] ${
133- hasValidDataUrl ? 'cursor-pointer' : ''
134- } `}
135- onClick = { ( e ) => {
136- if ( hasValidDataUrl ) {
137- e . preventDefault ( )
138- e . stopPropagation ( )
139- handleAttachmentClick ( attachment )
140- }
141- } }
142- >
143- { canDisplayAsImage ? (
144- < img
145- src = { attachment . dataUrl }
146- alt = { attachment . name }
147- className = 'h-[20px] w-[20px] flex-shrink-0 rounded-[3px] object-cover'
148- />
149- ) : (
150- < FileText className = 'h-[12px] w-[12px] flex-shrink-0 text-[var(--text-tertiary)]' />
151- ) }
152- < span className = 'truncate text-[10px] text-[var(--text-secondary)]' >
153- { attachment . name }
154- </ span >
155- </ div >
156- )
157- } ) }
158- </ div >
76+ < ChatMessageAttachments attachments = { message . attachments ! } align = 'start' className = 'mb-[4px]' />
15977 ) }
16078
16179 { formattedContent && ! formattedContent . startsWith ( 'Uploaded' ) && (
0 commit comments