From 433404566143e8e1e2984f00087a1be5333b298e Mon Sep 17 00:00:00 2001 From: Mufeed VH Date: Mon, 23 Jun 2025 00:30:15 +0530 Subject: [PATCH] feat(input): add image attachment support to prompt input - Add imperative handle for programmatic image attachment - Expose addImage() method via React ref - Support screenshot integration from preview pane Enables automatic attachment of screenshots to Claude prompts. --- src/components/FloatingPromptInput.tsx | 96 ++++++++++++++++++-------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/src/components/FloatingPromptInput.tsx b/src/components/FloatingPromptInput.tsx index b5d7f98..b803d3b 100644 --- a/src/components/FloatingPromptInput.tsx +++ b/src/components/FloatingPromptInput.tsx @@ -44,6 +44,10 @@ interface FloatingPromptInputProps { className?: string; } +export interface FloatingPromptInputRef { + addImage: (imagePath: string) => void; +} + type Model = { id: "sonnet" | "opus"; name: string; @@ -70,19 +74,21 @@ const MODELS: Model[] = [ * FloatingPromptInput component - Fixed position prompt input with model picker * * @example + * const promptRef = useRef(null); * console.log('Send:', prompt, model)} * isLoading={false} * /> */ -export const FloatingPromptInput: React.FC = ({ +export const FloatingPromptInput = React.forwardRef(({ onSend, isLoading = false, disabled = false, defaultModel = "sonnet", projectPath, className, -}) => { +}, ref) => { const [prompt, setPrompt] = useState(""); const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel); const [isExpanded, setIsExpanded] = useState(false); @@ -96,6 +102,34 @@ export const FloatingPromptInput: React.FC = ({ const textareaRef = useRef(null); const expandedTextareaRef = useRef(null); const unlistenDragDropRef = useRef<(() => void) | null>(null); + + // Expose a method to add images programmatically + React.useImperativeHandle( + ref, + () => ({ + addImage: (imagePath: string) => { + setPrompt(currentPrompt => { + const existingPaths = extractImagePaths(currentPrompt); + if (existingPaths.includes(imagePath)) { + return currentPrompt; // Image already added + } + + const mention = `@${imagePath}`; + const newPrompt = currentPrompt + (currentPrompt.endsWith(' ') || currentPrompt === '' ? '' : ' ') + mention + ' '; + + // Focus the textarea + setTimeout(() => { + const target = isExpanded ? expandedTextareaRef.current : textareaRef.current; + target?.focus(); + target?.setSelectionRange(newPrompt.length, newPrompt.length); + }, 0); + + return newPrompt; + }); + } + }), + [isExpanded] + ); // Helper function to check if a file is an image const isImageFile = (path: string): boolean => { @@ -410,21 +444,21 @@ export const FloatingPromptInput: React.FC = ({ - + {isLoading ? ( +
+
+
+ ) : ( + + )} @@ -541,18 +575,20 @@ export const FloatingPromptInput: React.FC = ({ {/* Send Button */} - + + )}
@@ -563,4 +599,6 @@ export const FloatingPromptInput: React.FC = ({
); -}; \ No newline at end of file +}); + +FloatingPromptInput.displayName = 'FloatingPromptInput'; \ No newline at end of file