import "./ChatQuestionResponse.css"; import { useCallback, useLayoutEffect, useMemo, useRef, useState, type MutableRefObject } from "react"; import { useTranslation } from "react-i18next"; import type { ChatQuestion, ChatQuestionAnswers, ChatQuestionAnswerValue, ParsedQuestionToolCall } from "../utils/parseQuestionToolCall"; import { formatQuestionAnswer } from "../utils/parseQuestionToolCall"; export interface ChatQuestionResponseProps { parsed: ParsedQuestionToolCall; answered?: boolean; submittedAnswer?: string; compact?: boolean; disabled?: boolean; onSubmit: (answerText: string, structured: Record) => void; } /** * FNXC:ChatQuestionResponse 2026-06-16-19:25: * In-chat question tools need an attractive shared answer affordance for single-select, multi-select, free-text, and confirm prompts. * Historical or already-answered messages must render read-only so old assistant questions do not keep duplicate live input boxes in regular chat or quick chat. */ export function ChatQuestionResponse({ parsed, answered = false, submittedAnswer, compact = false, disabled = false, onSubmit, }: ChatQuestionResponseProps) { const { t } = useTranslation("app"); const [answers, setAnswers] = useState({}); const textareaRefs = useRef(new Map()); const isValid = useMemo( () => parsed.questions.every((question) => isQuestionAnswerValid(question, answers[question.id])), [answers, parsed.questions], ); useLayoutEffect(() => { for (const textarea of textareaRefs.current.values()) { textarea.style.height = "0"; textarea.style.height = `${textarea.scrollHeight}px`; } }, [answers]); const setQuestionAnswer = useCallback((questionId: string, value: ChatQuestionAnswerValue) => { setAnswers((current) => ({ ...current, [questionId]: value })); }, []); const toggleMultiSelect = useCallback((questionId: string, optionId: string, checked: boolean) => { setAnswers((current) => { const currentValue = current[questionId]; const selected = Array.isArray(currentValue) ? currentValue : []; return { ...current, [questionId]: checked ? [...selected, optionId] : selected.filter((id) => id !== optionId), }; }); }, []); const handleSubmit = useCallback(() => { if (!isValid || answered || disabled) { return; } const answerText = formatQuestionAnswer(parsed.questions, answers); onSubmit(answerText, answers); }, [answers, answered, disabled, isValid, onSubmit, parsed.questions]); return (
{t("chat.questionResponseEyebrow", "Assistant question")} {answered && {t("chat.questionAnsweredLabel", "Answered")}}
{parsed.questions.map((question, questionIndex) => (
{question.header &&

{question.header}

}

{question.question}

{question.description &&

{question.description}

} {answered ? null : ( )}
))}
{answered ? (
{t("chat.questionSubmittedAnswerLabel", "Submitted answer")}
{submittedAnswer || t("chat.questionAnsweredWithoutContent", "A later user reply answered this question.")}
) : (

{t("chat.questionSelectHint", "Answer all questions to continue the chat.")}

)}
); } interface QuestionControlsProps { question: ChatQuestion; questionIndex: number; value: ChatQuestionAnswerValue | undefined; disabled: boolean; setQuestionAnswer: (questionId: string, value: ChatQuestionAnswerValue) => void; toggleMultiSelect: (questionId: string, optionId: string, checked: boolean) => void; textareaRefs: MutableRefObject>; } function QuestionControls({ question, questionIndex, value, disabled, setQuestionAnswer, toggleMultiSelect, textareaRefs, }: QuestionControlsProps) { const { t } = useTranslation("app"); if (question.type === "text") { return (