import { type ApprovalRequestId } from "@t3tools/contracts"; import { memo, useEffect, useEffectEvent, useRef } from "react"; import { type PendingUserInput } from "../../session-logic"; import { derivePendingUserInputProgress, type PendingUserInputDraftAnswer, } from "../../pendingUserInput"; import { CheckIcon } from "lucide-react"; import { cn } from "~/lib/utils"; interface PendingUserInputPanelProps { pendingUserInputs: PendingUserInput[]; respondingRequestIds: ApprovalRequestId[]; answers: Record; questionIndex: number; onToggleOption: (questionId: string, optionLabel: string) => void; onAdvance: () => void; } export const ComposerPendingUserInputPanel = memo(function ComposerPendingUserInputPanel({ pendingUserInputs, respondingRequestIds, answers, questionIndex, onToggleOption, onAdvance, }: PendingUserInputPanelProps) { if (pendingUserInputs.length === 0) return null; const activePrompt = pendingUserInputs[0]; if (!activePrompt) return null; return ( ); }); const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard({ prompt, isResponding, answers, questionIndex, onToggleOption, onAdvance, }: { prompt: PendingUserInput; isResponding: boolean; answers: Record; questionIndex: number; onToggleOption: (questionId: string, optionLabel: string) => void; onAdvance: () => void; }) { const progress = derivePendingUserInputProgress(prompt.questions, answers, questionIndex); const activeQuestion = progress.activeQuestion; const autoAdvanceTimerRef = useRef(null); const onAdvanceRef = useRef(onAdvance); useEffect(() => { onAdvanceRef.current = onAdvance; }, [onAdvance]); // Clear auto-advance timer on unmount useEffect(() => { return () => { if (autoAdvanceTimerRef.current !== null) { window.clearTimeout(autoAdvanceTimerRef.current); } }; }, []); const handleOptionSelection = useEffectEvent((questionId: string, optionLabel: string) => { onToggleOption(questionId, optionLabel); if (activeQuestion?.multiSelect) { return; } if (autoAdvanceTimerRef.current !== null) { window.clearTimeout(autoAdvanceTimerRef.current); } autoAdvanceTimerRef.current = window.setTimeout(() => { autoAdvanceTimerRef.current = null; onAdvanceRef.current(); }, 200); }); // Keyboard shortcut: number keys 1-9 select corresponding options when focus is // outside editable fields. Multi-select prompts toggle options in place; single- // select prompts keep the existing auto-advance behavior. useEffect(() => { if (!activeQuestion || isResponding) return; const handler = (event: globalThis.KeyboardEvent) => { if (event.metaKey || event.ctrlKey || event.altKey) return; const target = event.target; if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { return; } if ( target instanceof HTMLElement && target.closest('[contenteditable]:not([contenteditable="false"])') ) { return; } const digit = Number.parseInt(event.key, 10); if (Number.isNaN(digit) || digit < 1 || digit > 9) return; const optionIndex = digit - 1; if (optionIndex >= activeQuestion.options.length) return; const option = activeQuestion.options[optionIndex]; if (!option) return; event.preventDefault(); handleOptionSelection(activeQuestion.id, option.label); }; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); }, [activeQuestion, isResponding]); if (!activeQuestion) { return null; } return (
{prompt.questions.length > 1 ? ( {questionIndex + 1}/{prompt.questions.length} ) : null} {activeQuestion.header}

{activeQuestion.question}

{activeQuestion.multiSelect ? (

Select one or more options.

) : null}
{activeQuestion.options.map((option, index) => { const isSelected = progress.selectedOptionLabels.includes(option.label); const shortcutKey = index < 9 ? index + 1 : null; return ( ); })}
); });