@@ -91,6 +91,8 @@ export const messageElement = async (
9191 <div class="generated-image-wrapper" data-index="${ idx } ">
9292 <img class="generated-image" src="data:${ img . mimeType } ;base64,${ img . base64 } " loading="lazy" />
9393 <div class="generated-image-overlay">
94+ <button class="btn-textual btn-image-action btn-edit material-symbols-outlined" title="Edit this image">edit</button>
95+ <button class="btn-textual btn-image-action btn-attach material-symbols-outlined" title="Attach this image">attachment</button>
9496 <button class="btn-textual btn-image-action btn-download material-symbols-outlined" title="Download">download</button>
9597 <button class="btn-textual btn-image-action btn-expand material-symbols-outlined" title="Expand">open_in_full</button>
9698 </div>
@@ -447,17 +449,64 @@ async function updateMessageInDatabase(markdownContent: string, messageIndex: nu
447449 }
448450}
449451
450- // Adds overlay button interactions (download + expand) & a lightweight lightbox
452+ // Adds overlay button interactions (download + expand + edit + attach ) & a lightweight lightbox
451453function setupGeneratedImageInteractions ( root : HTMLElement ) {
452454 const wrappers = root . querySelectorAll < HTMLElement > ( ".generated-image-wrapper" ) ;
453455 if ( ! wrappers . length ) return ;
454456
455457 wrappers . forEach ( wrap => {
456458 const img = wrap . querySelector < HTMLImageElement > ( ".generated-image" ) ;
459+ const editBtn = wrap . querySelector < HTMLButtonElement > ( ".btn-edit" ) ;
460+ const attachBtn = wrap . querySelector < HTMLButtonElement > ( ".btn-attach" ) ;
457461 const downloadBtn = wrap . querySelector < HTMLButtonElement > ( ".btn-download" ) ;
458462 const expandBtn = wrap . querySelector < HTMLButtonElement > ( ".btn-expand" ) ;
459463 if ( ! img ) return ;
460464
465+ // Helper to convert base64 image to File
466+ const imageToFile = async ( ) : Promise < File > => {
467+ const base64Data = img . src . split ( ',' ) [ 1 ] ;
468+ const mimeType = img . src . match ( / d a t a : ( .* ?) ; / ) ?. [ 1 ] || 'image/png' ;
469+ const byteString = atob ( base64Data ) ;
470+ const arrayBuffer = new ArrayBuffer ( byteString . length ) ;
471+ const uint8Array = new Uint8Array ( arrayBuffer ) ;
472+ for ( let i = 0 ; i < byteString . length ; i ++ ) {
473+ uint8Array [ i ] = byteString . charCodeAt ( i ) ;
474+ }
475+ const blob = new Blob ( [ uint8Array ] , { type : mimeType } ) ;
476+ const ext = mimeType . split ( '/' ) [ 1 ] ;
477+ return new File ( [ blob ] , `image-${ Date . now ( ) } .${ ext } ` , { type : mimeType } ) ;
478+ } ;
479+
480+ // Edit button: attach image + toggle editing mode
481+ editBtn ?. addEventListener ( 'click' , async ( e ) => {
482+ e . stopPropagation ( ) ;
483+ try {
484+ const file = await imageToFile ( ) ;
485+
486+ // Dispatch custom event instead of manually modifying input
487+ window . dispatchEvent ( new CustomEvent ( 'attach-image-from-chat' , {
488+ detail : { file, toggleEditing : true }
489+ } ) ) ;
490+ } catch ( err ) {
491+ console . error ( 'Failed to attach image for editing' , err ) ;
492+ }
493+ } ) ;
494+
495+ // Attach button: just attach image without toggling editing
496+ attachBtn ?. addEventListener ( 'click' , async ( e ) => {
497+ e . stopPropagation ( ) ;
498+ try {
499+ const file = await imageToFile ( ) ;
500+
501+ // Dispatch custom event instead of manually modifying input
502+ window . dispatchEvent ( new CustomEvent ( 'attach-image-from-chat' , {
503+ detail : { file, toggleEditing : false }
504+ } ) ) ;
505+ } catch ( err ) {
506+ console . error ( 'Failed to attach image' , err ) ;
507+ }
508+ } ) ;
509+
461510 downloadBtn ?. addEventListener ( 'click' , ( e ) => {
462511 e . stopPropagation ( ) ;
463512 try {
@@ -494,23 +543,6 @@ function setupGeneratedImageInteractions(root: HTMLElement) {
494543
495544 // Dynamically adjust sizing to ensure containment within viewport preserving aspect
496545 const lightboxImg = overlay . querySelector < HTMLImageElement > ( '.lightbox-image' ) ;
497- function fit ( ) {
498- if ( ! lightboxImg || ! lightboxImg . naturalWidth ) return ;
499- const vw = window . innerWidth * 0.95 ;
500- const vh = window . innerHeight * 0.95 ;
501- const { naturalWidth : iw , naturalHeight : ih } = lightboxImg ;
502- const ratio = Math . min ( vw / iw , vh / ih , 1 ) ;
503- lightboxImg . style . width = Math . round ( iw * ratio ) + 'px' ;
504- lightboxImg . style . height = Math . round ( ih * ratio ) + 'px' ;
505- }
506- if ( lightboxImg ?. complete ) {
507- fit ( ) ;
508- } else {
509- lightboxImg ?. addEventListener ( 'load' , fit , { once : true } ) ;
510- }
511- window . addEventListener ( 'resize' , fit , { passive : true } ) ;
512- // Cleanup resize listener when closed
513- overlay . addEventListener ( 'remove' , ( ) => window . removeEventListener ( 'resize' , fit ) ) ;
514546 }
515547
516548 expandBtn ?. addEventListener ( 'click' , ( e ) => { e . stopPropagation ( ) ; openLightbox ( ) ; } ) ;
0 commit comments