|
|
import React, { useState, KeyboardEvent, useRef } from 'react'; |
|
|
import { Send, Loader2, Plus, X } from 'lucide-react'; |
|
|
|
|
|
interface ChatInputProps { |
|
|
onSend: (message: string) => void; |
|
|
disabled?: boolean; |
|
|
isLoading?: boolean; |
|
|
onImageSelect?: (image: string | null) => void; |
|
|
currentImage?: string | null; |
|
|
supportsImages?: boolean; |
|
|
} |
|
|
|
|
|
export const ChatInput: React.FC<ChatInputProps> = ({ |
|
|
onSend, |
|
|
disabled = false, |
|
|
isLoading = false, |
|
|
onImageSelect, |
|
|
currentImage, |
|
|
supportsImages = false |
|
|
}) => { |
|
|
const [input, setInput] = useState(''); |
|
|
const [preview, setPreview] = useState<string | null>(currentImage || null); |
|
|
const fileInputRef = useRef<HTMLInputElement>(null); |
|
|
|
|
|
const handleSend = () => { |
|
|
if (input.trim() && !disabled && !isLoading) { |
|
|
onSend(input.trim()); |
|
|
setInput(''); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleKeyPress = (e: KeyboardEvent<HTMLTextAreaElement>) => { |
|
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
|
e.preventDefault(); |
|
|
handleSend(); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
|
|
const file = e.target.files?.[0]; |
|
|
if (file) { |
|
|
const reader = new FileReader(); |
|
|
reader.onloadend = () => { |
|
|
const result = reader.result as string; |
|
|
setPreview(result); |
|
|
onImageSelect?.(result); |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleRemoveImage = () => { |
|
|
setPreview(null); |
|
|
onImageSelect?.(null); |
|
|
if (fileInputRef.current) { |
|
|
fileInputRef.current.value = ''; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
setPreview(currentImage || null); |
|
|
}, [currentImage]); |
|
|
|
|
|
return ( |
|
|
<div className="w-full max-w-4xl mx-auto"> |
|
|
{/* Image Preview */} |
|
|
{preview && ( |
|
|
<div className="mb-3 relative inline-block"> |
|
|
<img |
|
|
src={preview} |
|
|
alt="Preview" |
|
|
className="max-w-xs h-32 object-contain rounded-lg border border-gray-300 dark:border-gray-700" |
|
|
/> |
|
|
<button |
|
|
onClick={handleRemoveImage} |
|
|
className="absolute -top-2 -right-2 p-1 bg-red-500 hover:bg-red-600 text-white rounded-full transition-all shadow-lg" |
|
|
> |
|
|
<X size={14} /> |
|
|
</button> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<div className="relative flex items-center gap-2 bg-white dark:bg-gray-800 rounded-3xl shadow-lg border border-gray-200 dark:border-gray-700 px-3 py-3 transition-all focus-within:shadow-xl focus-within:border-primary-500 dark:focus-within:border-primary-600"> |
|
|
{/* Hidden File Input */} |
|
|
<input |
|
|
ref={fileInputRef} |
|
|
type="file" |
|
|
accept="image/*" |
|
|
onChange={handleFileChange} |
|
|
className="hidden" |
|
|
disabled={!supportsImages} |
|
|
/> |
|
|
|
|
|
{/* Plus Button for Image Upload */} |
|
|
<button |
|
|
onClick={() => fileInputRef.current?.click()} |
|
|
disabled={!supportsImages} |
|
|
className={`flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center transition-all ${ |
|
|
supportsImages |
|
|
? 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400' |
|
|
: 'text-gray-300 dark:text-gray-600 cursor-not-allowed' |
|
|
}`} |
|
|
title={supportsImages ? "Resim yükle" : "Bu model resim desteklemiyor"} |
|
|
> |
|
|
<Plus size={20} /> |
|
|
</button> |
|
|
|
|
|
<textarea |
|
|
value={input} |
|
|
onChange={(e) => setInput(e.target.value)} |
|
|
onKeyPress={handleKeyPress} |
|
|
placeholder="Herhangi bir şey sor" |
|
|
rows={1} |
|
|
disabled={disabled || isLoading} |
|
|
className="flex-1 bg-transparent text-gray-900 dark:text-gray-100 resize-none border-none focus:outline-none focus:ring-0 placeholder:text-gray-400 dark:placeholder:text-gray-500 disabled:cursor-not-allowed" |
|
|
style={{ minHeight: '24px', maxHeight: '120px' }} |
|
|
onInput={(e) => { |
|
|
const target = e.target as HTMLTextAreaElement; |
|
|
target.style.height = 'auto'; |
|
|
target.style.height = Math.min(target.scrollHeight, 120) + 'px'; |
|
|
}} |
|
|
/> |
|
|
<button |
|
|
onClick={handleSend} |
|
|
disabled={!input.trim() || disabled || isLoading} |
|
|
className="flex-shrink-0 w-8 h-8 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:cursor-not-allowed rounded-lg flex items-center justify-center transition-all" |
|
|
> |
|
|
{isLoading ? ( |
|
|
<Loader2 size={18} className="animate-spin text-gray-600 dark:text-gray-400" /> |
|
|
) : ( |
|
|
<Send size={18} className="text-gray-600 dark:text-gray-300" /> |
|
|
)} |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|