Files
Jyotisha/frontend/src/components/reply-down-reason.tsx
T

76 lines
2.9 KiB
TypeScript

"use client";
import { useState } from "react";
import { X } from "lucide-react";
import { REPLY_QUALITY_NOTE_MAX, REPLY_QUALITY_REASON_LABELS, REPLY_QUALITY_REASONS } from "@/lib/reply-quality-labels";
import { notifyError, notifySuccess } from "@/lib/notify";
import { saveReplyQualityReason } from "@/lib/reply-ratings";
const REASONS = REPLY_QUALITY_REASONS.map((key) => [key, REPLY_QUALITY_REASON_LABELS[key]] as const);
/**
* 不满意原因 after a 👎 (compliance round 2026-09-30). Everything here is
* optional: the 👎 and the turn's snapshot are already saved when this opens;
* closing it keeps them. One line says the turn is sent for review. The thanks
* (or a failure) is a toast; the panel closes itself on success (2026-09-30).
*/
export function ReplyDownReason({ feedbackKey, onClose }: Readonly<{ feedbackKey: string; onClose: () => void }>) {
const [picked, setPicked] = useState<ReadonlySet<string>>(() => new Set());
const [note, setNote] = useState("");
const [state, setState] = useState<"idle" | "sending" | "failed">("idle");
async function submit() {
if (state === "sending") return;
setState("sending");
const ok = await saveReplyQualityReason(feedbackKey, REASONS.map(([key]) => key).filter((key) => picked.has(key)), note);
if (ok) {
notifySuccess("已收到,谢谢。");
onClose();
return;
}
setState("failed");
notifyError("没能提交,再试一次。");
}
return (
<div className="reply-down-reason" role="group" aria-label="不满意原因">
<div className="reply-down-reason-head">
<span>哪里不满意?可以不选</span>
<button type="button" className="reply-down-reason-close" aria-label="关闭" onClick={onClose}><X aria-hidden="true" /></button>
</div>
<div className="reply-down-reason-chips">
{REASONS.map(([key, label]) => (
<button
key={key}
type="button"
aria-pressed={picked.has(key)}
onClick={() => setPicked((current) => {
const next = new Set(current);
if (next.has(key)) next.delete(key); else next.add(key);
return next;
})}
>
{label}
</button>
))}
</div>
<input
className="reply-down-reason-note"
type="text"
value={note}
maxLength={REPLY_QUALITY_NOTE_MAX}
placeholder="补充一句(可不填)"
aria-label="补充说明"
onChange={(event) => setNote(event.target.value)}
/>
<div className="reply-down-reason-foot">
<span>会把这一轮对话发给我们排查</span>
<button type="button" className="reply-down-reason-send" disabled={state === "sending"} onClick={() => void submit()}>
{state === "failed" ? "再试一次" : "提交"}
</button>
</div>
</div>
);
}