From 2b0322c5ce90b5513783cb27bc464fb0e4416b96 Mon Sep 17 00:00:00 2001 From: Xi Zhang Date: Mon, 1 Jun 2026 01:15:03 +0100 Subject: [PATCH] feat: implement AskUserInterrupt component and integrate user question handling in ChatInterface and ChatMessage --- src/app/components/AskUserInterrupt.tsx | 168 ++++++++++++++++++++++++ src/app/components/ChatInterface.tsx | 61 ++++++++- src/app/components/ChatMessage.tsx | 3 + src/app/components/ToolCallBox.tsx | 30 ++++- 4 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 src/app/components/AskUserInterrupt.tsx diff --git a/src/app/components/AskUserInterrupt.tsx b/src/app/components/AskUserInterrupt.tsx new file mode 100644 index 0000000..5f941f4 --- /dev/null +++ b/src/app/components/AskUserInterrupt.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { MessageCircleQuestion } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface Choice { + value: string; +} + +export interface AskUserQuestion { + question: string; + type: "text" | "multiple_choice"; + choices?: Choice[]; + required?: boolean; +} + +interface AskUserInterruptProps { + questions: AskUserQuestion[]; + onSubmit: (answers: string[]) => void; + onCancel: () => void; + isLoading?: boolean; +} + +const OTHER = "__other__"; + +export function AskUserInterrupt({ + questions, + onSubmit, + onCancel, + isLoading, +}: AskUserInterruptProps) { + // answers[i] = final answer string; picked[i] = selected choice value or + // OTHER for multiple_choice (null for free-text questions). + const [answers, setAnswers] = useState(() => + questions.map(() => "") + ); + const [picked, setPicked] = useState<(string | null)[]>(() => + questions.map(() => null) + ); + + const setAnswer = (i: number, value: string) => + setAnswers((prev) => prev.map((a, idx) => (idx === i ? value : a))); + const setPick = (i: number, value: string | null) => + setPicked((prev) => prev.map((p, idx) => (idx === i ? value : p))); + + const isOptional = (q: AskUserQuestion) => + q.type === "text" && q.required === false; + + const canSubmit = questions.every( + (q, i) => isOptional(q) || answers[i].trim() !== "" + ); + + return ( +
+
+
+ +
+ {questions.map((q, i) => ( +
+

+ {q.question} + {!isOptional(q) && ( + * + )} +

+ + {q.type === "multiple_choice" ? ( +
+ {q.choices?.map((choice) => { + const selected = picked[i] === choice.value; + return ( + + ); + })} + + {picked[i] === OTHER && ( +