"use client"; import { useEffect, useRef } from "react"; import { AiDisclaimer } from "@/components/AiDisclaimer"; import { ChatIcon, LayersIcon, PaperclipIcon, SendIcon, StopIcon, XIcon } from "@/components/icons"; import { Spinner } from "@/components/ui"; import { useI18n } from "@/lib/i18n"; import { useIsPhone } from "@/lib/use-phone"; import type { ChatAttachment } from "@/lib/chat"; import { cn } from "@/lib/utils"; export type PendingAttachment = { attachment: ChatAttachment; text: string }; /** * The pinned composer: auto-growing 16px textarea, send/stop, attach, and the * context + model chips. Enter sends on desktop, newline on phones; the * keyboard inset is handled by the screen around it. */ export function Composer({ value, onChange, onSend, onStop, busy, online, scopeLabel, modelLabel, scopeOpen, modelOpen, onOpenScope, onOpenModel, onAttachFile, attachBusy, attachments, onRemoveAttachment, }: { value: string; onChange: (value: string) => void; onSend: () => void; onStop: () => void; busy: boolean; online: boolean; scopeLabel: string; modelLabel: string; scopeOpen: boolean; modelOpen: boolean; onOpenScope: () => void; onOpenModel: () => void; onAttachFile: (file: File) => void; attachBusy: boolean; attachments: PendingAttachment[]; onRemoveAttachment: (index: number) => void; }) { const { t } = useI18n(); const isPhone = useIsPhone(); const textarea = useRef(null); const fileInput = useRef(null); useEffect(() => { const element = textarea.current; if (!element) return; element.style.height = "auto"; element.style.height = `${Math.min(element.scrollHeight, 180)}px`; }, [value]); const canSend = online && !busy && value.trim().length > 0; return (
{attachments.length > 0 ? (
    {attachments.map((entry, index) => (
  • {entry.attachment.name} {entry.attachment.kind === "photo" ? t("chat.attachPhoto") : t("chat.attachDoc")}
  • ))}
) : null}
{ const file = event.target.files?.[0]; if (file) onAttachFile(file); event.target.value = ""; }} />