fix(rectification): gate collect-phase adopt cards and keep post-adopt verify answerable
Public can_adopt follows session_outcome; reverse_verify reuses the persisted question id; offer cards settle on the owning message with a status-bar handoff. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
RECTIFICATION_TOOL_PROGRESS_LABELS,
|
||||
activityTraceFromReceipt,
|
||||
rectificationCompletedTrail,
|
||||
rectificationLiveProgressLabel,
|
||||
rectificationToolActivityPhase,
|
||||
} from "@/lib/rectification-activity-labels";
|
||||
import {
|
||||
@@ -28,7 +29,8 @@ import {
|
||||
type CompletedActivityReceiptView,
|
||||
} from "@/lib/rectification-activity-receipt";
|
||||
import {
|
||||
canRenderRectificationSelectionCards,
|
||||
canShowRectificationReadonlyRange,
|
||||
canShowRectificationSelectionCards,
|
||||
isRecommendedRectificationCandidate,
|
||||
natalRecastMeaning,
|
||||
parseRectificationCandidateResult,
|
||||
@@ -57,7 +59,7 @@ import {
|
||||
type ChoiceOptionId,
|
||||
} from "@/lib/rectification-agentic/v9/choice-action";
|
||||
import { isRectificationCaseStatus, isResumableStatus, type RectificationCaseStatus } from "@/lib/rectification-agentic/v9/case-status";
|
||||
import { CHOICE_MODE, CHOICE_STOP_LABEL, isPersistedFocusId, parseRectificationChoiceCard, type ChoiceKey, type RectificationChoiceCard as ChoiceCardModel } from "@/lib/rectification-agentic/v9/choice-card";
|
||||
import { CHOICE_MODE, CHOICE_SKIP_QUESTION_LABEL, CHOICE_SKIP_QUESTION_MESSAGE, CHOICE_STOP_LABEL, CHOICE_STOP_MESSAGE, isPersistedFocusId, parseRectificationChoiceCard, type ChoiceKey, type RectificationChoiceCard as ChoiceCardModel } from "@/lib/rectification-agentic/v9/choice-card";
|
||||
import type { PublicLanguageModel } from "@/lib/public-models";
|
||||
import { useConversationScrollAnchor } from "@/hooks/use-conversation-scroll-anchor";
|
||||
import { CharacterRemaining } from "./character-remaining";
|
||||
@@ -80,6 +82,7 @@ type PersistedTurn = Readonly<{
|
||||
text: string | null;
|
||||
status: string;
|
||||
question?: unknown;
|
||||
offer_result_id?: string | null;
|
||||
receipt?: Readonly<{
|
||||
status: string;
|
||||
phases: readonly string[];
|
||||
@@ -87,6 +90,9 @@ type PersistedTurn = Readonly<{
|
||||
tool: string;
|
||||
status: string;
|
||||
methods?: readonly string[];
|
||||
started_at?: string | null;
|
||||
elapsed_ms?: number | null;
|
||||
detail?: Readonly<Record<string, unknown>> | null;
|
||||
}>[];
|
||||
tools: readonly string[];
|
||||
methods?: readonly string[];
|
||||
@@ -109,9 +115,11 @@ type CurrentQuestionModel = Readonly<{
|
||||
function currentQuestionFromSnapshot(value: unknown): CurrentQuestionModel | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const question = value as { kind?: unknown; prompt?: unknown; focus_id?: unknown; question_id?: unknown };
|
||||
if (question.kind !== "choice" && question.kind !== "collect_spoken") return null;
|
||||
if (question.kind !== "choice" && question.kind !== "collect_spoken" && question.kind !== "reverse_verify") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: question.kind,
|
||||
kind: question.kind === "collect_spoken" ? "collect_spoken" : "choice",
|
||||
prompt: typeof question.prompt === "string" && question.prompt.trim()
|
||||
? question.prompt.trim()
|
||||
: null,
|
||||
@@ -126,6 +134,16 @@ function questionSourceFromSnapshot(value: unknown): "focus" | "unavailable" | n
|
||||
return null;
|
||||
}
|
||||
|
||||
function RectificationReadonlyRange({
|
||||
range,
|
||||
}: Readonly<{ range: readonly [string, string] }>) {
|
||||
return (
|
||||
<p className="rectification-readonly-range" role="status">
|
||||
目前范围 {range[0]}–{range[1]},还在收窄
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function RectificationCandidateCards({
|
||||
result,
|
||||
acceptingCandidateId,
|
||||
@@ -200,20 +218,31 @@ type RenderMessage = ChatMessageView & {
|
||||
turnId?: string;
|
||||
activityTrace?: readonly AgentActivityTraceItem[];
|
||||
question?: TurnQuestion;
|
||||
candidateOffer?: Readonly<{ resultId: string }>;
|
||||
};
|
||||
|
||||
function mergeTurnQuestions(current: RenderMessage[], turns: readonly unknown[]): RenderMessage[] {
|
||||
const byId = new Map<string, TurnQuestion | null>();
|
||||
const byId = new Map<string, { question: TurnQuestion | null; offerResultId: string | null }>();
|
||||
for (const item of turns) {
|
||||
if (!item || typeof item !== "object") continue;
|
||||
const turn = item as { id?: unknown; question?: unknown };
|
||||
const turn = item as { id?: unknown; question?: unknown; offer_result_id?: unknown };
|
||||
if (typeof turn.id !== "string") continue;
|
||||
byId.set(turn.id, parseTurnQuestion(turn.question));
|
||||
byId.set(turn.id, {
|
||||
question: parseTurnQuestion(turn.question),
|
||||
offerResultId: typeof turn.offer_result_id === "string" ? turn.offer_result_id : null,
|
||||
});
|
||||
}
|
||||
return current.map((message) => {
|
||||
if (!message.turnId || !byId.has(message.turnId)) return message;
|
||||
const question = byId.get(message.turnId) ?? undefined;
|
||||
return { ...message, question: question ?? undefined };
|
||||
const next = byId.get(message.turnId);
|
||||
const question = next?.question ?? undefined;
|
||||
return {
|
||||
...message,
|
||||
question: question ?? undefined,
|
||||
candidateOffer: next?.offerResultId
|
||||
? { resultId: next.offerResultId }
|
||||
: message.candidateOffer,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -271,8 +300,8 @@ function choiceCardFromQuestion(
|
||||
answer_class: "unsure",
|
||||
role: "primary",
|
||||
})),
|
||||
stop_label: CHOICE_STOP_LABEL,
|
||||
stop_message: "先这样",
|
||||
stop_label: question.kind === "reverse_verify" ? CHOICE_SKIP_QUESTION_LABEL : CHOICE_STOP_LABEL,
|
||||
stop_message: question.kind === "reverse_verify" ? CHOICE_SKIP_QUESTION_MESSAGE : CHOICE_STOP_MESSAGE,
|
||||
scoring: question.kind !== "reverse_verify",
|
||||
probe_id: question.probe_id,
|
||||
case_revision: null,
|
||||
@@ -342,6 +371,9 @@ function messagesFromTurns(initialTurns: readonly PersistedTurn[]): RenderMessag
|
||||
failed,
|
||||
turnId: turn.id,
|
||||
question: parseTurnQuestion(turn.question) ?? undefined,
|
||||
candidateOffer: typeof turn.offer_result_id === "string"
|
||||
? { resultId: turn.offer_result_id }
|
||||
: undefined,
|
||||
}];
|
||||
}
|
||||
if (isIncompleteRunBanner(turn.text ?? "")) return [];
|
||||
@@ -404,6 +436,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const runAbort = useRef<AbortController | null>(null);
|
||||
const choiceActionIds = useRef(new Map<string, string>());
|
||||
const currentQuestionRef = useRef(currentQuestion);
|
||||
const offerSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
const runStartedAtRef = useRef(0);
|
||||
const stepStartedAtRef = useRef(0);
|
||||
const liveToolRef = useRef<string | null>(null);
|
||||
const liveBaseLabelRef = useRef("正在处理…");
|
||||
const [compactBoard, setCompactBoard] = useState(false);
|
||||
const [boardOpen, setBoardOpen] = useState(false);
|
||||
const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null));
|
||||
@@ -447,6 +484,75 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onPendingChange?.(value);
|
||||
}, [onPendingChange]);
|
||||
|
||||
const beginLiveRun = useCallback((baseLabel: string) => {
|
||||
const now = Date.now();
|
||||
runStartedAtRef.current = now;
|
||||
stepStartedAtRef.current = now;
|
||||
liveToolRef.current = null;
|
||||
liveBaseLabelRef.current = baseLabel;
|
||||
}, []);
|
||||
|
||||
const rememberLiveActivity = useCallback((baseLabel: string, tool: string | null = liveToolRef.current) => {
|
||||
if (liveBaseLabelRef.current !== baseLabel) {
|
||||
liveBaseLabelRef.current = baseLabel;
|
||||
stepStartedAtRef.current = Date.now();
|
||||
}
|
||||
liveToolRef.current = tool;
|
||||
return rectificationLiveProgressLabel({
|
||||
tool,
|
||||
baseLabel,
|
||||
stepStartedAt: stepStartedAtRef.current,
|
||||
runStartedAt: runStartedAtRef.current,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!busy) return;
|
||||
const id = window.setInterval(() => {
|
||||
const label = rectificationLiveProgressLabel({
|
||||
tool: liveToolRef.current,
|
||||
baseLabel: liveBaseLabelRef.current,
|
||||
stepStartedAt: stepStartedAtRef.current,
|
||||
runStartedAt: runStartedAtRef.current,
|
||||
});
|
||||
setMessages((current) => current.map((message) => {
|
||||
if (message.state !== "thinking" && message.state !== "streaming") return message;
|
||||
if (!message.activity || message.activity.label === label) return message;
|
||||
return { ...message, activity: { ...message.activity, label } };
|
||||
}));
|
||||
}, 4000);
|
||||
return () => window.clearInterval(id);
|
||||
}, [busy]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!candidateResult?.resultId) return;
|
||||
const canOffer = canShowRectificationSelectionCards(candidateResult);
|
||||
const adopted = Boolean(candidateResult.selectedTime);
|
||||
if (!canOffer && !adopted) return;
|
||||
const resultId = candidateResult.resultId;
|
||||
queueMicrotask(() => {
|
||||
setMessages((current) => {
|
||||
if (current.some((message) => message.candidateOffer?.resultId === resultId)) return current;
|
||||
if (adopted && current.some((message) => message.candidateOffer)) {
|
||||
return current.map((message) => (
|
||||
message.candidateOffer ? { ...message, candidateOffer: { resultId } } : message
|
||||
));
|
||||
}
|
||||
if (!canOffer || adopted) return current;
|
||||
const owner = [...current].reverse().find((message) => (
|
||||
message.role === "assistant"
|
||||
&& message.state === "settled"
|
||||
&& Boolean(message.text)
|
||||
&& !message.failed
|
||||
));
|
||||
if (!owner) return current;
|
||||
return current.map((message) => (
|
||||
message.renderKey === owner.renderKey ? { ...message, candidateOffer: { resultId } } : message
|
||||
));
|
||||
});
|
||||
});
|
||||
}, [candidateResult]);
|
||||
|
||||
// Candidate snapshot comes from the persisted Candidate Snapshot API, never
|
||||
// from parsing agent text or hidden sentinels.
|
||||
const applyCaseSnapshot = useCallback((payload: {
|
||||
@@ -529,6 +635,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
if ((action === "message" && !trimmed) || busy || readonly) return;
|
||||
setError("");
|
||||
setPending(true);
|
||||
beginLiveRun("正在处理…");
|
||||
|
||||
keyCounter.current += 1;
|
||||
const requestId = globalThis.crypto.randomUUID();
|
||||
@@ -658,14 +765,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
activityTrace = freezeLiveThink(activityTrace);
|
||||
currentActivity = nextActivityView(currentActivity, {
|
||||
phase: "answer-composition",
|
||||
label: "正在组织回答…",
|
||||
label: rememberLiveActivity("正在组织回答…"),
|
||||
});
|
||||
frames.setAnswer(raw);
|
||||
} else if (event.type === "activity.changed" && isPublicRectificationActivity(event.activity)) {
|
||||
const activity = event.activity;
|
||||
currentActivity = nextActivityView(currentActivity, {
|
||||
phase: "evidence-validation",
|
||||
label: RECTIFICATION_ACTIVITY_PROGRESS_LABELS[activity],
|
||||
label: rememberLiveActivity(RECTIFICATION_ACTIVITY_PROGRESS_LABELS[activity]),
|
||||
});
|
||||
frames.touch();
|
||||
} else if (event.type === "attempt.reset") {
|
||||
@@ -676,7 +783,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
completedTurnId = undefined;
|
||||
currentActivity = nextActivityView(undefined, {
|
||||
phase: "evidence-validation",
|
||||
label: "正在处理…",
|
||||
label: rememberLiveActivity("正在处理…", null),
|
||||
});
|
||||
frames.reset();
|
||||
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
|
||||
@@ -714,7 +821,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
);
|
||||
currentActivity = nextActivityView(currentActivity, {
|
||||
phase: rectificationToolActivityPhase(tool),
|
||||
label: RECTIFICATION_TOOL_PROGRESS_LABELS[tool],
|
||||
label: rememberLiveActivity(RECTIFICATION_TOOL_PROGRESS_LABELS[tool], tool),
|
||||
completedTrail: rectificationCompletedTrail(activityReceiptState.completedSteps),
|
||||
});
|
||||
frames.touch();
|
||||
@@ -736,7 +843,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
|
||||
currentActivity = nextActivityView(currentActivity, {
|
||||
phase: rectificationToolActivityPhase(tool),
|
||||
label: RECTIFICATION_TOOL_DONE_LABELS[tool],
|
||||
label: rememberLiveActivity(RECTIFICATION_TOOL_DONE_LABELS[tool], tool),
|
||||
completedTrail: rectificationCompletedTrail(activityReceiptState.completedSteps),
|
||||
});
|
||||
frames.touch();
|
||||
@@ -833,7 +940,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
if (runAbort.current === abortController) runAbort.current = null;
|
||||
setPending(false);
|
||||
}
|
||||
}, [busy, caseId, loadCaseSnapshot, onCompleted, onMessagesChange, onProfileIncomplete, readonly, selectedModelId, sessionId, setPending]);
|
||||
}, [beginLiveRun, busy, caseId, loadCaseSnapshot, onCompleted, onMessagesChange, onProfileIncomplete, readonly, rememberLiveActivity, selectedModelId, sessionId, setPending]);
|
||||
|
||||
const actionIdForChoice = useCallback((focusId: string, optionId: ChoiceOptionId) => {
|
||||
const key = stableChoiceActionKey(focusId, optionId);
|
||||
@@ -847,19 +954,29 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const submitStructuredChoice = useCallback(async (
|
||||
action: typeof CHOICE_ACTION | typeof STOP_ACTION,
|
||||
optionId: ChoiceKey | "stop",
|
||||
override?: Readonly<{
|
||||
focusId: string;
|
||||
questionId: string | null;
|
||||
probeId?: string | null;
|
||||
caseRevision?: number | null;
|
||||
}>,
|
||||
) => {
|
||||
if (!choiceCard || busy || readonly) return;
|
||||
const focusId = choiceCard.focus_id;
|
||||
const focusId = override?.focusId ?? choiceCard?.focus_id;
|
||||
if (!focusId || busy || readonly) return;
|
||||
if (!override && !choiceCard) return;
|
||||
if (!isPersistedFocusId(focusId)) {
|
||||
setError("当前选择题已失效,请等待下一问。");
|
||||
return;
|
||||
}
|
||||
const questionId = choiceCard.question_id;
|
||||
const questionId = override?.questionId ?? choiceCard?.question_id;
|
||||
const probeId = override?.probeId ?? choiceCard?.probe_id;
|
||||
const expectedRevision = override?.caseRevision ?? choiceCard?.case_revision ?? 0;
|
||||
const actionId = actionIdForChoice(focusId, optionId);
|
||||
setError("");
|
||||
keyCounter.current += 1;
|
||||
const turnKey = keyCounter.current;
|
||||
const assistantRenderKey = `v9-choice-assistant-${turnKey}`;
|
||||
beginLiveRun(RECTIFICATION_ACTIVITY_PROGRESS_LABELS.recording_answer);
|
||||
setMessages((current) => [
|
||||
...markQuestionAnswered(current, focusId, optionId),
|
||||
{
|
||||
@@ -888,9 +1005,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
actionId,
|
||||
focusId,
|
||||
questionId,
|
||||
probeId: choiceCard.probe_id,
|
||||
probeId: probeId ?? null,
|
||||
optionId: optionId === "stop" ? undefined : optionId,
|
||||
expectedRevision: choiceCard.case_revision ?? 0,
|
||||
expectedRevision,
|
||||
origin: "choice_click",
|
||||
clientActionId: actionId,
|
||||
}),
|
||||
@@ -950,8 +1067,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
setPending(false);
|
||||
}
|
||||
}, [
|
||||
actionIdForChoice,
|
||||
busy,
|
||||
beginLiveRun,
|
||||
caseId,
|
||||
choiceCard,
|
||||
loadCaseSnapshot,
|
||||
@@ -1119,26 +1235,40 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
latestLiveQuestion
|
||||
&& latestLiveQuestion.options?.length === 4
|
||||
&& currentQuestion?.focus_id === latestLiveQuestion.focus_id
|
||||
&& (latestLiveQuestion.kind === "choice" || latestLiveQuestion.kind === "reverse_verify")
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null,
|
||||
);
|
||||
const canOfferCards = canShowRectificationSelectionCards(candidateResult);
|
||||
const persistedOfferKey = messages.find((message) => message.candidateOffer)?.renderKey;
|
||||
const selectionCardMessageKey = persistedOfferKey
|
||||
?? (canOfferCards && !candidateResult?.selectedTime ? latestSettledAssistant?.renderKey : undefined);
|
||||
const showSelectionCards = Boolean(
|
||||
candidateResult?.selectionAllowed
|
||||
&& candidateResult?.canAdopt
|
||||
&& canRenderRectificationSelectionCards(candidateResult)
|
||||
candidateResult
|
||||
&& selectionCardMessageKey
|
||||
&& (canOfferCards || Boolean(candidateResult.selectedTime)),
|
||||
);
|
||||
const showReadonlyRange = Boolean(
|
||||
canShowRectificationReadonlyRange(candidateResult)
|
||||
&& latestSettledAssistant
|
||||
&& !showLiveChoiceCard
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null,
|
||||
&& !showSelectionCards,
|
||||
);
|
||||
const selectionCardMessageKey = showSelectionCards && latestSettledAssistant
|
||||
? latestSettledAssistant.renderKey
|
||||
: undefined;
|
||||
const collectSpokenPrompt = currentQuestion?.kind === "collect_spoken"
|
||||
? currentQuestion.prompt
|
||||
: null;
|
||||
const showCollectStop = Boolean(
|
||||
latestLiveQuestion
|
||||
&& latestLiveQuestion.kind === "collect_spoken"
|
||||
&& currentQuestion?.focus_id === latestLiveQuestion.focus_id
|
||||
&& !questionIsAnswered(latestLiveQuestion)
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null,
|
||||
);
|
||||
const adoptedRangeLabel = candidateResult?.credibleRange
|
||||
? `${candidateResult.credibleRange[0]}–${candidateResult.credibleRange[1]}`
|
||||
: null;
|
||||
const resumableCase = caseStatus !== null && isResumableStatus(caseStatus);
|
||||
const liveQuestionOnMessages = messages.some((message) => (
|
||||
message.role === "assistant"
|
||||
@@ -1185,6 +1315,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
void submitStructuredChoice(STOP_ACTION, "stop");
|
||||
}
|
||||
|
||||
function submitCollectStop() {
|
||||
if (!latestLiveQuestion || latestLiveQuestion.kind !== "collect_spoken") return;
|
||||
void submitStructuredChoice(STOP_ACTION, "stop", {
|
||||
focusId: latestLiveQuestion.focus_id,
|
||||
questionId: latestLiveQuestion.question_id,
|
||||
probeId: latestLiveQuestion.probe_id,
|
||||
caseRevision: choiceCard?.case_revision ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
const boardPeek = compactBoard && !boardOpen ? (
|
||||
<RectificationBoardPeek
|
||||
result={candidateResult}
|
||||
@@ -1254,11 +1394,17 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
question
|
||||
&& currentQuestion?.focus_id === question.focus_id
|
||||
&& !questionIsAnswered(question)
|
||||
&& choiceCard
|
||||
&& choiceCard.focus_id === question.focus_id
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null,
|
||||
&& regeneratingMessageKey === null
|
||||
&& (
|
||||
question.kind === "collect_spoken"
|
||||
|| (
|
||||
choiceCard
|
||||
&& choiceCard.focus_id === question.focus_id
|
||||
&& (question.kind === "choice" || question.kind === "reverse_verify")
|
||||
)
|
||||
),
|
||||
);
|
||||
const embeddedCard = question ? choiceCardFromQuestion(question, liveQuestion ? choiceCard : null) : null;
|
||||
const afterAnswer = question && displayedMessage.state === "settled"
|
||||
@@ -1277,11 +1423,20 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onStop={submitStop}
|
||||
/>
|
||||
)}
|
||||
{question.status === "skipped" && (
|
||||
<p className="rectification-question-skipped" role="status">
|
||||
{savedTime ? `已跳过(已采用 ${savedTime})` : "已跳过"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: undefined;
|
||||
return (
|
||||
<div key={message.renderKey} className="rectification-message-wrap rectification-message-entry">
|
||||
<div
|
||||
key={message.renderKey}
|
||||
className="rectification-message-wrap rectification-message-entry"
|
||||
ref={showSelectionCards && message.renderKey === selectionCardMessageKey ? offerSectionRef : undefined}
|
||||
>
|
||||
{(!message.failed || Boolean(displayedMessage.text) || regenerating) && (
|
||||
<ChatMessageRow
|
||||
message={displayedMessage}
|
||||
@@ -1307,10 +1462,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
<RectificationCandidateCards
|
||||
result={candidateResult}
|
||||
acceptingCandidateId={acceptingCandidateId}
|
||||
readonly={readonly}
|
||||
readonly={readonly || busy || regeneratingMessageKey !== null}
|
||||
onAccept={(candidateId) => void acceptCandidate(candidateId)}
|
||||
/>
|
||||
)}
|
||||
{showReadonlyRange && message.renderKey === latestSettledAssistant?.renderKey && candidateResult?.credibleRange && (
|
||||
<RectificationReadonlyRange range={candidateResult.credibleRange} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -1319,13 +1477,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
已确认校正时间:{savedTime}
|
||||
</p>
|
||||
)}
|
||||
{savedTime && savedStatus === "accepted" && onStartConsultation && (
|
||||
<div className="rectification-consult-handoff">
|
||||
<Button type="button" onClick={onStartConsultation}>
|
||||
用这个时间看盘
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{error && <p className="error-message" role="alert">{error}</p>}
|
||||
{readonly && (
|
||||
<div className="rectification-terminal-actions">
|
||||
@@ -1340,6 +1491,22 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
{!conversationAnchor.anchored && (
|
||||
<JumpToLatestButton onClick={conversationAnchor.anchorToLatest} />
|
||||
)}
|
||||
{savedTime && savedStatus === "accepted" && (
|
||||
<div className="rectification-adopt-status" role="status">
|
||||
<span>
|
||||
已采用 {savedTime}
|
||||
{adoptedRangeLabel ? ` · 范围 ${adoptedRangeLabel}` : ""}
|
||||
</span>
|
||||
{showSelectionCards && (
|
||||
<span className="rectification-adopt-status__link">改选</span>
|
||||
)}
|
||||
{onStartConsultation && (
|
||||
<Button type="button" variant="outline" onClick={onStartConsultation}>
|
||||
用这个时间看盘
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(showMissingQuestion || showUnavailableQuestion || showQuestionLoadFailed) && (
|
||||
<p className="rectification-composer-status" role="status">
|
||||
{showQuestionLoadFailed
|
||||
@@ -1378,6 +1545,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}}
|
||||
onStop={stopRun}
|
||||
/>
|
||||
{showCollectStop && (
|
||||
<button
|
||||
type="button"
|
||||
className="rectification-collect-stop"
|
||||
disabled={!canSend}
|
||||
onClick={submitCollectStop}
|
||||
>
|
||||
{CHOICE_STOP_LABEL}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="composer-footer">
|
||||
<ModelSelector
|
||||
|
||||
Reference in New Issue
Block a user