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.
This commit is contained in:
@@ -44,6 +44,10 @@ interface FloatingPromptInputProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FloatingPromptInputRef {
|
||||||
|
addImage: (imagePath: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
type Model = {
|
type Model = {
|
||||||
id: "sonnet" | "opus";
|
id: "sonnet" | "opus";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -70,19 +74,21 @@ const MODELS: Model[] = [
|
|||||||
* FloatingPromptInput component - Fixed position prompt input with model picker
|
* FloatingPromptInput component - Fixed position prompt input with model picker
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
* const promptRef = useRef<FloatingPromptInputRef>(null);
|
||||||
* <FloatingPromptInput
|
* <FloatingPromptInput
|
||||||
|
* ref={promptRef}
|
||||||
* onSend={(prompt, model) => console.log('Send:', prompt, model)}
|
* onSend={(prompt, model) => console.log('Send:', prompt, model)}
|
||||||
* isLoading={false}
|
* isLoading={false}
|
||||||
* />
|
* />
|
||||||
*/
|
*/
|
||||||
export const FloatingPromptInput: React.FC<FloatingPromptInputProps> = ({
|
export const FloatingPromptInput = React.forwardRef<FloatingPromptInputRef, FloatingPromptInputProps>(({
|
||||||
onSend,
|
onSend,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
defaultModel = "sonnet",
|
defaultModel = "sonnet",
|
||||||
projectPath,
|
projectPath,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}, ref) => {
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
@@ -97,6 +103,34 @@ export const FloatingPromptInput: React.FC<FloatingPromptInputProps> = ({
|
|||||||
const expandedTextareaRef = useRef<HTMLTextAreaElement>(null);
|
const expandedTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const unlistenDragDropRef = useRef<(() => void) | null>(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
|
// Helper function to check if a file is an image
|
||||||
const isImageFile = (path: string): boolean => {
|
const isImageFile = (path: string): boolean => {
|
||||||
const ext = path.split('.').pop()?.toLowerCase();
|
const ext = path.split('.').pop()?.toLowerCase();
|
||||||
@@ -410,21 +444,21 @@ export const FloatingPromptInput: React.FC<FloatingPromptInputProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center min-w-[60px] h-10">
|
||||||
|
<div className="rotating-symbol text-primary text-2xl"></div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!prompt.trim() || isLoading || disabled}
|
disabled={!prompt.trim() || disabled}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="min-w-[80px]"
|
className="min-w-[80px]"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
|
||||||
<div className="rotating-symbol text-primary-foreground"></div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Send className="mr-2 h-4 w-4" />
|
<Send className="mr-2 h-4 w-4" />
|
||||||
Send
|
Send
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -541,18 +575,20 @@ export const FloatingPromptInput: React.FC<FloatingPromptInputProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Send Button */}
|
{/* Send Button */}
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center min-w-[60px] h-10">
|
||||||
|
<div className="rotating-symbol text-primary text-2xl"></div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!prompt.trim() || isLoading || disabled}
|
disabled={!prompt.trim() || disabled}
|
||||||
size="default"
|
size="default"
|
||||||
className="min-w-[60px]"
|
className="min-w-[60px]"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
|
||||||
<div className="rotating-symbol text-primary-foreground"></div>
|
|
||||||
) : (
|
|
||||||
<Send className="h-4 w-4" />
|
<Send className="h-4 w-4" />
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 text-xs text-muted-foreground">
|
<div className="mt-2 text-xs text-muted-foreground">
|
||||||
@@ -563,4 +599,6 @@ export const FloatingPromptInput: React.FC<FloatingPromptInputProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
FloatingPromptInput.displayName = 'FloatingPromptInput';
|
Reference in New Issue
Block a user