()
| 54 | } |
| 55 | |
| 56 | export default function Feedback() { |
| 57 | const { recent } = useLiveData(); |
| 58 | const requests = useMemo( |
| 59 | () => |
| 60 | recent.filter( |
| 61 | (r): r is RecentRequest & { state: typeof r.state } => |
| 62 | Boolean(r.tier) && r.state === "completed", |
| 63 | ) as unknown as RecentRequest[], |
| 64 | [recent], |
| 65 | ); |
| 66 | const [submitted, setSubmitted] = useState<Record<string, FeedbackResult>>({}); |
| 67 | const [busy, setBusy] = useState<string | null>(null); |
| 68 | |
| 69 | useEffect(() => { |
| 70 | setSubmitted((prev) => { |
| 71 | const next = { ...prev }; |
| 72 | for (const request of requests) { |
| 73 | const persisted = storedFeedback(request); |
| 74 | if (persisted) next[request.request_id] = persisted; |
| 75 | } |
| 76 | return next; |
| 77 | }); |
| 78 | }, [requests]); |
| 79 | |
| 80 | async function handle(requestId: string, signal: "ok" | "weak" | "strong") { |
| 81 | setBusy(requestId); |
| 82 | const result = await submitFeedback(requestId, signal); |
| 83 | if (result && result.action !== "expired") { |
| 84 | setSubmitted((prev) => ({ ...prev, [requestId]: result })); |
| 85 | } |
| 86 | setBusy(null); |
| 87 | } |
| 88 | |
| 89 | const visibleRequests = requests.filter((r) => r.feedback_pending || Boolean(submitted[r.request_id] ?? storedFeedback(r))); |
| 90 | const pendingCount = visibleRequests.filter((r) => r.feedback_pending && !(submitted[r.request_id] ?? storedFeedback(r))).length; |
| 91 | |
| 92 | return ( |
| 93 | <div className="space-y-6 animate-fadeIn"> |
| 94 | <div> |
| 95 | <div className="flex items-baseline gap-3 mb-1"> |
| 96 | <h1 className="font-display text-[36px] text-n-display tracking-tight">FEEDBACK</h1> |
| 97 | {pendingCount > 0 && ( |
| 98 | <span className="rounded-pill border border-n-warning px-2 py-0.5 font-mono text-[11px] uppercase tracking-wider text-n-warning"> |
| 99 | {pendingCount} AWAITING |
| 100 | </span> |
| 101 | )} |
| 102 | </div> |
| 103 | <p className="text-[13px] text-n-secondary"> |
| 104 | Rate routing decisions to improve the classifier. All training happens locally. |
| 105 | </p> |
| 106 | </div> |
| 107 | |
| 108 | <div className="rounded-card border border-n-border bg-n-surface overflow-x-auto"> |
| 109 | <table className="w-full"> |
| 110 | <thead> |
| 111 | <tr className="border-b border-n-border"> |
| 112 | <th className="label px-6 py-4 text-left w-24">TIME</th> |
| 113 | <th className="label px-6 py-4 text-left">REQUEST</th> |
nothing calls this directly
no test coverage detected