'use client'; import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle, } from 'react'; import { motion } from 'framer-motion'; import { Loader2, X } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { handleFiles } from './file-upload-handler'; import { MessageInput } from './message-input'; import { FileAttachment } from '../file-attachment'; import { AttachmentGroup } from '../attachment-group'; import { useModelSelection } from './_use-model-selection'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { toast } from 'sonner'; export interface ChatInputHandles { getPendingFiles: () => File[]; clearPendingFiles: () => void; } export interface ChatInputProps { onSubmit: ( message: string, options?: { model_name?: string; enable_thinking?: boolean }, ) => void; placeholder?: string; loading?: boolean; disabled?: boolean; isAgentRunning?: boolean; onStopAgent?: () => void; autoFocus?: boolean; value?: string; onChange?: (value: string) => void; onFileBrowse?: () => void; sandboxId?: string; hideAttachments?: boolean; } export interface UploadedFile { name: string; path: string; size: number; type: string; localUrl?: string; } export const ChatInput = forwardRef( ( { onSubmit, placeholder = 'Describe what you need help with...', loading = false, disabled = false, isAgentRunning = false, onStopAgent, autoFocus = true, value: controlledValue, onChange: controlledOnChange, onFileBrowse, sandboxId, hideAttachments = false, }, ref, ) => { const isControlled = controlledValue !== undefined && controlledOnChange !== undefined; const [uncontrolledValue, setUncontrolledValue] = useState(''); const value = isControlled ? controlledValue : uncontrolledValue; const [uploadedFiles, setUploadedFiles] = useState([]); const [pendingFiles, setPendingFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const [isDraggingOver, setIsDraggingOver] = useState(false); const { selectedModel, setSelectedModel: handleModelChange, subscriptionStatus, allModels: modelOptions, canAccessModel, } = useModelSelection(); const textareaRef = useRef(null); const fileInputRef = useRef(null); useImperativeHandle(ref, () => ({ getPendingFiles: () => pendingFiles, clearPendingFiles: () => setPendingFiles([]), })); useEffect(() => { if (autoFocus && textareaRef.current) { textareaRef.current.focus(); } }, [autoFocus]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if ( (!value.trim() && uploadedFiles.length === 0) || loading || (disabled && !isAgentRunning) ) return; if (isAgentRunning && onStopAgent) { onStopAgent(); return; } let message = value; if (uploadedFiles.length > 0) { const fileInfo = uploadedFiles .map((file) => `[Uploaded File: ${file.path}]`) .join('\n'); message = message ? `${message}\n\n${fileInfo}` : fileInfo; } let baseModelName = selectedModel; let thinkingEnabled = false; if (selectedModel.endsWith('-thinking')) { baseModelName = selectedModel.replace(/-thinking$/, ''); thinkingEnabled = true; } onSubmit(message, { model_name: baseModelName, enable_thinking: thinkingEnabled, }); if (!isControlled) { setUncontrolledValue(''); } setUploadedFiles([]); }; const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value; if (isControlled) { controlledOnChange(newValue); } else { setUncontrolledValue(newValue); } }; const removeUploadedFile = (index: number) => { const fileToRemove = uploadedFiles[index]; if (fileToRemove.localUrl) { URL.revokeObjectURL(fileToRemove.localUrl); } setUploadedFiles((prev) => prev.filter((_, i) => i !== index)); if (!sandboxId && pendingFiles.length > index) { setPendingFiles((prev) => prev.filter((_, i) => i !== index)); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDraggingOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDraggingOver(false); }; return (
{ e.preventDefault(); e.stopPropagation(); setIsDraggingOver(false); if (fileInputRef.current && e.dataTransfer.files.length > 0) { const files = Array.from(e.dataTransfer.files); handleFiles( files, sandboxId, setPendingFiles, setUploadedFiles, setIsUploading, ); } }} >
{isAgentRunning && (
Kortix Suna is working...
)}
); }, ); ChatInput.displayName = 'ChatInput';