@@ -86,6 +86,7 @@ export function useMessages(options: UseMessagesOptions) {
8686 const messagesBySession = reactive < Record < string , ChatRecord [ ] > > ( { } ) ;
8787 const loadedSessions = reactive < Record < string , boolean > > ( { } ) ;
8888 const activeConnections = reactive < Record < string , ActiveConnection > > ( { } ) ;
89+ const attachmentBlobCache = new Map < string , string > ( ) ;
8990 const sessionProjects = reactive < Record < string , ChatSessionProject | null > > (
9091 { } ,
9192 ) ;
@@ -129,6 +130,47 @@ export function useMessages(options: UseMessagesOptions) {
129130 return ! isUserMessage ( msg ) && msgIndex === activeMessages . value . length - 1 ;
130131 }
131132
133+ async function resolvePartMedia ( part : MessagePart ) : Promise < void > {
134+ if ( part . embedded_url ) return ;
135+ let url : string ;
136+ let cacheKey : string ;
137+ if ( part . attachment_id ) {
138+ cacheKey = `att:${ part . attachment_id } ` ;
139+ url = `/api/chat/get_attachment?attachment_id=${ encodeURIComponent ( part . attachment_id ) } ` ;
140+ } else if ( part . filename ) {
141+ cacheKey = `file:${ part . filename } ` ;
142+ url = `/api/chat/get_file?filename=${ encodeURIComponent ( part . filename ) } ` ;
143+ } else {
144+ return ;
145+ }
146+ const cached = attachmentBlobCache . get ( cacheKey ) ;
147+ if ( cached ) {
148+ part . embedded_url = cached ;
149+ return ;
150+ }
151+ try {
152+ const resp = await axios . get ( url , { responseType : "blob" } ) ;
153+ const blobUrl = URL . createObjectURL ( resp . data ) ;
154+ attachmentBlobCache . set ( cacheKey , blobUrl ) ;
155+ part . embedded_url = blobUrl ;
156+ } catch ( e ) {
157+ console . error ( "Failed to resolve media:" , cacheKey , e ) ;
158+ }
159+ }
160+
161+ async function resolveRecordMedia ( records : ChatRecord [ ] ) {
162+ const mediaTypes = [ "image" , "record" , "video" ] ;
163+ const tasks : Promise < void > [ ] = [ ] ;
164+ for ( const record of records ) {
165+ for ( const part of record . content ?. message || [ ] ) {
166+ if ( mediaTypes . includes ( part . type ) && ! part . embedded_url && ( part . attachment_id || part . filename ) ) {
167+ tasks . push ( resolvePartMedia ( part ) ) ;
168+ }
169+ }
170+ }
171+ await Promise . all ( tasks ) ;
172+ }
173+
132174 async function loadSessionMessages ( sessionId : string ) {
133175 if ( ! sessionId ) return ;
134176 loadingMessages . value = true ;
@@ -138,7 +180,9 @@ export function useMessages(options: UseMessagesOptions) {
138180 } ) ;
139181 const payload = response . data ?. data || { } ;
140182 const history = payload . history || [ ] ;
141- messagesBySession [ sessionId ] = history . map ( normalizeHistoryRecord ) ;
183+ const records = history . map ( normalizeHistoryRecord ) ;
184+ await resolveRecordMedia ( records ) ;
185+ messagesBySession [ sessionId ] = records ;
142186 sessionProjects [ sessionId ] = normalizeSessionProject ( payload . project ) ;
143187 loadedSessions [ sessionId ] = true ;
144188 } catch ( error ) {
@@ -438,7 +482,14 @@ export function useMessages(options: UseMessagesOptions) {
438482 . replace ( "[FILE]" , "" )
439483 . replace ( "[VIDEO]" , "" )
440484 . split ( "|" , 1 ) [ 0 ] ;
441- messageContent ( botRecord ) . message . push ( { type : msgType , filename } ) ;
485+ const mediaPart : MessagePart = { type : msgType , filename } ;
486+ if ( msgType !== "file" ) {
487+ resolvePartMedia ( mediaPart ) . then ( ( ) => {
488+ messageContent ( botRecord ) . message . push ( mediaPart ) ;
489+ } ) ;
490+ } else {
491+ messageContent ( botRecord ) . message . push ( mediaPart ) ;
492+ }
442493 }
443494 }
444495
0 commit comments