From 9abf76f5f23512211dfddd9c2ffda7efef2adb43 Mon Sep 17 00:00:00 2001
From: Etienne <45695613+etiennejouan@users.noreply.github.com>
Date: Mon, 20 Jul 2026 18:53:51 +0200
Subject: [PATCH] fix(ai): show answered ask_questions as a card in chat
history (#23075)
## Context
Feedback on the `ask_questions` (Ask AI) tool:
- When answering a select prompt with a free-form message, the answer
wasn't surfaced as expected in the conversation history.
- The selected value also looked dropped once picked.
Root cause: answered `ask_questions` parts were caught by the
thinking-steps grouping and rendered as a generic collapsible "Ran
ask_questions" tool step (JSON output), so the dedicated renderer was
never reached.
## Changes
- **Render answered questions as a card**
(`AiChatQuestionStatusRenderer`): an "Answers" card that shows each full
question with the chosen option label(s) or the free-text answer beneath
it, instead of the faint inline `header: value` line.
- **Free-text keyboard navigation** (`AiChatQuestionCard`): pressing
Enter in the free-text area now advances to the next question, or
submits when on the last question (mirroring the option-select flow).
Shift+Enter still inserts a newline.
## Notes
- No schema/GraphQL changes; display + interaction only.
- Existing `thinkingStepsDisplayState` grouping test is unaffected (only
`web_search`/`create_task`/`code_interpreter` are used there).
fixes
https://discord.com/channels/1130383047699738754/1526871110300209282
---
.../ai/components/AiChatQuestionCard.tsx | 7 ++
.../AiChatQuestionStatusRenderer.tsx | 82 +++++++++++--------
.../utils/groupContiguousThinkingStepParts.ts | 3 +-
.../ai/utils/isAskQuestionsToolPart.ts | 8 ++
4 files changed, 64 insertions(+), 36 deletions(-)
create mode 100644 packages/twenty-front/src/modules/ai/utils/isAskQuestionsToolPart.ts
diff --git a/packages/twenty-front/src/modules/ai/components/AiChatQuestionCard.tsx b/packages/twenty-front/src/modules/ai/components/AiChatQuestionCard.tsx
index e36ec0eb1b..b181c10f36 100644
--- a/packages/twenty-front/src/modules/ai/components/AiChatQuestionCard.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AiChatQuestionCard.tsx
@@ -330,6 +330,13 @@ export const AiChatQuestionCard = ({
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
+
+ if (!isLastQuestion) {
+ setCurrentIndex(currentIndex + 1);
+
+ return;
+ }
+
handleSend();
}
};
diff --git a/packages/twenty-front/src/modules/ai/components/AiChatQuestionStatusRenderer.tsx b/packages/twenty-front/src/modules/ai/components/AiChatQuestionStatusRenderer.tsx
index 6f8e855d69..27fd4b383e 100644
--- a/packages/twenty-front/src/modules/ai/components/AiChatQuestionStatusRenderer.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AiChatQuestionStatusRenderer.tsx
@@ -22,26 +22,40 @@ const StyledContainer = styled.div`
}
`;
-const StyledContent = styled.div`
- display: flex;
- flex-direction: column;
- gap: ${themeCssVariables.spacing['0.5']};
- min-width: 0;
-`;
-
const StyledMessage = styled.span`
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.medium};
`;
-const StyledAnswerLine = styled.span`
+const StyledAnswersCard = styled.div`
+ background-color: ${themeCssVariables.background.transparent.lighter};
+ border: 1px solid ${themeCssVariables.border.color.medium};
+ border-radius: ${themeCssVariables.border.radius.sm};
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ gap: ${themeCssVariables.spacing[2]};
+ padding: ${themeCssVariables.spacing[3]};
+`;
+
+const StyledAnswerBlock = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${themeCssVariables.spacing['0.5']};
+ min-width: 0;
+`;
+
+const StyledAnswerQuestion = styled.span`
color: ${themeCssVariables.font.color.tertiary};
font-size: ${themeCssVariables.font.size.sm};
+ overflow-wrap: anywhere;
`;
const StyledAnswerValue = styled.span`
color: ${themeCssVariables.font.color.secondary};
+ font-size: ${themeCssVariables.font.size.sm};
+ overflow-wrap: anywhere;
`;
export const AiChatQuestionStatusRenderer = ({
@@ -79,34 +93,32 @@ export const AiChatQuestionStatusRenderer = ({
const answers = result?.answers ?? [];
return (
-
-
-
- {t`Questions answered`}
- {questions.map((question, index) => {
- const answer = answers.find(
- (candidate) => candidate.questionIndex === index,
- );
- const selectedLabels = (answer?.selectedOptionIndices ?? [])
- .map((optionIndex) => question.options[optionIndex]?.label)
- .filter(isNonEmptyString);
- const freeTextAnswer = answer?.freeText ?? '';
- const value =
- freeTextAnswer.length > 0
- ? freeTextAnswer
- : selectedLabels.join(', ');
+
+ {t`Answers`}
+ {questions.map((question, index) => {
+ const answer = answers.find(
+ (candidate) => candidate.questionIndex === index,
+ );
+ const selectedLabels = (answer?.selectedOptionIndices ?? [])
+ .map((optionIndex) => question.options[optionIndex]?.label)
+ .filter(isNonEmptyString);
+ const freeTextAnswer = answer?.freeText ?? '';
+ const value =
+ freeTextAnswer.length > 0
+ ? freeTextAnswer
+ : selectedLabels.join(', ');
- if (value.length === 0) {
- return null;
- }
+ if (value.length === 0) {
+ return null;
+ }
- return (
-
- {question.header}: {value}
-
- );
- })}
-
-
+ return (
+
+ {question.question}
+ {value}
+
+ );
+ })}
+
);
};
diff --git a/packages/twenty-front/src/modules/ai/utils/groupContiguousThinkingStepParts.ts b/packages/twenty-front/src/modules/ai/utils/groupContiguousThinkingStepParts.ts
index 663d4c2cbb..f63d92f73b 100644
--- a/packages/twenty-front/src/modules/ai/utils/groupContiguousThinkingStepParts.ts
+++ b/packages/twenty-front/src/modules/ai/utils/groupContiguousThinkingStepParts.ts
@@ -1,6 +1,7 @@
import { type ExtendedUIMessagePart } from 'twenty-shared/ai';
import { type AssistantMessageRenderItem } from '@/ai/utils/assistantMessageRenderItem';
+import { isAskQuestionsToolPart } from '@/ai/utils/isAskQuestionsToolPart';
import { isThinkingStepPart } from '@/ai/utils/isThinkingStepPart';
import { type ThinkingStepPart } from '@/ai/utils/thinkingStepPart';
@@ -25,7 +26,7 @@ export const groupContiguousThinkingStepParts = (
continue;
}
- if (isThinkingStepPart(part)) {
+ if (isThinkingStepPart(part) && !isAskQuestionsToolPart(part)) {
currentThinkingParts.push(part);
continue;
}
diff --git a/packages/twenty-front/src/modules/ai/utils/isAskQuestionsToolPart.ts b/packages/twenty-front/src/modules/ai/utils/isAskQuestionsToolPart.ts
new file mode 100644
index 0000000000..69fe625e49
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/isAskQuestionsToolPart.ts
@@ -0,0 +1,8 @@
+import { getToolName, isToolUIPart } from 'ai';
+import {
+ ASK_QUESTIONS_TOOL_NAME,
+ type ExtendedUIMessagePart,
+} from 'twenty-shared/ai';
+
+export const isAskQuestionsToolPart = (part: ExtendedUIMessagePart): boolean =>
+ isToolUIPart(part) && getToolName(part) === ASK_QUESTIONS_TOOL_NAME;