feat(F0AiChatTextArea): inline clarifying questions#3611
feat(F0AiChatTextArea): inline clarifying questions#3611
Conversation
Adds a multi-step clarifying question panel that expands inside the chat textarea. The AI can trigger it via the CopilotKit action 'AiWidgets.F0ClarifyingQuestion' to gather structured input from the user before executing a task. - ClarifyingQuestion/ClarifyingOption types in F0AiChatTextArea/types.ts - Animated panel (Framer Motion) with skeleton → checkboxes crossfade - Back button (step 2+), Next/Submit primary, enable logic (checkbox or text) - Placeholder changes to 'Type your answer…' while question is active - useClarifyingQuestionAction: CopilotKit action + ClarifyingQuestionController (ref-stabilised callbacks to avoid infinite render loops) - State wired through AiChatStateProvider → ChatTextarea → F0AiChatTextArea - WithClarifyingQuestions Storybook story for local validation
✅ No New Circular DependenciesNo new circular dependencies detected. Current count: 0 |
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
There was a problem hiding this comment.
Pull request overview
Adds an inline “clarifying question” flow to the AI chat input (F0AiChatTextArea), allowing the backend to prompt multi-step checkbox questions directly inside the textarea UI.
Changes:
- Introduced
ClarifyingQuestion/ClarifyingOptiontypes and a newclarifyingQuestionprop onF0AiChatTextArea. - Rendered an inline expanding panel (with skeleton/loading state) above the textarea, including checkbox options and navigation controls.
- Added a CopilotKit action (
AiWidgets.F0ClarifyingQuestion) plus provider plumbing to manage/display clarifying questions from the AI chat state.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/sds/ai/F0AiChatTextArea/types.ts | Adds public types for clarifying question data/callbacks and wires the new prop into F0AiChatTextAreaProps. |
| packages/react/src/sds/ai/F0AiChatTextArea/stories/F0AiChatTextArea.stories.tsx | Adds a Storybook scenario demonstrating a multi-step clarifying question flow. |
| packages/react/src/sds/ai/F0AiChatTextArea/F0AiChatTextArea.tsx | Implements the inline clarifying question panel, confirm/back controls, and submit behavior updates (including input clearing). |
| packages/react/src/sds/ai/F0AiChat/providers/AiChatStateProvider.tsx | Adds clarifyingQuestion state + setter to the AI chat context. |
| packages/react/src/sds/ai/F0AiChat/internal-types.ts | Extends the provider return type to expose clarifyingQuestion and setClarifyingQuestion. |
| packages/react/src/sds/ai/F0AiChat/copilotActions/useDefaultCopilotActions.ts | Registers the new clarifying question CopilotKit action by default. |
| packages/react/src/sds/ai/F0AiChat/copilotActions/useClarifyingQuestionAction.tsx | New CopilotKit action + controller to drive multi-step clarifying question state and submit aggregated selections as a user message. |
| packages/react/src/sds/ai/F0AiChat/components/ChatTextarea.tsx | Passes clarifyingQuestion from context into F0AiChatTextArea. |
| {/* Clarifying question panel — expands inside the form above the textarea */} | ||
| <AnimatePresence> | ||
| {clarifyingQuestion && ( | ||
| <motion.div | ||
| key="clarifying-question" | ||
| initial={{ opacity: 0, height: 0 }} | ||
| animate={{ opacity: 1, height: "auto" }} | ||
| exit={{ opacity: 0, height: 0 }} | ||
| transition={{ duration: 0.25, ease: "easeOut" }} | ||
| className="overflow-hidden" |
There was a problem hiding this comment.
The new clarifying-question panel introduces multiple animations (AnimatePresence + motion height/opacity), but the component doesn’t respect prefers-reduced-motion. packages/react/AGENTS.md recommends using useReducedMotion() for animated components and setting durations to 0 when true.
| <ButtonInternal | ||
| type="button" | ||
| variant="outline" | ||
| label="Back" | ||
| icon={ArrowLeft} | ||
| hideLabel | ||
| onClick={clarifyingQuestion.onBack} | ||
| /> |
There was a problem hiding this comment.
The Back button label is hardcoded ("Back"). Since this is user-facing UI copy, it should come from useI18n() (or be provided via the clarifyingQuestion prop) to keep the component fully localizable.
| parts.push(labels.join(", ")) | ||
| } | ||
| }) | ||
| sendMessage(parts.join(" | ")) |
There was a problem hiding this comment.
handleConfirm only serializes selected checkbox labels into sendMessage(...). If the user answers via free text (which the UI currently allows), that text is ignored and sendMessage may be called with an empty string when no options are selected. Align the behavior with the action description by including the textarea text in the confirm payload (and consider sending option IDs + question context rather than only labels to make backend parsing reliable).
| sendMessage(parts.join(" | ")) | |
| const payload = parts.join(" | ").trim() | |
| if (payload.length === 0) { | |
| return | |
| } | |
| sendMessage(payload) |
| setLoadingNext(true) | ||
| setTimeout(() => { | ||
| setStepIndex((prev) => prev + 1) | ||
| setLoadingNext(false) | ||
| }, 600) |
There was a problem hiding this comment.
The setTimeout used to advance to the next step isn’t cleaned up on unmount. If the action is removed/unmounted before the timeout fires, this can call setStepIndex/setLoadingNext on an unmounted component (React warning + potential leak). Store the timeout id in a ref and clear it in an effect cleanup/unmount handler.
| setClarifyingQuestion({ | ||
| question: currentStep.question, | ||
| options: currentStep.options, | ||
| selectedOptionIds: selections[stepIndex] ?? [], | ||
| onToggleOption: handleToggle, | ||
| onConfirm: handleConfirm, | ||
| onBack: handleBack, | ||
| confirmLabel: isLastStep ? "Submit" : "Next", | ||
| loading: false, |
There was a problem hiding this comment.
confirmLabel is set to hardcoded strings ("Submit" / "Next"). Since these surface in the UI, they should be localized (via useI18n()) or passed in from the backend so the action works correctly across locales.
| onToggleOption: (id: string) => void | ||
| /** Called when the user confirms and moves forward */ | ||
| onConfirm: () => void | ||
| /** Optional: called when the user wants to go back to a previous step */ | ||
| onBack?: () => void | ||
| /** Step label, e.g. "1 / 3" */ |
There was a problem hiding this comment.
PR description mentions confirm/cancel (dismiss) actions for the inline clarifying question, but the proposed ClarifyingQuestion API only supports onConfirm and optional onBack. If users need to dismiss the clarifying flow, consider adding an explicit onDismiss/onCancel callback (and a corresponding UI control) so the interaction can be exited deterministically.
| mentions.close() | ||
| if (inProgress) { | ||
| onStop?.() | ||
| } else if (clarifyingQuestion && !clarifyingQuestion.loading) { |
There was a problem hiding this comment.
When clarifyingQuestion is active, form submit always calls clarifyingQuestion.onConfirm() and clears inputValue, so any free-text the user typed is never sent/used. Since the confirm button is enabled when hasDataToSend is true, this can drop user input and even confirm with an empty payload. Consider extending ClarifyingQuestion.onConfirm to accept the current text (and/or exposing a controlled text value in clarifyingQuestion), or route free-text submits through onSend when no options are selected.
| } else if (clarifyingQuestion && !clarifyingQuestion.loading) { | |
| } else if (clarifyingQuestion && !clarifyingQuestion.loading && !hasDataToSend) { |
| }, [clarifyingQuestion?.question]) | ||
|
|
||
| const resolvedDefaultPlaceholder = clarifyingQuestion | ||
| ? "Type your answer…" |
There was a problem hiding this comment.
resolvedDefaultPlaceholder uses a hardcoded user-facing string ("Type your answer…") when a clarifying question is present. Per packages/react/AGENTS.md i18n guidelines, user-facing strings should come from useI18n() so they can be translated consistently.
| ? "Type your answer…" | |
| ? translation.ai.inputPlaceholder |
| * { "question": "Which time period?", "options": [...] } | ||
| * ] | ||
| * }} | ||
| */ |
There was a problem hiding this comment.
I am not a big fan of commenting code, TBH is something extra to maintain and also it can consecuent be outdated with every change.
Plus, the code is enough clear at first look, could we remove these?
Description
Adds support for inline clarifying questions in
F0AiChatTextArea. When the AI returns a clarifying question, it is displayed inline inside the textarea area with confirm/cancel actions, without leaving the chat flow.The idea is to replace the QuestionCard we made for the sales skill and have something that can be reused everywhere.
Screenshots (if applicable)
Grabacion.de.pantalla.2026-03-09.a.las.15.42.29.mov
Implementation details
What changed
F0AiChatTextArea: Added support for aquestionStatusprop (or equivalent) that renders a clarifying question inline within the component, with confirm and dismiss actions.Why
The AI chat flow requires a way to present clarifying questions to the user without interrupting or navigating away from the current context. Rendering them inline inside
F0AiChatTextAreakeeps the interaction contained and consistent with the existing chat UX.