import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square } from "lucide-react" import * as React from "react" import { AgentChatContextStatus, ChatAttachmentRows, useAgentChatComposer, type AgentChatComposerFile, } from "@agents24/chat-react/ui" import { attachmentFileAccept, type AgentChatInputContract } from "@agents24/chat-react/runtime" import type { ContextWindow } from "@agents24/client/protocol" import { Button } from "../ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "../ui/dropdown-menu" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Textarea } from "../ui/textarea" import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" import { cn } from "../../lib/utils" export type Agents24ChatComposerProps = { queue?: React.ReactNode onRecordingStreamChange?: (stream: MediaStream | null) => void inputContract: AgentChatInputContract disabled?: boolean contextWindow?: ContextWindow | null scopeKey?: string | null focusKey?: string | null forceExpanded?: boolean isCancelling?: boolean isRunning: boolean agentId: string | null agents: Array<{ id: string; label: string }> modelId: string | null models: Array<{ id: string; label: string }> onModelChange: (modelId: string) => void onAgentChange: (agentId: string) => void onStop: () => void onSubmit: (text: string, files: AgentChatComposerFile[]) => Promise restoredDraft?: { revision: number; text: string; files: AgentChatComposerFile[] } | null utilityRow?: "inline" | "below" } export function ChatComposer({ inputContract, queue, onRecordingStreamChange, disabled, contextWindow, focusKey, scopeKey, forceExpanded = false, isCancelling = false, isRunning, agentId, agents, modelId, models, onModelChange, onAgentChange, onStop, onSubmit, restoredDraft, utilityRow = "inline", }: Agents24ChatComposerProps) { const inputRef = React.useRef(null) const { text, setText, files, removeFile, addFiles, textareaRef, submit, attachmentError, attachmentMimeTypes, allowAttachments, recordingAllowed, recorder, isExpanded, validationError, action, canSubmit } = useAgentChatComposer({ inputContract, disabled, scopeKey, focusKey, forceExpanded, isCancelling, isRunning, onSubmit, restoredDraft, onRecordingStreamChange, }) const selectedAgent = agents.find((item) => item.id === agentId) const selectedModel = models.find((item) => item.id === modelId) const modelSelector = models.length > 0 ? (
) : null const runtimeSelector = agents.length > 1 ? ( Agent{selectedAgent?.label} {agents.map((agent) => {agent.label})} {models.length > 0 ? ( Model{selectedModel?.label} {models.map((model) => {model.label})} ) : null} ) : modelSelector const hasContextWindow = Number.isFinite(Number(contextWindow?.max_tokens)) && Number(contextWindow?.max_tokens) > 0 const showUtilityRow = utilityRow === "below" return (
{queue} {attachmentError || recorder.error ?

{attachmentError || recorder.error}

: null}
0 || undefined} className={cn( "agents24-composer-grid grid min-w-0 items-center gap-2 rounded-xl border bg-background shadow-sm", isExpanded ? "p-2" : "p-1.5", )} > {allowAttachments && files.length > 0 && (
{ if (!attachment.id) return removeFile(attachment.id) }} />
)} {allowAttachments ? ( <> { addFiles(Array.from(event.target.files ?? [])) event.currentTarget.value = "" }} />
) :