({
input,
status,
onSubmit,
onChange,
onClearChat,
files = [],
onFileChange = () => {},
pdfData = new Map(),
showHistory = false,
onToggleHistory = () => {},
sessionId,
error = null,
}: ChatInputProps)
| 132 | } |
| 133 | |
| 134 | export function ChatInput({ |
| 135 | input, |
| 136 | status, |
| 137 | onSubmit, |
| 138 | onChange, |
| 139 | onClearChat, |
| 140 | files = [], |
| 141 | onFileChange = () => {}, |
| 142 | pdfData = new Map(), |
| 143 | showHistory = false, |
| 144 | onToggleHistory = () => {}, |
| 145 | sessionId, |
| 146 | error = null, |
| 147 | }: ChatInputProps) { |
| 148 | const { diagramHistory, saveDiagramToFile } = useDiagram() |
| 149 | const textareaRef = useRef<HTMLTextAreaElement>(null) |
| 150 | const fileInputRef = useRef<HTMLInputElement>(null) |
| 151 | const [isDragging, setIsDragging] = useState(false) |
| 152 | const [showClearDialog, setShowClearDialog] = useState(false) |
| 153 | const [showSaveDialog, setShowSaveDialog] = useState(false) |
| 154 | |
| 155 | // Allow retry when there's an error (even if status is still "streaming" or "submitted") |
| 156 | const isDisabled = |
| 157 | (status === "streaming" || status === "submitted") && !error |
| 158 | |
| 159 | const adjustTextareaHeight = useCallback(() => { |
| 160 | const textarea = textareaRef.current |
| 161 | if (textarea) { |
| 162 | textarea.style.height = "auto" |
| 163 | textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px` |
| 164 | } |
| 165 | }, []) |
| 166 | |
| 167 | // Handle programmatic input changes (e.g., setInput("") after form submission) |
| 168 | useEffect(() => { |
| 169 | adjustTextareaHeight() |
| 170 | }, [input, adjustTextareaHeight]) |
| 171 | |
| 172 | const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { |
| 173 | onChange(e) |
| 174 | adjustTextareaHeight() |
| 175 | } |
| 176 | |
| 177 | const handleKeyDown = (e: React.KeyboardEvent) => { |
| 178 | if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { |
| 179 | e.preventDefault() |
| 180 | const form = e.currentTarget.closest("form") |
| 181 | if (form && input.trim() && !isDisabled) { |
| 182 | form.requestSubmit() |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | const handlePaste = async (e: React.ClipboardEvent) => { |
| 188 | if (isDisabled) return |
| 189 | |
| 190 | const items = e.clipboardData.items |
| 191 | const imageItems = Array.from(items).filter((item) => |
nothing calls this directly
no test coverage detected