fix(consult): treat pinched answers as failed and restore reply actions

Incomplete Flash generations were billed as completed consultations. Fail
those runs, keep the partial text, and reuse the rectification like/copy/rerun
bar on ordinary chat replies.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-19 19:37:02 +08:00
parent c38f11dbd3
commit 7d667fecbf
17 changed files with 487 additions and 89 deletions
@@ -0,0 +1,65 @@
"use client";
import { Check, Copy, RotateCcw, ThumbsDown, ThumbsUp } from "lucide-react";
export type ChatMessageFeedback = "up" | "down";
export function toggleChatMessageFeedback(
current: ChatMessageFeedback | undefined,
requested: ChatMessageFeedback,
): ChatMessageFeedback | undefined {
return current === requested ? undefined : requested;
}
export function ChatMessageActions({
feedback,
copied = false,
canRegenerate,
onFeedback,
onCopy,
onRegenerate,
}: Readonly<{
feedback?: ChatMessageFeedback;
copied?: boolean;
canRegenerate: boolean;
onFeedback: (value: ChatMessageFeedback) => void;
onCopy: () => void;
onRegenerate: () => void;
}>) {
return (
<div className="message-actions" aria-label="Agent 回答操作">
<button
aria-label="赞"
aria-pressed={feedback === "up"}
className={feedback === "up" ? "is-active" : ""}
title="赞"
type="button"
onClick={() => onFeedback("up")}
>
<ThumbsUp aria-hidden="true" />
</button>
<button
aria-label="踩"
aria-pressed={feedback === "down"}
className={feedback === "down" ? "is-active" : ""}
title="踩"
type="button"
onClick={() => onFeedback("down")}
>
<ThumbsDown aria-hidden="true" />
</button>
<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>
</div>
);
}
@@ -1,6 +1,6 @@
"use client";
import { ArrowUp, Check, Copy, RotateCcw, ThumbsDown, ThumbsUp } from "lucide-react";
import { ArrowUp } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { parseAgentReply } from "@/lib/agent-reply";
import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view";
@@ -23,6 +23,10 @@ import {
} from "@/lib/rectification-agentic/v9/public-receipt";
import type { PublicLanguageModel } from "@/lib/public-models";
import { ChatMessageRow } from "./chat-message-row";
import {
ChatMessageActions,
toggleChatMessageFeedback,
} from "./chat-message-actions";
import { CompletedActivityReceipt } from "./completed-activity-receipt";
import { ModelSelector } from "./model-selector";
import { Button } from "./ui/button";
@@ -125,7 +129,7 @@ export function toggleRectificationFeedback(
current: "up" | "down" | undefined,
requested: "up" | "down",
): "up" | "down" | undefined {
return current === requested ? undefined : requested;
return toggleChatMessageFeedback(current, requested);
}
function activityPhase(tool: PublicRectificationTool): NonNullable<ChatMessageView["activity"]>["phase"] {
@@ -584,48 +588,17 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
/>
)}
{showActions && !regenerating && (
<div className="rectification-message-actions" aria-label="Agent 回答操作">
<button
aria-label="赞"
aria-pressed={feedback[message.renderKey] === "up"}
className={feedback[message.renderKey] === "up" ? "is-active" : ""}
title="赞"
type="button"
onClick={() => setFeedback((current) => ({
...current,
[message.renderKey]: toggleRectificationFeedback(current[message.renderKey], "up"),
}))}
>
<ThumbsUp aria-hidden="true" />
</button>
<button
aria-label="踩"
aria-pressed={feedback[message.renderKey] === "down"}
className={feedback[message.renderKey] === "down" ? "is-active" : ""}
title="踩"
type="button"
onClick={() => setFeedback((current) => ({
...current,
[message.renderKey]: toggleRectificationFeedback(current[message.renderKey], "down"),
}))}
>
<ThumbsDown aria-hidden="true" />
</button>
<button aria-label="复制回答" title="复制" type="button" onClick={() => void copyMessage(message)}>
{copiedMessageKey === message.renderKey
? <Check aria-hidden="true" />
: <Copy aria-hidden="true" />}
</button>
<button
aria-label="重新生成回答"
disabled={!canRegenerate}
title={canRegenerate ? "重新生成" : "只能重新生成最近一条回答"}
type="button"
onClick={() => void regenerateMessage(message)}
>
<RotateCcw aria-hidden="true" />
</button>
</div>
<ChatMessageActions
feedback={feedback[message.renderKey]}
copied={copiedMessageKey === message.renderKey}
canRegenerate={canRegenerate}
onFeedback={(requested) => setFeedback((current) => ({
...current,
[message.renderKey]: toggleRectificationFeedback(current[message.renderKey], requested),
}))}
onCopy={() => void copyMessage(message)}
onRegenerate={() => void regenerateMessage(message)}
/>
)}
</div>
);