Hovering A/B/C/D rewrote a leading/lagging time line and made the card flicker; the step-state sentence above the composer was instructional filler, not something the user needed to answer. Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { InlineSpinner } from "@/components/inline-spinner";
|
|
import {
|
|
CHOICE_STOP_LABEL,
|
|
type ChoiceKey,
|
|
type RectificationChoiceCard as ChoiceCard,
|
|
} from "@/lib/rectification-agentic/v9/choice-card";
|
|
|
|
type RectificationChoiceCardProps = Readonly<{
|
|
card: ChoiceCard;
|
|
pending: boolean;
|
|
disabled: boolean;
|
|
selectedKey?: ChoiceKey | "stop" | "skip_probe" | "";
|
|
onSelect: (key: ChoiceKey) => void;
|
|
onStop: () => void;
|
|
variant?: "card" | "embedded";
|
|
hidePrompt?: boolean;
|
|
}>;
|
|
|
|
export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
|
const [localSelected, setLocalSelected] = useState<ChoiceKey | "stop" | "skip_probe" | "">("");
|
|
const selectedKey = props.selectedKey || localSelected;
|
|
const answered = Boolean(selectedKey);
|
|
|
|
function select(key: ChoiceKey) {
|
|
if (props.pending || props.disabled || selectedKey) return;
|
|
setLocalSelected(key);
|
|
props.onSelect(key);
|
|
}
|
|
|
|
function stop() {
|
|
if (props.pending || props.disabled || selectedKey) return;
|
|
setLocalSelected("stop");
|
|
props.onStop();
|
|
}
|
|
|
|
const embedded = props.variant === "embedded";
|
|
const hidePrompt = embedded || props.hidePrompt === true;
|
|
|
|
return (
|
|
<section
|
|
className={`rectification-choice-card${embedded ? " is-embedded" : ""}${props.pending ? " is-pending" : ""}${answered ? " is-answered" : ""}`}
|
|
aria-label={props.card.scoring ? "校正判断" : "盘外核对"}
|
|
>
|
|
{props.pending && answered ? (
|
|
// The tap is being recorded: the same live-row shape as a timeline step,
|
|
// so the card itself says something is happening.
|
|
<p className="rectification-choice-card__pending" role="status">
|
|
<InlineSpinner size={12} />
|
|
<span className="agent-activity-status__text">正在记录…</span>
|
|
</p>
|
|
) : null}
|
|
<fieldset
|
|
className="birth-time-choice-question"
|
|
disabled={props.pending || props.disabled || answered}
|
|
data-answered={answered ? "true" : "false"}
|
|
>
|
|
{hidePrompt
|
|
? <legend className="sr-only">{props.card.prompt}</legend>
|
|
: <legend>{props.card.prompt}</legend>}
|
|
{!hidePrompt && props.card.why_user ? (
|
|
<details className="rectification-choice-why-user">
|
|
<summary>为什么问这题</summary>
|
|
<p>{props.card.why_user}</p>
|
|
</details>
|
|
) : null}
|
|
<div className="birth-time-primary-choices">
|
|
{props.card.options.map((option) => (
|
|
<button
|
|
key={option.key}
|
|
type="button"
|
|
className="birth-time-choice-option is-primary"
|
|
data-selected={selectedKey === option.key ? "true" : "false"}
|
|
onClick={() => select(option.key)}
|
|
>
|
|
<strong>{option.key}.</strong> {option.label}
|
|
</button>
|
|
))}
|
|
{props.card.stop_label !== CHOICE_STOP_LABEL ? (
|
|
<button
|
|
type="button"
|
|
className="birth-time-choice-option is-primary"
|
|
data-selected={selectedKey === "stop" || selectedKey === "skip_probe" ? "true" : "false"}
|
|
onClick={stop}
|
|
>
|
|
{props.card.stop_label}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</fieldset>
|
|
</section>
|
|
);
|
|
}
|