feat(rectification): remove the rectification regenerate button and endpoint (BUG-1056)
T2 of TASK-rectification-grounding-20260927 (product decision P1). The rectification 「重试(重新生成)」 was a blind agent.generate rewrite that persisted unchecked text (invented range / fit rate reproduced). Removed: the regenerate route, regenerate-turn.ts, the regeneration agent and its read-only tool set, the client regenerate action/state and the regenerating/canRegenerate props. ChatMessageActions renders the regenerate button only when onRegenerate is passed; ordinary consultation is unchanged. The DB function regenerate_agentic_rectification_turn is kept (AGENTS §7.6, retire in a later round). DESIGN.md / VOICE.md updated; source-contract tests follow with 原值/新值/原因 notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5a8084494b
commit
84eb23152a
@@ -21,10 +21,11 @@ export function ChatMessageActions({
|
||||
}: Readonly<{
|
||||
feedback?: ChatMessageFeedback;
|
||||
copied?: boolean;
|
||||
canRegenerate: boolean;
|
||||
/** Without `onRegenerate` the regenerate button is not rendered (rectification, BUG-1056). */
|
||||
canRegenerate?: boolean;
|
||||
onFeedback: (value: ChatMessageFeedback) => void;
|
||||
onCopy: () => void;
|
||||
onRegenerate: () => void;
|
||||
onRegenerate?: () => void;
|
||||
}>) {
|
||||
return (
|
||||
<div className="message-actions" aria-label="Agent 回答操作">
|
||||
@@ -51,15 +52,17 @@ export function ChatMessageActions({
|
||||
<button aria-label="复制回答" title="复制" type="button" onClick={onCopy}>
|
||||
{copied ? <Check aria-hidden="true" /> : <Copy aria-hidden="true" />}
|
||||
</button>
|
||||
<button
|
||||
aria-label="重新生成回答"
|
||||
disabled={!canRegenerate}
|
||||
title={canRegenerate ? "重新生成" : "只能重新生成最近一条回答"}
|
||||
type="button"
|
||||
onClick={onRegenerate}
|
||||
>
|
||||
<RotateCcw aria-hidden="true" />
|
||||
</button>
|
||||
{onRegenerate && (
|
||||
<button
|
||||
aria-label="重新生成回答"
|
||||
disabled={!canRegenerate}
|
||||
title={canRegenerate ? "重新生成" : "只能重新生成最近一条回答"}
|
||||
type="button"
|
||||
onClick={onRegenerate}
|
||||
>
|
||||
<RotateCcw aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,10 +58,7 @@ import {
|
||||
type RectificationStructuredChoiceOverride,
|
||||
} from "@/lib/rectification-chat-choice-run";
|
||||
import { runRectificationCandidateAccept } from "@/lib/rectification-chat-accept-run";
|
||||
import {
|
||||
copyRectificationMessage,
|
||||
regenerateRectificationMessage,
|
||||
} from "@/lib/rectification-chat-message-actions";
|
||||
import { copyRectificationMessage } from "@/lib/rectification-chat-message-actions";
|
||||
import {
|
||||
refetchRectificationQuestion,
|
||||
repairRectificationQuestion,
|
||||
@@ -177,13 +174,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const [acceptingCandidateId, setAcceptingCandidateId] = useState<string | null>(null);
|
||||
const [feedback, setFeedback] = useState<Record<string, "up" | "down" | undefined>>({});
|
||||
const [copiedMessageKey, setCopiedMessageKey] = useState<string | null>(null);
|
||||
const [regeneratingMessageKey, setRegeneratingMessageKey] = useState<string | null>(null);
|
||||
const [choiceNonce, setChoiceNonce] = useState(0);
|
||||
const messageActionsRef = useRef<RectificationMessageActions>({
|
||||
submitChoice() {},
|
||||
submitStop() {},
|
||||
copyMessage() {},
|
||||
regenerateMessage() {},
|
||||
onFeedback() {},
|
||||
});
|
||||
const conversation = useRef<HTMLElement>(null);
|
||||
@@ -390,23 +385,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
await copyRectificationMessage({ setCopiedMessageKey }, message);
|
||||
}
|
||||
|
||||
async function regenerateMessage(message: RenderMessage) {
|
||||
await regenerateRectificationMessage({
|
||||
caseId,
|
||||
sessionId,
|
||||
busy,
|
||||
readonly,
|
||||
regeneratingMessageKey,
|
||||
setError,
|
||||
setMessages,
|
||||
setRegeneratingMessageKey,
|
||||
}, message);
|
||||
}
|
||||
|
||||
async function submit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
if (readonly) return;
|
||||
if (busy || regeneratingMessageKey) {
|
||||
if (busy) {
|
||||
if (queued.enqueue(draft)) setDraft("");
|
||||
return;
|
||||
}
|
||||
@@ -416,7 +398,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}
|
||||
|
||||
const {
|
||||
latestRegeneratableKey,
|
||||
latestSettledAssistant,
|
||||
showLiveChoiceCard,
|
||||
timelineView,
|
||||
@@ -436,7 +417,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
messages,
|
||||
busy,
|
||||
readonly,
|
||||
regeneratingMessageKey,
|
||||
shouldStartOpening,
|
||||
openingRequested,
|
||||
acceptingCandidateId,
|
||||
@@ -529,7 +509,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
submitChoice,
|
||||
submitStop,
|
||||
copyMessage,
|
||||
regenerateMessage,
|
||||
onFeedback(renderKey, requested) {
|
||||
setFeedback((current) => ({
|
||||
...current,
|
||||
@@ -564,7 +543,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
ref={conversation}
|
||||
className="conversation is-rectification"
|
||||
aria-label="生时校正对话"
|
||||
aria-busy={busy || regeneratingMessageKey !== null}
|
||||
aria-busy={busy}
|
||||
>
|
||||
<div className="message-list">
|
||||
{resultNotice && <p role="status" className="rectification-pending-note">{resultNotice}</p>}
|
||||
@@ -585,11 +564,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
{messages.map((message) => {
|
||||
// D3: the same message with the persisted question placed on it.
|
||||
const entryMessage = placedByKey?.get(message.renderKey) ?? message;
|
||||
const regenerating = regeneratingMessageKey === message.renderKey;
|
||||
const canRegenerate = message.renderKey === latestRegeneratableKey
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null;
|
||||
return (
|
||||
<div
|
||||
key={message.renderKey}
|
||||
@@ -599,10 +573,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
message={entryMessage}
|
||||
busy={busy}
|
||||
readonly={readonly}
|
||||
regenerating={regenerating}
|
||||
canRegenerate={canRegenerate}
|
||||
currentQuestionFocusId={currentQuestion?.focus_id ?? null}
|
||||
interactive={!busy && !readonly && regeneratingMessageKey === null}
|
||||
interactive={!busy && !readonly}
|
||||
liveChoiceCard={choiceCard}
|
||||
choiceNonce={choiceNonce}
|
||||
savedTime={savedTime}
|
||||
@@ -615,7 +587,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
<RectificationRangeDelivery
|
||||
result={candidateResult}
|
||||
acceptingCandidateId={acceptingCandidateId}
|
||||
readonly={readonly || regeneratingMessageKey !== null || (busy && !acceptingCandidateId)}
|
||||
readonly={readonly || (busy && !acceptingCandidateId)}
|
||||
onAccept={(candidateId) => void acceptCandidate(candidateId)}
|
||||
adoptLocked={liveInterviewBlockingAdopt}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/components/chat-message-actions";
|
||||
import { ChatMessageRow } from "@/components/chat-message-row";
|
||||
import { RectificationChoiceCard } from "@/components/rectification-choice-card";
|
||||
import { emptyActivityTrace, type AgentActivityTraceItem } from "@/lib/agent-activity-trace";
|
||||
import type { AgentActivityTraceItem } from "@/lib/agent-activity-trace";
|
||||
import type { ChatMessageView } from "@/lib/chat-message-view";
|
||||
import {
|
||||
noteSettledRowRender,
|
||||
@@ -48,7 +48,6 @@ export type RectificationMessageActions = Readonly<{
|
||||
submitChoice: (key: ChoiceKey) => void;
|
||||
submitStop: () => void;
|
||||
copyMessage: (message: RenderMessage) => void;
|
||||
regenerateMessage: (message: RenderMessage) => void;
|
||||
onFeedback: (renderKey: string, requested: ChatMessageFeedback) => void;
|
||||
}>;
|
||||
|
||||
@@ -56,8 +55,6 @@ export type RectificationMessageEntryProps = Readonly<{
|
||||
message: RenderMessage;
|
||||
busy: boolean;
|
||||
readonly: boolean;
|
||||
regenerating: boolean;
|
||||
canRegenerate: boolean;
|
||||
currentQuestionFocusId: string | null;
|
||||
interactive: boolean;
|
||||
liveChoiceCard: ChoiceCardModel | null;
|
||||
@@ -102,8 +99,6 @@ export function choiceCardFromQuestion(
|
||||
function RectificationMessageEntryView({
|
||||
message,
|
||||
busy,
|
||||
regenerating,
|
||||
canRegenerate,
|
||||
currentQuestionFocusId,
|
||||
interactive,
|
||||
liveChoiceCard,
|
||||
@@ -117,30 +112,19 @@ function RectificationMessageEntryView({
|
||||
&& message.state === "settled"
|
||||
&& !message.failed
|
||||
&& Boolean(message.text);
|
||||
const displayedMessage: RenderMessage = regenerating
|
||||
? {
|
||||
...message,
|
||||
text: "",
|
||||
thinkingText: undefined,
|
||||
state: "thinking" as const,
|
||||
activity: undefined,
|
||||
activityTrace: emptyActivityTrace(),
|
||||
completedReceipt: undefined,
|
||||
timeline: [],
|
||||
}
|
||||
: {
|
||||
...message,
|
||||
timeline: rectificationTimelineRows({
|
||||
trace: message.activityTrace,
|
||||
receipt: message.completedReceipt,
|
||||
activity: message.activity,
|
||||
settled: message.state === "settled",
|
||||
}),
|
||||
};
|
||||
const displayedMessage: RenderMessage = {
|
||||
...message,
|
||||
timeline: rectificationTimelineRows({
|
||||
trace: message.activityTrace,
|
||||
receipt: message.completedReceipt,
|
||||
activity: message.activity,
|
||||
settled: message.state === "settled",
|
||||
}),
|
||||
};
|
||||
const vargaSentence = !message.failed
|
||||
? vargaSentenceFromMethods(message.completedReceipt?.methods)
|
||||
: null;
|
||||
const question = regenerating ? undefined : message.question;
|
||||
const question = message.question;
|
||||
const liveQuestion = Boolean(
|
||||
question
|
||||
&& currentQuestionFocusId === question.focus_id
|
||||
@@ -193,7 +177,7 @@ function RectificationMessageEntryView({
|
||||
|
||||
return (
|
||||
<>
|
||||
{(!message.failed || Boolean(displayedMessage.text) || regenerating) && (
|
||||
{(!message.failed || Boolean(displayedMessage.text)) && (
|
||||
<ChatMessageRow
|
||||
message={displayedMessage}
|
||||
showActivity={displayedMessage.state !== "settled"}
|
||||
@@ -202,14 +186,13 @@ function RectificationMessageEntryView({
|
||||
stoppedNotice={message.stopped ? RECTIFICATION_STOPPED_NOTICE : undefined}
|
||||
/>
|
||||
)}
|
||||
{showActions && !regenerating && (
|
||||
{/* BUG-1056: copy and feedback only; the rectification reply has no rewrite action. */}
|
||||
{showActions && (
|
||||
<ChatMessageActions
|
||||
feedback={feedback}
|
||||
copied={copied}
|
||||
canRegenerate={canRegenerate}
|
||||
onFeedback={(requested) => actionsRef.current.onFeedback(message.renderKey, requested)}
|
||||
onCopy={() => void actionsRef.current.copyMessage(message)}
|
||||
onRegenerate={() => void actionsRef.current.regenerateMessage(message)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -219,24 +202,20 @@ function RectificationMessageEntryView({
|
||||
export const RectificationMessageEntry = memo(function RectificationMessageEntry(
|
||||
props: RectificationMessageEntryProps,
|
||||
) {
|
||||
if (props.regenerating || props.message.state !== "settled") noteStreamingRowRender();
|
||||
if (props.message.state !== "settled") noteStreamingRowRender();
|
||||
else noteSettledRowRender();
|
||||
return <RectificationMessageEntryView {...props} />;
|
||||
});
|
||||
|
||||
export function UnsplitRectificationMessageList({
|
||||
messages,
|
||||
latestRegeneratableKey,
|
||||
copiedMessageKey,
|
||||
feedbackByKey,
|
||||
regeneratingMessageKey,
|
||||
...shared
|
||||
}: Omit<RectificationMessageEntryProps, "message" | "regenerating" | "canRegenerate" | "copied" | "feedback"> & {
|
||||
}: Omit<RectificationMessageEntryProps, "message" | "copied" | "feedback"> & {
|
||||
messages: readonly RenderMessage[];
|
||||
latestRegeneratableKey?: string;
|
||||
copiedMessageKey: string | null;
|
||||
feedbackByKey: Readonly<Record<string, ChatMessageFeedback | undefined>>;
|
||||
regeneratingMessageKey: string | null;
|
||||
}) {
|
||||
noteUnsplitListRender();
|
||||
return (
|
||||
@@ -249,8 +228,6 @@ export function UnsplitRectificationMessageList({
|
||||
key={message.renderKey}
|
||||
{...shared}
|
||||
message={message}
|
||||
regenerating={regeneratingMessageKey === message.renderKey}
|
||||
canRegenerate={message.renderKey === latestRegeneratableKey && shared.interactive}
|
||||
copied={copiedMessageKey === message.renderKey}
|
||||
feedback={feedbackByKey[message.renderKey]}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user