From eebaf7df142f9d051ddd55be3de44cae03c98166 Mon Sep 17 00:00:00 2001 From: Zamil Majdy Date: Sat, 24 Jan 2026 15:45:34 -0600 Subject: [PATCH] feat(frontend): implement clarification questions UI for agent generation Add interactive form to collect user answers when agent-generator service returns clarifying questions during agent creation/editing. Changes: - Add clarification_needed message type to ChatMessageData - Create ClarificationQuestionsWidget component for collecting answers - Update parseToolResponse to detect clarification_needed responses - Integrate widget into ChatMessage rendering Fixes issue where users had no way to answer clarifying questions, causing the chat to keep retrying without necessary context. --- .../Chat/components/ChatContainer/helpers.ts | 17 ++ .../components/ChatMessage/ChatMessage.tsx | 30 ++++ .../components/ChatMessage/useChatMessage.ts | 13 ++ .../ClarificationQuestionsWidget.tsx | 150 ++++++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 autogpt_platform/frontend/src/components/contextual/Chat/components/ClarificationQuestionsWidget/ClarificationQuestionsWidget.tsx diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/helpers.ts b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/helpers.ts index 9d51003a93..0edd1b411a 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/helpers.ts +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatContainer/helpers.ts @@ -213,6 +213,23 @@ export function parseToolResponse( timestamp: timestamp || new Date(), }; } + if (responseType === "clarification_needed") { + return { + type: "clarification_needed", + toolName, + questions: + (parsedResult.questions as Array<{ + question: string; + keyword: string; + example?: string; + }>) || [], + message: + (parsedResult.message as string) || + "I need more information to proceed.", + sessionId: (parsedResult.session_id as string) || "", + timestamp: timestamp || new Date(), + }; + } if (responseType === "need_login") { return { type: "login_needed", diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/ChatMessage.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/ChatMessage.tsx index a2827ce611..b872a8b25c 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/ChatMessage.tsx +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/ChatMessage.tsx @@ -14,6 +14,7 @@ import { AgentCarouselMessage } from "../AgentCarouselMessage/AgentCarouselMessa import { AIChatBubble } from "../AIChatBubble/AIChatBubble"; import { AuthPromptWidget } from "../AuthPromptWidget/AuthPromptWidget"; import { ChatCredentialsSetup } from "../ChatCredentialsSetup/ChatCredentialsSetup"; +import { ClarificationQuestionsWidget } from "../ClarificationQuestionsWidget/ClarificationQuestionsWidget"; import { ExecutionStartedMessage } from "../ExecutionStartedMessage/ExecutionStartedMessage"; import { MarkdownContent } from "../MarkdownContent/MarkdownContent"; import { NoResultsMessage } from "../NoResultsMessage/NoResultsMessage"; @@ -69,6 +70,7 @@ export function ChatMessage({ isToolResponse, isLoginNeeded, isCredentialsNeeded, + isClarificationNeeded, } = useChatMessage(message); const displayContent = getDisplayContent(message, isUser); @@ -96,6 +98,22 @@ export function ChatMessage({ } } + const handleClarificationAnswers = useCallback( + function handleClarificationAnswers(answers: Record) { + // Format answers as context for the tool to retry + if (onSendMessage) { + const contextMessage = Object.entries(answers) + .map(([keyword, answer]) => `${keyword}: ${answer}`) + .join("\n"); + + onSendMessage( + `I have the answers to your questions:\n\n${contextMessage}\n\nPlease proceed with creating the agent.`, + ); + } + }, + [onSendMessage], + ); + const handleCopy = useCallback( async function handleCopy() { if (message.type !== "message") return; @@ -141,6 +159,18 @@ export function ChatMessage({ ); } + // Render clarification needed messages + if (isClarificationNeeded && message.type === "clarification_needed") { + return ( + + ); + } + // Render login needed messages if (isLoginNeeded && message.type === "login_needed") { // If user is already logged in, show success message instead of auth prompt diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/useChatMessage.ts b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/useChatMessage.ts index 5ee61bc554..142b140c8b 100644 --- a/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/useChatMessage.ts +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ChatMessage/useChatMessage.ts @@ -91,6 +91,18 @@ export type ChatMessageData = credentialsSchema?: Record; message: string; timestamp?: string | Date; + } + | { + type: "clarification_needed"; + toolName: string; + questions: Array<{ + question: string; + keyword: string; + example?: string; + }>; + message: string; + sessionId: string; + timestamp?: string | Date; }; export function useChatMessage(message: ChatMessageData) { @@ -111,5 +123,6 @@ export function useChatMessage(message: ChatMessageData) { isAgentCarousel: message.type === "agent_carousel", isExecutionStarted: message.type === "execution_started", isInputsNeeded: message.type === "inputs_needed", + isClarificationNeeded: message.type === "clarification_needed", }; } diff --git a/autogpt_platform/frontend/src/components/contextual/Chat/components/ClarificationQuestionsWidget/ClarificationQuestionsWidget.tsx b/autogpt_platform/frontend/src/components/contextual/Chat/components/ClarificationQuestionsWidget/ClarificationQuestionsWidget.tsx new file mode 100644 index 0000000000..d75b7b6767 --- /dev/null +++ b/autogpt_platform/frontend/src/components/contextual/Chat/components/ClarificationQuestionsWidget/ClarificationQuestionsWidget.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { Button } from "@/components/atoms/Button/Button"; +import { Card } from "@/components/atoms/Card/Card"; +import { Text } from "@/components/atoms/Text/Text"; +import { cn } from "@/lib/utils"; +import { CheckCircleIcon, QuestionIcon } from "@phosphor-icons/react"; +import { useState } from "react"; + +export interface ClarifyingQuestion { + question: string; + keyword: string; + example?: string; +} + +interface Props { + questions: ClarifyingQuestion[]; + message: string; + onSubmitAnswers: (answers: Record) => void; + onCancel?: () => void; + className?: string; +} + +export function ClarificationQuestionsWidget({ + questions, + message, + onSubmitAnswers, + onCancel, + className, +}: Props) { + const [answers, setAnswers] = useState>({}); + + function handleAnswerChange(keyword: string, value: string) { + setAnswers((prev) => ({ ...prev, [keyword]: value })); + } + + function handleSubmit() { + // Check if all questions are answered + const allAnswered = questions.every((q) => answers[q.keyword]?.trim()); + if (!allAnswered) { + return; + } + onSubmitAnswers(answers); + } + + const allAnswered = questions.every((q) => answers[q.keyword]?.trim()); + + return ( +
+
+
+
+ +
+
+ +
+ +
+ + I need more information + + + {message} + +
+ +
+ {questions.map((q, index) => { + const isAnswered = !!answers[q.keyword]?.trim(); + + return ( +
+
+ {isAnswered ? ( + + ) : ( +
+ {index + 1} +
+ )} +
+ + {q.question} + + {q.example && ( + + Example: {q.example} + + )} +