fix(rectification): ask spoken collect questions in the agent body
Independent Staging Quality Gate / validate (push) Successful in 9m32s
Independent Staging Quality Gate / publish (push) Successful in 6m53s

The standalone collect prompt bar reused the choice-card chrome and
duplicated the question. Visibility now comes from the spoken reply,
with an empty-body fallback that posts the persisted focus prompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-30 09:39:47 +08:00
parent 02d4c91626
commit ea0fbd4406
10 changed files with 226 additions and 108 deletions
@@ -59,10 +59,8 @@ import { finalizeRectificationSpokenAndThinking } from "@/lib/rectification-agen
import {
isPersistedFocusId,
parseRectificationChoiceCard,
parseRectificationSpokenCollect,
type ChoiceKey,
type RectificationChoiceCard as ChoiceCardModel,
type RectificationSpokenCollect,
} from "@/lib/rectification-agentic/v9/choice-card";
import type { PublicLanguageModel } from "@/lib/public-models";
import { ChatMessageRow } from "./chat-message-row";
@@ -288,7 +286,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const [savedStatus, setSavedStatus] = useState<"accepted" | "confirmed" | null>(null);
const [candidateResult, setCandidateResult] = useState<CandidateResult>(null);
const [choiceCard, setChoiceCard] = useState<ChoiceCardModel | null>(null);
const [spokenCollect, setSpokenCollect] = useState<RectificationSpokenCollect | null>(null);
const [acceptingCandidateId, setAcceptingCandidateId] = useState<string | null>(null);
const [feedback, setFeedback] = useState<Record<string, "up" | "down" | undefined>>({});
const [copiedMessageKey, setCopiedMessageKey] = useState<string | null>(null);
@@ -391,18 +388,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const applyCaseSnapshot = useCallback((payload: {
latest_result?: unknown;
choice_card?: unknown;
current_question?: unknown;
case?: { accepted_time?: unknown; confirmed_time?: unknown };
} | null) => {
if (!payload) return;
const nextCandidate = parseRectificationCandidateResult(payload.latest_result);
const nextChoice = parseRectificationChoiceCard(payload.choice_card);
const nextSpoken = parseRectificationSpokenCollect(payload.current_question);
const acceptedTime = typeof payload.case?.accepted_time === "string" ? payload.case.accepted_time : null;
const confirmedTime = typeof payload.case?.confirmed_time === "string" ? payload.case.confirmed_time : null;
setCandidateResult(nextCandidate);
setChoiceCard(nextChoice);
setSpokenCollect(nextSpoken);
if (confirmedTime) {
setSavedTime(confirmedTime);
setSavedStatus("confirmed");
@@ -1053,14 +1047,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
choiceCardsOpen.current = showLiveChoiceCard;
updateFollowState();
}, [showLiveChoiceCard, updateFollowState]);
const showLiveSpokenCollect = Boolean(
!choiceCard
&& spokenCollect
&& liveChoiceHost
&& !busy
&& !readonly
&& regeneratingMessageKey === null,
);
const showSelectionCards = Boolean(
candidateResult?.selectionAllowed
&& offeredSelectionOnce
@@ -1075,9 +1061,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const liveChoiceMessageKey = showLiveChoiceCard && liveChoiceHost
? liveChoiceHost.renderKey
: undefined;
const liveSpokenMessageKey = showLiveSpokenCollect && liveChoiceHost
? liveChoiceHost.renderKey
: undefined;
const canSend = !busy && !readonly && !regeneratingMessageKey;
function submitChoice(key: ChoiceKey) {
@@ -1145,9 +1128,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const liveChoice = showLiveChoiceCard && message.renderKey === liveChoiceMessageKey
? choiceCard
: null;
const liveSpoken = showLiveSpokenCollect && message.renderKey === liveSpokenMessageKey
? spokenCollect
: null;
const visibleChoiceCard = settledChoice?.card ?? liveChoice;
const vargaSentence = !message.failed
? vargaSentenceFromMethods(message.completedReceipt?.methods)
@@ -1200,11 +1180,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
onStop={submitStop}
/>
)}
{liveSpoken && (
<section className="rectification-choice-card" aria-label="口述采集题">
<p className="rectification-spoken-collect-prompt">{liveSpoken.prompt}</p>
</section>
)}
</div>
);
})}