fix(rectification): satisfy React compiler lint for collect stem attach
Move the current-question ref off render and persist the collect stem from snapshot/send callbacks so ESLint no longer fails the staging gate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -120,6 +120,25 @@ function currentQuestionFromSnapshot(value: unknown): CurrentQuestionModel | nul
|
||||
};
|
||||
}
|
||||
|
||||
function collectSpokenPromptFromQuestion(question: CurrentQuestionModel | null): string | null {
|
||||
return question?.kind === "collect_spoken" ? question.prompt : null;
|
||||
}
|
||||
|
||||
function attachCollectSpokenStem(current: RenderMessage[], prompt: string): RenderMessage[] {
|
||||
const last = [...current].reverse().find((message) => (
|
||||
message.role === "assistant"
|
||||
&& message.state === "settled"
|
||||
&& !message.failed
|
||||
&& Boolean(message.text)
|
||||
));
|
||||
if (!last) return current;
|
||||
const combined = composeCollectSpokenAssistantText(last.text, prompt);
|
||||
if (combined === last.text) return current;
|
||||
return current.map((item) => (
|
||||
item.renderKey === last.renderKey ? { ...item, text: combined } : item
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
function RectificationCandidateCards({
|
||||
result,
|
||||
@@ -324,7 +343,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const runAbort = useRef<AbortController | null>(null);
|
||||
const choiceActionIds = useRef(new Map<string, string>());
|
||||
const currentQuestionRef = useRef(currentQuestion);
|
||||
currentQuestionRef.current = currentQuestion;
|
||||
const [compactBoard, setCompactBoard] = useState(false);
|
||||
const [boardOpen, setBoardOpen] = useState(false);
|
||||
const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null));
|
||||
@@ -335,6 +353,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
// new cards land the viewport on the bottom only while the reader is there.
|
||||
const conversationAnchor = useConversationScrollAnchor(conversation, true, caseId);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currentQuestionRef.current = currentQuestion;
|
||||
}, [currentQuestion]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const query = window.matchMedia(`(max-width: ${RECTIFICATION_BOARD_SPLIT_MIN_PX - 1}px)`);
|
||||
const update = () => {
|
||||
@@ -399,16 +421,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadCaseSnapshot = useCallback(async () => {
|
||||
const loadCaseSnapshot = useCallback(async (): Promise<CurrentQuestionModel | null | undefined> => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/rectification/cases/${encodeURIComponent(caseId)}?sessionId=${encodeURIComponent(sessionId)}`,
|
||||
{ cache: "no-store" },
|
||||
);
|
||||
if (!response.ok) return;
|
||||
applyCaseSnapshot(await response.json().catch(() => null));
|
||||
if (!response.ok) return undefined;
|
||||
const payload = await response.json().catch(() => null);
|
||||
applyCaseSnapshot(payload);
|
||||
return currentQuestionFromSnapshot(payload?.current_question);
|
||||
} catch {
|
||||
// Snapshot refresh is best-effort; the durable Case remains on the server.
|
||||
return undefined;
|
||||
}
|
||||
}, [applyCaseSnapshot, caseId, sessionId]);
|
||||
|
||||
@@ -420,7 +445,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
)
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((payload) => {
|
||||
if (!controller.signal.aborted) applyCaseSnapshot(payload);
|
||||
if (controller.signal.aborted) return;
|
||||
applyCaseSnapshot(payload);
|
||||
const prompt = collectSpokenPromptFromQuestion(currentQuestionFromSnapshot(payload?.current_question));
|
||||
if (prompt) setMessages((current) => attachCollectSpokenStem(current, prompt));
|
||||
})
|
||||
.catch(() => {
|
||||
// Snapshot refresh is best-effort; the durable Case remains on the server.
|
||||
@@ -428,25 +456,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
return () => controller.abort();
|
||||
}, [applyCaseSnapshot, caseId, sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
const prompt = currentQuestion?.kind === "collect_spoken" ? currentQuestion.prompt : null;
|
||||
if (!prompt || busy || regeneratingMessageKey !== null) return;
|
||||
setMessages((current) => {
|
||||
const last = [...current].reverse().find((message) => (
|
||||
message.role === "assistant"
|
||||
&& message.state === "settled"
|
||||
&& !message.failed
|
||||
&& Boolean(message.text)
|
||||
));
|
||||
if (!last) return current;
|
||||
const combined = composeCollectSpokenAssistantText(last.text, prompt);
|
||||
if (combined === last.text) return current;
|
||||
return current.map((item) => (
|
||||
item.renderKey === last.renderKey ? { ...item, text: combined } : item
|
||||
));
|
||||
});
|
||||
}, [busy, currentQuestion, regeneratingMessageKey]);
|
||||
|
||||
const send = useCallback(async (action: "opening" | "message" | "read_only", messageText: string) => {
|
||||
const trimmed = action === "message" ? messageText.trim() : "";
|
||||
if ((action === "message" && !trimmed) || busy || readonly) return;
|
||||
@@ -459,8 +468,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const userRenderKey = `v9-user-${turnKey}`;
|
||||
const assistantRenderKey = `v9-assistant-${turnKey}`;
|
||||
|
||||
const pendingCollectPrompt = collectSpokenPromptFromQuestion(currentQuestionRef.current);
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
...(action === "message" && pendingCollectPrompt
|
||||
? attachCollectSpokenStem(current, pendingCollectPrompt)
|
||||
: current),
|
||||
...(action === "message"
|
||||
? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage]
|
||||
: []),
|
||||
@@ -667,9 +679,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
|
||||
const parsed = completed && !streamFailed ? parseAgentReply(raw) : { text: "", title: undefined };
|
||||
const succeeded = completed && !streamFailed && Boolean(parsed.text);
|
||||
const collectPrompt = currentQuestionRef.current?.kind === "collect_spoken"
|
||||
? currentQuestionRef.current.prompt
|
||||
: null;
|
||||
const collectPrompt = collectSpokenPromptFromQuestion(currentQuestionRef.current);
|
||||
const settledText = succeeded && collectPrompt
|
||||
? composeCollectSpokenAssistantText(parsed.text, collectPrompt)
|
||||
: parsed.text;
|
||||
@@ -708,12 +718,20 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
setError((current) => current || userFacingRunFailure("run_failed"));
|
||||
}
|
||||
if (succeeded) {
|
||||
const snapshotPrompt = collectSpokenPromptFromQuestion(await loadCaseSnapshot() ?? null);
|
||||
const attachedText = snapshotPrompt
|
||||
? composeCollectSpokenAssistantText(parsed.text, snapshotPrompt)
|
||||
: settledText;
|
||||
if (attachedText !== settledText) {
|
||||
setMessages((current) => current.map((message) => (
|
||||
message.renderKey === assistantRenderKey ? { ...message, text: attachedText } : message
|
||||
)));
|
||||
}
|
||||
onMessagesChange?.([
|
||||
...(action === "message" ? [{ role: "user" as const, text: trimmed }] : []),
|
||||
{ role: "assistant", text: settledText },
|
||||
{ role: "assistant", text: attachedText },
|
||||
]);
|
||||
onCompleted?.();
|
||||
await loadCaseSnapshot();
|
||||
}
|
||||
} catch (caught) {
|
||||
frames.settle();
|
||||
@@ -1010,8 +1028,12 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
if (!response.ok || payload?.ok !== true || typeof payload.assistantMessage !== "string") {
|
||||
throw new Error(payload?.message || payload?.error || "暂时无法重新生成回答");
|
||||
}
|
||||
const collectPrompt = collectSpokenPromptFromQuestion(currentQuestion);
|
||||
const nextText = collectPrompt
|
||||
? composeCollectSpokenAssistantText(payload.assistantMessage, collectPrompt)
|
||||
: payload.assistantMessage;
|
||||
setMessages((current) => current.map((item) => item.renderKey === message.renderKey
|
||||
? { ...item, text: payload.assistantMessage, state: "settled" }
|
||||
? { ...item, text: nextText, state: "settled" }
|
||||
: item));
|
||||
} catch (caught) {
|
||||
setMessages((current) => current.map((item) => item.renderKey === message.renderKey
|
||||
|
||||
Reference in New Issue
Block a user