"use client"; import { CheckIcon, RotateCcwIcon } from "lucide-react"; import { AnimatePresence, LayoutGroup, motion, useReducedMotion } from "motion/react"; import { type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { cn } from "@/lib/utils"; export interface TriageSegment { text: string; risk?: "high" | "medium"; note?: string; } export interface TriageItem { id: string; title: string; agent: string; kind: string; risk: number; body: TriageSegment[]; } export type TriageAction = "approve" | "edit" | "reject" | "skip"; export interface TriageDeskProps { items: TriageItem[]; onResolve?: (item: TriageItem, action: Exclude, text: string) => void; title?: string; className?: string; } const stamps: Record = { approve: { label: "Approved", tone: "border-success text-success-foreground" }, edit: { label: "Edited", tone: "border-info text-info-foreground" }, reject: { label: "Rejected", tone: "border-destructive text-destructive-foreground" }, skip: { label: "Later", tone: "border-muted-foreground text-muted-foreground" }, }; const exits: Record = { approve: { x: 28, y: -4, rotate: 2 }, edit: { x: 28, y: -4, rotate: 2 }, reject: { x: -28, y: -4, rotate: -2 }, skip: { x: 0, y: -16, rotate: 0 }, }; const press = "transition-[scale,box-shadow,background-color] duration-150 ease-out active:scale-[0.97]"; const easeOut = [0.23, 1, 0.32, 1] as const; function riskTone(risk: number) { if (risk >= 70) return "bg-destructive/12 text-destructive-foreground"; if (risk >= 40) return "bg-warning/14 text-warning-foreground"; return "bg-success/12 text-success-foreground"; } const plain = (item: TriageItem) => item.body.map((s) => s.text).join(""); export function TriageDesk({ items, onResolve, title = "Needs review", className }: TriageDeskProps) { const reduce = useReducedMotion(); const uid = useId(); const sorted = useMemo(() => [...items].sort((a, b) => b.risk - a.risk), [items]); const [queue, setQueue] = useState(sorted); const [tally, setTally] = useState({ approved: 0, rejected: 0 }); const [stamp, setStamp] = useState(null); const [leaving, setLeaving] = useState("skip"); const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(""); const rootRef = useRef(null); const textRef = useRef(null); const timer = useRef>(undefined); useEffect(() => () => clearTimeout(timer.current), []); const current = queue[0]; const act = (action: TriageAction) => { if (!current || stamp) return; if (action === "edit" && !editing) { setDraft(plain(current)); setEditing(true); requestAnimationFrame(() => textRef.current?.focus()); return; } const final = editing ? "edit" : action; setStamp(final); setLeaving(final); timer.current = setTimeout( () => { if (final !== "skip") onResolve?.(current, final, final === "edit" ? draft : plain(current)); setTally((t) => ({ approved: t.approved + (final === "approve" || final === "edit" ? 1 : 0), rejected: t.rejected + (final === "reject" ? 1 : 0), })); setQueue((q) => (final === "skip" && q.length > 1 ? [...q.slice(1), q[0]] : q.slice(1))); setStamp(null); setEditing(false); rootRef.current?.focus(); }, reduce ? 150 : 420, ); }; const onKeyDown = (e: KeyboardEvent) => { if (e.metaKey || e.ctrlKey || e.altKey) { if (editing && e.key === "Enter") { e.preventDefault(); act("approve"); } return; } if (editing) { if (e.key === "Escape") { e.preventDefault(); setEditing(false); rootRef.current?.focus(); } return; } const map: Record = { a: "approve", e: "edit", r: "reject", s: "skip" }; const action = map[e.key.toLowerCase()]; if (action) { e.preventDefault(); act(action); } }; const reload = () => { setQueue(sorted); setTally({ approved: 0, rejected: 0 }); requestAnimationFrame(() => rootRef.current?.focus()); }; const spring = reduce ? { duration: 0 } : { type: "spring" as const, stiffness: 380, damping: 32 }; let n = 0; return (

{title}

{queue.length} left · {tally.approved} approved · {tally.rejected} rejected

    {queue.map((item, i) => ( {item.risk} {i === 0 ? ( {item.kind} ) : ( {item.kind} )} ))}
{current ? (

{current.kind} · by {current.agent}

{current.title}

risk {current.risk}
{editing ? (