import React, { useState, useRef, useCallback, useEffect } from 'react'; import { Send, Paperclip, XCircle } from 'lucide-react'; import LoadingSpinner from './LoadingSpinner'; interface ChatInputProps { onSendMessage: (message: string, imageFile?: File | null) => void; isLoading: boolean; isApiKeyMissing: boolean; } const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; const ChatInput: React.FC = ({ onSendMessage, isLoading, isApiKeyMissing }) => { const [inputValue, setInputValue] = useState(''); const [selectedImage, setSelectedImage] = useState(null); const [imagePreviewUrl, setImagePreviewUrl] = useState(null); const [isDraggingOver, setIsDraggingOver] = useState(false); const textareaRef = useRef(null); const fileInputRef = useRef(null); useEffect(() => { if (selectedImage) { const objectUrl = URL.createObjectURL(selectedImage); setImagePreviewUrl(objectUrl); return () => URL.revokeObjectURL(objectUrl); } setImagePreviewUrl(null); }, [selectedImage]); const handleImageFile = (file: File | null) => { if (file && ACCEPTED_IMAGE_TYPES.includes(file.type)) { setSelectedImage(file); } else if (file) { alert('不支持的文件类型。请选择 JPG, PNG, GIF, 或 WEBP 格式的图片。'); setSelectedImage(null); } else { setSelectedImage(null); } // Reset file input value to allow selecting the same file again if (fileInputRef.current) { fileInputRef.current.value = ""; } }; const removeImage = () => { setSelectedImage(null); setImagePreviewUrl(null); }; const triggerSendMessage = () => { if ((inputValue.trim() || selectedImage) && !isLoading && !isApiKeyMissing) { onSendMessage(inputValue.trim(), selectedImage); setInputValue(''); removeImage(); // Clear image after sending if (textareaRef.current) { // Reset textarea height after sending textareaRef.current.style.height = 'auto'; } } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); triggerSendMessage(); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); // Prevent new line triggerSendMessage(); } }; const handlePaste = useCallback((e: React.ClipboardEvent) => { const items = e.clipboardData?.items; if (items) { for (let i = 0; i < items.length; i++) { if (ACCEPTED_IMAGE_TYPES.includes(items[i].type)) { const file = items[i].getAsFile(); if (file) { handleImageFile(file); e.preventDefault(); // Prevent pasting file path as text break; } } } } }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDraggingOver(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { handleImageFile(e.dataTransfer.files[0]); e.dataTransfer.clearData(); } }, []); const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDraggingOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDraggingOver(false); }; const handleFileButtonClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; const handleFileSelected = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleImageFile(e.target.files[0]); } }; const isDisabled = isLoading || isApiKeyMissing; return (
{imagePreviewUrl && selectedImage && (
{selectedImage.name
{selectedImage.name} ({(selectedImage.size / 1024).toFixed(1)} KB)
)}