Files
Jyotisha/frontend/src/components/rectification-choice-card.tsx
T
jesse-uxandGrok (agent loop) d2296c2654 把前后端共用的请求契约和小工具收成一份,避免再各抄一份。
报告创建请求、校正入口和同名类型改从 contracts 推导;同义的对象判断与 sha256 改走共享函数。调用处用 import 别名保留原名,使 diff 低于 1500 行。行为、哈希和状态字符串不变。

Co-Authored-By: Grok (agent loop)
2026-10-15 22:22:44 +08:00

96 lines
3.3 KiB
TypeScript

"use client";
import { useState } from "react";
import { InlineSpinner } from "@/components/inline-spinner";
import {
CHOICE_STOP_LABEL,
type RectificationChoiceCard as ChoiceCard,
} from "@/lib/rectification-agentic/v9/choice-card";
import type { ChoiceKey } 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>
);
}