import { useCallback, useRef, type ComponentProps, type FormEvent, type HTMLAttributes, type KeyboardEventHandler, type MouseEvent } from "react"; import type { ChatStatus } from "ai"; import { CornerDownLeftIcon, SquareIcon, XIcon } from "lucide-react"; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@/shared/ui/input-group"; import { Spinner } from "@/shared/ui/spinner"; import { cn } from "@/shared/ui/cn"; export type PromptInputMessage = { text: string }; export type PromptInputProps = Omit, "onSubmit"> & { onSubmit: (message: PromptInputMessage, event: FormEvent) => void | Promise; }; /** Text composition only. The owning panel controls drafts and restores failed submissions. */ export function PromptInput({ className, onSubmit, children, ...props }: PromptInputProps) { const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const form = event.currentTarget; const text = String(new FormData(form).get("message") ?? ""); form.reset(); // Keep submission after the form event, matching the controlled panel's draft lifecycle. await Promise.resolve(); try { await onSubmit({ text }, event); } catch { /* The owner keeps the draft and exposes its submission error. */ } }; return
{children}
; } export function PromptInputBody({ className, ...props }: HTMLAttributes) { return
; } export function PromptInputTextarea({ onKeyDown, onCompositionStart, onCompositionEnd, className, placeholder = "What would you like to know?", ...props }: ComponentProps) { const composing = useRef(false); const handleKeyDown: KeyboardEventHandler = (event) => { onKeyDown?.(event); if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || composing.current || event.nativeEvent.isComposing) return; event.preventDefault(); const form = event.currentTarget.form; const submit = form?.querySelector('button[type="submit"]'); if (!submit?.disabled) form?.requestSubmit(); }; return { composing.current = true; onCompositionStart?.(event); }} onCompositionEnd={(event) => { composing.current = false; onCompositionEnd?.(event); }} />; } export function PromptInputFooter({ className, ...props }: Omit, "align">) { return ; } export type PromptInputSubmitProps = ComponentProps & { status?: ChatStatus; onStop?: () => void }; export function PromptInputSubmit({ className, variant = "default", size = "icon-sm", status, onStop, onClick, children, ...props }: PromptInputSubmitProps) { const generating = status === "submitted" || status === "streaming"; const handleClick = useCallback((event: MouseEvent) => { if (generating && onStop) { event.preventDefault(); onStop(); } else onClick?.(event); }, [generating, onStop, onClick]); const icon = status === "submitted" ? : status === "streaming" ? : status === "error" ? : ; return {children ?? icon}; }