import { useEffect, useRef, useState } from "react"; import { CheckCircle2, Clock, Eye, EyeOff, Flag, Inbox, Search, Undo2, XCircle } from "lucide-react"; import { api, type Report, type ReportStatus } from "../lib/api.ts"; import { Button, ConfirmDialog, Field, Input, Spinner } from "./ui.tsx"; import { useT, type StringKey } from "../lib/i18n.ts"; import { cn } from "../lib/cn.ts"; const STATUS_STYLE: Record = { open: { cls: "bg-amber-500/10 text-amber-700 dark:text-amber-400" }, reviewing: { cls: "bg-sky-500/10 text-sky-700 dark:text-sky-400" }, resolved: { cls: "bg-accent-500/10 text-accent-700 dark:text-accent-400" }, dismissed: { cls: "bg-red-500/10 text-red-600 dark:text-red-400" }, }; type Filter = "all" | ReportStatus; const FILTERS: Filter[] = ["all", "open", "reviewing", "resolved", "dismissed"]; /** Admin triage for user-flagged AI responses: review / resolve / dismiss. */ export function ReportsPanel() { const [reports, setReports] = useState([]); const [filter, setFilter] = useState("open"); const [selectedId, setSelectedId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const { t } = useT(); const listBusy = useRef(false); async function refresh(select?: string) { if (listBusy.current) return; listBusy.current = true; try { const res = await api.adminReportList(); setReports(res.reports); if (select) setSelectedId(select); } catch (err) { setError(err instanceof Error ? err.message : t("admin.loadFailed")); } finally { listBusy.current = false; setLoading(false); } } useEffect(() => { refresh(); // Live list: new reports pop without manual refresh. const id = setInterval(() => { if (!document.hidden) refresh(); }, 15000); return () => clearInterval(id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const visible = reports.filter((r) => filter === "all" || r.status === filter); const openCount = reports.filter((r) => r.status === "open").length; return (
{FILTERS.map((f) => { const n = f === "all" ? reports.length : reports.filter((r) => r.status === f).length; return ( ); })}
{error &&

{error}

} {loading &&

{t("common.loading")}

} {!loading && visible.length === 0 && (

{openCount > 0 ? t("reports.emptyFiltered") : t("reports.empty")}

)}
    {visible.map((r) => (
  • {selectedId === r.id && refresh(r.id)} />}
  • ))}
); } function ReportDetail({ report, onChanged }: { report: Report; onChanged: () => void }) { const [note, setNote] = useState(report.admin_note); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [confirmBan, setConfirmBan] = useState(null); const { t } = useT(); const closed = report.status === "resolved" || report.status === "dismissed"; const isBanned = report.reporter_shadowbanned === 1; useEffect(() => { setNote(report.admin_note); }, [report.id, report.admin_note]); async function setStatus(status: ReportStatus) { setBusy(true); setError(""); try { await api.adminReportPatch(report.id, { status, adminNote: note.trim() }); onChanged(); } catch (err) { setError(err instanceof Error ? err.message : t("reports.updateFailed")); } finally { setBusy(false); } } async function toggleBan(shadowbanned: boolean) { setBusy(true); setError(""); try { await api.adminShadowban(report.reporter_id, shadowbanned); onChanged(); } catch (err) { setError(err instanceof Error ? err.message : t("reports.updateFailed")); } finally { setBusy(false); } } return (
{report.prompt && (

{t("reports.prompt")}

{report.prompt}
)}

{t("reports.response")}

{report.content || {t("reports.noContent")}}
{t("reports.model")}: {report.model || "—"} {t("reports.reporter")}: @{report.reporter_name} {report.conversation_id && ( {t("reports.conversation")}: {report.conversation_id.slice(0, 8)}… )} {report.details && ( {t("reports.detailsGiven")}: {report.details} )}
{isBanned && {t("reports.unreliable")}}
setConfirmBan(null)} onConfirm={() => { const v = confirmBan; setConfirmBan(null); if (v !== null) void toggleBan(v); }} title={t(confirmBan === false ? "reports.unshadowbanTitle" : "reports.shadowbanTitle")} message={t(confirmBan === false ? "reports.unshadowbanMsg" : "reports.shadowbanMsg", { user: report.reporter_name })} confirmLabel={t(confirmBan === false ? "reports.unshadowban" : "reports.shadowban")} /> {!closed ? (
setNote(e.target.value)} placeholder={t("reports.notePh")} maxLength={1000} />
{report.status === "open" && ( )}
) : (

{t("reports.closedNote", { status: t(`reports.status.${report.status}` as StringKey).toLowerCase() })} {report.admin_note && <> {t("reports.noteWord")}: {report.admin_note}}

)} {error &&

{error}

}
); }