import { useEffect, useMemo, useRef, useState } from 'react'; import type { ScrollView, TextInput } from 'react-native'; import { useAgentBi } from '../../analytics/mixpanelContext'; import type { SuperagentToolRendererProps } from '../../types'; import { buildAnswersPayload, buildClarificationSubmittedProperties, buildClarificationSuggestedProperties, } from './answersUtils'; import { emptyQuestionState, isQuestionComplete, isSomethingElseSelected, parseClarifyingQuestions, pressSomethingElse, type QuestionState, } from './questionsUtils'; import { parseToolArgs } from '../toolWidgetUtils'; import { useApprovalSubmit } from '../useApprovalSubmit'; type ClarifyingQuestionsProps = Pick & { isKeyboardVisible?: boolean; }; /** State, submission and analytics for ClarifyingQuestionsCard, keeping it pure UI. */ export function useClarifyingQuestions({ isKeyboardVisible, submitToolCallInput, toolCall }: ClarifyingQuestionsProps) { const args = useMemo(() => parseToolArgs(toolCall), [toolCall]); const questions = useMemo(() => parseClarifyingQuestions(args), [args]); const { canAct, error, isSubmitting, submit } = useApprovalSubmit(toolCall, submitToolCallInput); const bi = useAgentBi(); const [states, setStates] = useState>({}); const [currentIndex, setCurrentIndex] = useState(0); const hasFiredSuggested = useRef(false); // once per card, not per render useEffect(() => { if (questions.length === 0 || hasFiredSuggested.current) return; hasFiredSuggested.current = true; void bi.track('Clarification Questions - Suggested', buildClarificationSuggestedProperties(questions)); }, [bi, questions]); const question = questions[currentIndex]; const state = states[currentIndex] ?? emptyQuestionState(currentIndex); // An empty payload still resolves the call, so an unrenderable question can't // leave the turn stuck. const submitAnswers = async (excludeIndex?: number) => { const answers = buildAnswersPayload(questions, states, excludeIndex); if (await submit(true, { answers })) { // false when the server rejected it void bi.track('Clarification Questions - Submitted', buildClarificationSubmittedProperties(questions, answers)); } }; // Skip advances, and only resolves on the last question — excluding this index // so a selection made before skipping isn't submitted (web handleSkip parity). const isLast = currentIndex === questions.length - 1; const skipCurrent = async () => { if (isLast) return submitAnswers(currentIndex); setStates(({ [currentIndex]: _skipped, ...rest }) => rest); setCurrentIndex((index) => Math.min(questions.length - 1, index + 1)); }; const somethingElse = !!question && isSomethingElseSelected(question, state); const updateCurrent = (next: QuestionState) => setStates((prev) => ({ ...prev, [currentIndex]: next })); const [isCustomInputFocused, setIsCustomInputFocused] = useState(false); const scrollRef = useRef(null); const customInputRef = useRef(null); const wasKeyboardVisibleRef = useRef(isKeyboardVisible); useEffect(() => { const wasKeyboardVisible = wasKeyboardVisibleRef.current; wasKeyboardVisibleRef.current = isKeyboardVisible; // The description and nav row reappearing can push the input out of view — // wait a frame for that reflow before scrolling, or scrollToEnd targets the old size. if (wasKeyboardVisible && !isKeyboardVisible && isCustomInputFocused) { requestAnimationFrame(() => scrollRef.current?.scrollToEnd({ animated: true })); } }, [isKeyboardVisible, isCustomInputFocused]); return { canAct, // Free the description and nav row's space while the answer is being typed. collapseForKeyboard: isCustomInputFocused && !!isKeyboardVisible, currentIndex, customInputRef, error, goBack: () => setCurrentIndex((index) => Math.max(0, index - 1)), goNext: () => setCurrentIndex((index) => Math.min(questions.length - 1, index + 1)), isComplete: isQuestionComplete(question, state), isFirst: currentIndex === 0, isLast, isSubmitting, onCustomInputBlur: () => setIsCustomInputFocused(false), onCustomInputFocus: () => setIsCustomInputFocused(true), pressCustomRow: () => { if (question) updateCurrent(pressSomethingElse(question, state)); customInputRef.current?.focus(); }, question, questions, scrollRef, skipCurrent, somethingElse, state, submitAnswers, updateCurrent, }; }