fix(rectification): continue after structured choices
Independent Staging Quality Gate / validate (push) Successful in 12m35s
Independent Staging Quality Gate / publish (push) Successful in 15m16s

This commit is contained in:
Jesse_Chen
2026-08-27 09:23:32 +08:00
parent 1c0ee8db13
commit 518119acbd
23 changed files with 649 additions and 35 deletions
@@ -50,6 +50,7 @@ import { isNearBottom, shouldShowJumpToLatest } from "@/lib/rectification-sticky
import {
CHOICE_ACTION,
STOP_ACTION,
shouldContinueAfterStructuredChoice,
stableChoiceActionKey,
type ChoiceOptionId,
} from "@/lib/rectification-agentic/v9/choice-action";
@@ -285,6 +286,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const [copiedMessageKey, setCopiedMessageKey] = useState<string | null>(null);
const [regeneratingMessageKey, setRegeneratingMessageKey] = useState<string | null>(null);
const [choiceNonce, setChoiceNonce] = useState(0);
const choiceContinuationPending = useRef(false);
const conversation = useRef<HTMLElement>(null);
const workspace = useRef<HTMLDivElement>(null);
const composer = useRef<HTMLTextAreaElement>(null);
@@ -428,7 +430,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
return () => controller.abort();
}, [applyCaseSnapshot, caseId, sessionId]);
const send = useCallback(async (action: "opening" | "message", messageText: string) => {
const send = useCallback(async (action: "opening" | "message" | "read_only", messageText: string) => {
const trimmed = action === "message" ? messageText.trim() : "";
if ((action === "message" && !trimmed) || busy || readonly) return;
setError("");
@@ -472,7 +474,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
action,
modelId: selectedModelId,
...(action === "message" ? { message: trimmed } : {}),
origin: "typed",
origin: action === "read_only" ? "choice_click" : "typed",
clientActionId: requestId,
}),
});
@@ -805,7 +807,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}
const narration = typeof payload?.narration === "string" && payload.narration.trim()
? payload.narration.trim()
: "已记录你的选择。正在准备下一步。";
: "已记录你的选择。";
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
? {
...message,
@@ -820,6 +822,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
]);
onCompleted?.();
await loadCaseSnapshot();
if (shouldContinueAfterStructuredChoice(payload?.nextAction)) {
choiceContinuationPending.current = true;
}
} catch {
setMessages((current) => current.filter((message) => (
message.renderKey !== userRenderKey && message.renderKey !== assistantRenderKey
@@ -843,6 +848,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
setPending,
]);
useEffect(() => {
if (!choiceContinuationPending.current || busy || readonly) return;
choiceContinuationPending.current = false;
void send("read_only", "");
}, [busy, readonly, send]);
useEffect(() => {
if (initialTurns.length > 0) {
if (shouldStartOpening) onOpeningConsumed?.();