import React, { useRef, useState } from 'react'; import { ArrowUpIcon, PlusIcon, SquareIcon } from 'lucide-react'; import { isTouchKeyboard, sendsOnEnter, } from '../../../lib/copilot/composer-keys'; import { useAutoGrow } from '../../../lib/hooks/use-auto-grow'; import type { ICopilotThinkingMode } from '../../../service/copilot/copilot.interface'; import { BARE_FIELD } from '../bare-field'; import { cn } from '../cn'; import { ThinkingSelect } from '../ThinkingSelect'; /** How tall the box grows before it scrolls inside itself. Past this the * composer would be eating the conversation it belongs to. */ const MAX_COMPOSER_HEIGHT = 200; /** Past this many characters the box says how long the question is. */ const COUNTER_THRESHOLD = 1500; /** The ring every control in the composer shows on keyboard focus. */ const FOCUS_RING = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#e0379f]/70 focus-visible:ring-offset-2 focus-visible:ring-offset-background'; /** Said once, under the box, where it is read before every question. */ const FALLIBILITY_NOTICE = 'Recomaze can make mistakes. Check anything that matters.'; /** * HQ's question box: one input for everything the copilot can do, with the * thinking toggle and what an answer costs under it. Escape stops an answer * that is still being written. * * @param props - Component props. * @param props.onSend - Send the typed question. * @param props.onStop - Stop the answer being written. * @param props.isStreaming - An answer is being written. * @param props.thinking - The thinking mode picked. * @param props.onThinking - Pick another. * @param props.placeholder - What the empty box says. * @param props.compact - The tighter box docked under a conversation. * @return The composer. */ export function HomeComposer({ onSend, onStop, isStreaming, thinking, onThinking, placeholder, compact = false, }: { onSend: (text: string) => void; onStop: () => void; isStreaming: boolean; thinking: ICopilotThinkingMode; onThinking: (mode: ICopilotThinkingMode) => void; placeholder: string; compact?: boolean; }): React.ReactElement { const [text, setText] = useState(''); const [isFocused, setIsFocused] = useState(false); const boxRef = useRef(null); useAutoGrow(boxRef, text, MAX_COMPOSER_HEIGHT); const asked: string = text.trim(); const canSend: boolean = asked !== '' && !isStreaming; const submit = (event: React.FormEvent): void => { event.preventDefault(); if (!canSend) return; setText(''); onSend(asked); }; const onKeyDown = (event: React.KeyboardEvent): void => { if (event.key === 'Escape' && isStreaming) { event.preventDefault(); onStop(); return; } if (sendsOnEnter(event, isTouchKeyboard())) submit(event); }; return (