feat: Add clipboard image paste support and fix image previews in CC sessions

- Add save_clipboard_image and cleanup_temp_images commands in Rust backend
- Implement paste event handler in FloatingPromptInput to capture pasted images
- Save pasted images to .claude_temp/session_id/ directory
- Add automatic cleanup of temp images when session ends

- Fix image preview display for file paths containing spaces
- Update regex patterns to handle both quoted (@"path with spaces") and unquoted (@path) mentions
- Automatically wrap paths with spaces in quotes when inserting
- Update remove handler to properly handle both quoted and unquoted paths

Users can now paste images directly from clipboard (e.g., screenshots) and see proper previews for all image files regardless of filename format.
This commit is contained in:
Vivek R
2025-07-06 16:35:41 +05:30
parent 10628fcc82
commit 2009601dd9
4 changed files with 232 additions and 15 deletions

View File

@@ -922,6 +922,105 @@ pub async fn load_session_history(
Ok(messages)
}
/// Saves a clipboard image to a temporary location for a session
///
/// This command handles pasted images by saving them to a temp directory
/// within the project structure, making them accessible for Claude.
#[tauri::command]
pub async fn save_clipboard_image(
project_path: String,
session_id: String,
image_data: String, // Base64 encoded image data
mime_type: String,
) -> Result<String, String> {
log::info!(
"Saving clipboard image for session: {} in project: {}",
session_id,
project_path
);
// Parse the base64 data (remove data URL prefix if present)
let base64_data = if image_data.starts_with("data:") {
image_data
.split(',')
.nth(1)
.ok_or("Invalid data URL format")?
} else {
&image_data
};
// Decode base64 to bytes
use base64::Engine;
let image_bytes = base64::engine::general_purpose::STANDARD
.decode(base64_data)
.map_err(|e| format!("Failed to decode base64 image: {}", e))?;
// Determine file extension from MIME type
let extension = match mime_type.as_str() {
"image/png" => "png",
"image/jpeg" | "image/jpg" => "jpg",
"image/gif" => "gif",
"image/webp" => "webp",
"image/svg+xml" => "svg",
_ => "png", // Default to PNG
};
// Create temp directory for the project if it doesn't exist
let project_path_buf = PathBuf::from(&project_path);
let temp_dir = project_path_buf.join(".claude_temp").join(&session_id);
fs::create_dir_all(&temp_dir)
.map_err(|e| format!("Failed to create temp directory: {}", e))?;
// Generate unique filename with timestamp
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let filename = format!("pasted_image_{}.{}", timestamp, extension);
let file_path = temp_dir.join(&filename);
// Write image to file
fs::write(&file_path, image_bytes)
.map_err(|e| format!("Failed to write image file: {}", e))?;
// Return the absolute path
let absolute_path = file_path
.canonicalize()
.map_err(|e| format!("Failed to get absolute path: {}", e))?
.to_string_lossy()
.to_string();
log::info!("Saved clipboard image to: {}", absolute_path);
Ok(absolute_path)
}
/// Cleans up temporary images for a session
///
/// This command removes the temporary directory created for pasted images
/// when a session ends to avoid accumulating temporary files.
#[tauri::command]
pub async fn cleanup_temp_images(
project_path: String,
session_id: String,
) -> Result<(), String> {
log::info!(
"Cleaning up temp images for session: {} in project: {}",
session_id,
project_path
);
let project_path_buf = PathBuf::from(&project_path);
let temp_dir = project_path_buf.join(".claude_temp").join(&session_id);
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir)
.map_err(|e| format!("Failed to remove temp directory: {}", e))?;
log::info!("Cleaned up temp directory: {}", temp_dir.display());
}
Ok(())
}
/// Execute a new interactive Claude Code session with streaming output
#[tauri::command]
pub async fn execute_claude_code(