fe(fe): surface moderation explainability in live feed

Show confidence bar, flag chips, status dot, and error text per action
in the Live Stream Audit Log; drop stale media comment on SkeletonHero.
This commit is contained in:
asepharyana
2026-09-23 23:46:22 +07:00
parent b72423c64d
commit b12eb0a038
2 changed files with 80 additions and 2 deletions
@@ -49,6 +49,15 @@ function severityTone(
}
}
function confidenceTone(confidence?: number | null) {
if (confidence == null) return null;
return confidence >= 0.75
? "bg-signal"
: confidence >= 0.5
? "bg-amber"
: "bg-vermilion";
}
export function LiveModerationFeed({
actions,
}: {
@@ -115,8 +124,27 @@ export function LiveModerationFeed({
className="mod-feed-item hud-card animate-stagger flex items-start gap-3 p-3 transition-all"
style={staggerDelay(i)}
>
<span className="flex size-7 shrink-0 items-center justify-center rounded-[6px] border border-hairline bg-surface-2">
<span
className={`relative flex size-7 shrink-0 items-center justify-center rounded-[6px] border ${
a.status === "failed"
? "border-vermilion/40 bg-vermilion/10"
: a.status === "pending"
? "border-amber/40 bg-amber/10"
: "border-hairline bg-surface-2"
}`}
>
{actionIcon(a.action_type)}
{a.status && (
<span
className={`absolute -right-0.5 -top-0.5 size-2 rounded-full ring-2 ring-surface ${
a.status === "failed"
? "bg-vermilion"
: a.status === "pending"
? "bg-amber"
: "bg-signal"
}`}
/>
)}
</span>
<div className="min-w-0 flex-1">
@@ -151,6 +179,56 @@ export function LiveModerationFeed({
</p>
)}
{(a.confidence != null || a.flags?.length) && (
<div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1">
{a.confidence != null && (
<span
className="flex items-center gap-1.5"
title={`Model confidence ${Math.round(a.confidence * 100)}%`}
>
<span className="font-mono text-[9px] tracking-wider text-ink-faint uppercase">
conf
</span>
<span className="inline-flex h-1 w-14 overflow-hidden rounded-full bg-surface-2">
<span
className={`h-full rounded-full ${confidenceTone(a.confidence) ?? "bg-ink-faint"}`}
style={{
width: `${Math.min(100, Math.round(a.confidence * 100))}%`,
}}
/>
</span>
<span className="font-mono text-[9px] text-ink-faint">
{Math.round(a.confidence * 100)}%
</span>
</span>
)}
{a.flags && a.flags.length > 0 && (
<span className="flex flex-wrap items-center gap-1">
{a.flags.slice(0, 2).map((flag) => (
<Badge
key={flag}
tone={tone ?? "neutral"}
size="sm"
className="uppercase"
>
{flag}
</Badge>
))}
{a.flags.length > 2 && (
<span className="font-mono text-[9px] text-ink-faint">
+{a.flags.length - 2}
</span>
)}
</span>
)}
{a.error && (
<span className="font-mono text-[9px] text-vermilion/80">
{a.error}
</span>
)}
</div>
)}
<div className="mt-1.5 flex items-center gap-2 font-mono text-[10px] text-ink-faint">
<span>TARGET:</span>
<span className="text-ink-soft">
@@ -75,7 +75,7 @@ export function SkeletonRows({
);
}
/** Hero block placeholder (dashboard / media "now playing"). */
/** Hero block placeholder (dashboard headline). */
export function SkeletonHero({ className }: { className?: string }) {
return (
<GlassPanel glow className={cn("relative overflow-hidden", className)}>