diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index 792bfc11a63471e02b22835823fa8c59cdfc9bcf..3552e3f45fa291b3fd7acb625c7e39686914863b 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -654,7 +654,7 @@ mod tests { /// Inserts a list of images into the editor as context mentions. /// This is the shared implementation used by both paste and file picker operations. pub(crate) async fn insert_images_as_context( - images: Vec, + images: Vec<(gpui::Image, Option)>, editor: Entity, mention_set: Entity, workspace: WeakEntity, @@ -664,9 +664,15 @@ pub(crate) async fn insert_images_as_context( return; } - let replacement_text = MentionUri::PastedImage.as_link().to_string(); + for (image, path) in images { + let mention_uri = match &path { + Some(abs_path) => MentionUri::File { + abs_path: abs_path.clone(), + }, + None => MentionUri::PastedImage, + }; + let replacement_text = mention_uri.as_link().to_string(); - for image in images { let Some((excerpt_id, text_anchor, multibuffer_anchor)) = editor .update_in(cx, |editor, window, cx| { let snapshot = editor.snapshot(window, cx); @@ -700,11 +706,11 @@ pub(crate) async fn insert_images_as_context( excerpt_id, text_anchor, content_len, - MentionUri::PastedImage.name().into(), - IconName::Image.path().into(), - None, - None, - None, + mention_uri.name().into(), + mention_uri.icon_path(cx), + mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(workspace.clone()), Some(Task::ready(Ok(image.clone())).shared()), editor.clone(), window, @@ -732,7 +738,7 @@ pub(crate) async fn insert_images_as_context( .shared(); mention_set.update(cx, |mention_set, _cx| { - mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone()) + mention_set.insert_mention(crease_id, mention_uri, task.clone()) }); if task @@ -760,7 +766,7 @@ pub(crate) fn paste_images_as_context( let clipboard = cx.read_from_clipboard()?; Some(window.spawn(cx, async move |mut cx| { use itertools::Itertools; - let (mut images, paths) = clipboard + let (clipboard_images, paths) = clipboard .into_entries() .filter_map(|entry| match entry { ClipboardEntry::Image(image) => Some(Either::Left(image)), @@ -769,35 +775,40 @@ pub(crate) fn paste_images_as_context( }) .partition_map::, Vec<_>, _, _, _>(std::convert::identity); + // Clipboard images don't have a file path + let mut images: Vec<(gpui::Image, Option)> = clipboard_images + .into_iter() + .map(|img| (img, None)) + .collect(); + + // Images from external paths preserve their file path if !paths.is_empty() { - images.extend( - cx.background_spawn(async move { + let path_images = cx + .background_spawn(async move { let mut images = vec![]; for path in paths.into_iter().flat_map(|paths| paths.paths().to_owned()) { - let Ok(content) = async_fs::read(path).await else { + let Ok(content) = async_fs::read(&path).await else { continue; }; let Ok(format) = image::guess_format(&content) else { continue; }; - images.push(gpui::Image::from_bytes( - match format { - image::ImageFormat::Png => gpui::ImageFormat::Png, - image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg, - image::ImageFormat::WebP => gpui::ImageFormat::Webp, - image::ImageFormat::Gif => gpui::ImageFormat::Gif, - image::ImageFormat::Bmp => gpui::ImageFormat::Bmp, - image::ImageFormat::Tiff => gpui::ImageFormat::Tiff, - image::ImageFormat::Ico => gpui::ImageFormat::Ico, - _ => continue, - }, - content, - )); + let gpui_format = match format { + image::ImageFormat::Png => gpui::ImageFormat::Png, + image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg, + image::ImageFormat::WebP => gpui::ImageFormat::Webp, + image::ImageFormat::Gif => gpui::ImageFormat::Gif, + image::ImageFormat::Bmp => gpui::ImageFormat::Bmp, + image::ImageFormat::Tiff => gpui::ImageFormat::Tiff, + image::ImageFormat::Ico => gpui::ImageFormat::Ico, + _ => continue, + }; + images.push((gpui::Image::from_bytes(gpui_format, content), Some(path))); } images }) - .await, - ); + .await; + images.extend(path_images); } cx.update(|_window, cx| { diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index c75d0479b7bf16229cc487544d2c87403b3da430..86d5ec9fe911e412243992f66cdd3fddf249ca32 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -829,6 +829,44 @@ impl MessageEditor { let http_client = workspace.read(cx).client().http_client(); for (anchor, content_len, mention_uri) in all_mentions { + // For image files, load a preview image + let image_preview = if let MentionUri::File { ref abs_path } = mention_uri { + let extension = abs_path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or_default(); + if gpui::Img::extensions().contains(&extension) + && !extension.contains("svg") + { + let path = abs_path.clone(); + Some( + cx.spawn(async move |_, _| { + let content = async_fs::read(&path) + .await + .map_err(|e| e.to_string())?; + let format = image::guess_format(&content) + .map_err(|e| e.to_string())?; + let gpui_format = match format { + image::ImageFormat::Png => gpui::ImageFormat::Png, + image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg, + image::ImageFormat::WebP => gpui::ImageFormat::Webp, + image::ImageFormat::Gif => gpui::ImageFormat::Gif, + image::ImageFormat::Bmp => gpui::ImageFormat::Bmp, + image::ImageFormat::Tiff => gpui::ImageFormat::Tiff, + image::ImageFormat::Ico => gpui::ImageFormat::Ico, + _ => return Err("Unsupported image format".to_string()), + }; + Ok(Arc::new(gpui::Image::from_bytes(gpui_format, content))) + }) + .shared(), + ) + } else { + None + } + } else { + None + }; + let Some((crease_id, tx)) = insert_crease_for_mention( anchor.excerpt_id, anchor.text_anchor, @@ -838,7 +876,7 @@ impl MessageEditor { mention_uri.tooltip_text(), Some(mention_uri.clone()), Some(self.workspace.clone()), - None, + image_preview, self.editor.clone(), window, cx, @@ -1198,7 +1236,7 @@ impl MessageEditor { continue; }; - images.push(gpui::Image::from_bytes(format, content)); + images.push((gpui::Image::from_bytes(format, content), Some(path))); } crate::mention_set::insert_images_as_context(