/** * ChatInput — fixed-bottom input bar for submitting chat queries. * * - Positioned at the bottom of the main content area * - Input field + submit button, both disabled while streaming * - "Download conversation" button visible when messages exist * - On submit: fires onSubmit(query) callback and clears the input * * Requirements: 4.1, 4.6, 11.1 */ import { useState, type FormEvent, type KeyboardEvent } from "react"; export interface ChatInputProps { onSubmit: (query: string) => void; isStreaming: boolean; onStop: () => void; onExportConversation: () => void; hasMessages: boolean; /** * "hero" — floating centred card on the empty-state screen (no sticky) * "bottom" — sticky footer bar once a conversation is in progress (default) */ variant?: "hero" | "bottom"; } /** * Renders the fixed-bottom chat input bar. * * The input and submit button are disabled while `isStreaming` is true * (Requirement 4.6). The "Download conversation" button is only visible * when `hasMessages` is true (Requirement 11.1). On form submission, * the component calls `onSubmit` with the current query text and clears * the input field. */ export function ChatInput({ onSubmit, isStreaming, onStop, onExportConversation, hasMessages, variant = "bottom", }: ChatInputProps) { const [query, setQuery] = useState(""); const canSubmit = query.trim().length > 0 && !isStreaming; function handleSubmit(e: FormEvent) { e.preventDefault(); if (!canSubmit) return; onSubmit(query.trim()); setQuery(""); } function handleKeyDown(e: KeyboardEvent) { // Submit on Enter without Shift (Shift+Enter inserts a newline) if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (canSubmit) { onSubmit(query.trim()); setQuery(""); } } } if (variant === "hero") { // Floating card — centred on the empty-state screen, no sticky positioning. // Larger textarea (rows + font size) matches the Claude.ai input prominence. return (
{/* Large textarea — the focal point of the empty screen */}