"use client"; import { ArrowUp, Square } from "lucide-react"; import { type KeyboardEvent, type TextareaHTMLAttributes, useState } from "react"; import { cn } from "@/lib/utils"; export interface PromptInputProps extends Omit, "value" | "defaultValue" | "onChange" | "onSubmit"> { /** Controlled value. */ value?: string; /** Starting value when uncontrolled. Default "". */ defaultValue?: string; onChange?: (value: string) => void; /** Called with the trimmed text on Enter or the send button. */ onSubmit: (value: string) => void; /** Swaps the send button for a stop button. Default false. */ loading?: boolean; /** Called from the stop button while loading. */ onStop?: () => void; className?: string; } // Same crossfade CopyButton uses, so the button never changes size. const SWAP = "col-start-1 row-start-1 transition-[opacity,scale] duration-200 ease-out motion-reduce:transition-none"; const SHOWN = "scale-100 opacity-100"; const HIDDEN = "scale-50 opacity-0"; /** * A chat composer: a textarea that grows with its content up to a scrollable * cap, and a send button beside it. Enter submits, Shift+Enter starts a new * line, and the button crossfades into a stop button while a reply streams. */ export function PromptInput({ value, defaultValue = "", onChange, onSubmit, loading = false, onStop, disabled = false, placeholder = "Message...", className, ...props }: PromptInputProps) { const [uncontrolled, setUncontrolled] = useState(defaultValue); const isControlled = value !== undefined; const text = isControlled ? value : uncontrolled; const setValue = (next: string) => { if (!isControlled) setUncontrolled(next); onChange?.(next); }; const submit = () => { const trimmed = text.trim(); if (!trimmed || loading || disabled) return; onSubmit(trimmed); if (!isControlled) setUncontrolled(""); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Enter" && !event.shiftKey && !loading && !disabled) { event.preventDefault(); submit(); } }; return (