2026-08-14 10:49:44 +07:00
|
|
|
"use client";
|
|
|
|
|
|
2026-08-26 10:39:00 +07:00
|
|
|
import { AlertTriangle, CheckCircle2, Shield } from "lucide-react";
|
|
|
|
|
import { useEffect, useState } from "react";
|
2026-08-15 17:53:48 +07:00
|
|
|
import { useAmbient } from "@/components/ambient/ambient-context";
|
2026-08-24 16:32:16 +07:00
|
|
|
import { LiveModerationFeed } from "@/components/LiveModerationFeed";
|
2026-08-18 20:45:12 +07:00
|
|
|
import { ModerationHeatmap } from "@/components/ModerationHeatmap";
|
2026-08-26 10:39:00 +07:00
|
|
|
import { GlassPanel } from "@/components/primitives";
|
2026-08-24 16:32:16 +07:00
|
|
|
import {
|
|
|
|
|
ErrorState,
|
|
|
|
|
MetricTile,
|
2026-08-26 03:37:14 +07:00
|
|
|
PageTransition,
|
2026-08-24 16:32:16 +07:00
|
|
|
SectionHeader,
|
|
|
|
|
SkeletonMetricRow,
|
|
|
|
|
SkeletonPanel,
|
|
|
|
|
} from "@/components/shared";
|
2026-08-18 17:43:02 +07:00
|
|
|
import {
|
2026-08-18 20:45:12 +07:00
|
|
|
useHourlyModeration,
|
2026-08-18 17:43:02 +07:00
|
|
|
useModerationActions,
|
|
|
|
|
useModerationStats,
|
|
|
|
|
} from "@/hooks";
|
2026-08-26 03:37:14 +07:00
|
|
|
import { useStaggerReveal } from "@/hooks/use-gsap-animation";
|
|
|
|
|
import { formatNumber } from "@/lib/format";
|
2026-08-15 17:53:48 +07:00
|
|
|
import type {
|
|
|
|
|
ModerationAction,
|
2026-08-26 03:37:14 +07:00
|
|
|
ModerationCoverage,
|
2026-08-15 17:53:48 +07:00
|
|
|
ModerationStats,
|
|
|
|
|
} from "@/lib/types";
|
2026-08-14 10:49:44 +07:00
|
|
|
|
2026-08-15 17:53:48 +07:00
|
|
|
export function ModerationView({
|
2026-08-14 10:49:44 +07:00
|
|
|
initialStats,
|
|
|
|
|
initialActions,
|
2026-08-26 10:39:00 +07:00
|
|
|
initialCoverage: _initialCoverage,
|
2026-08-14 10:49:44 +07:00
|
|
|
}: {
|
|
|
|
|
initialStats?: ModerationStats;
|
|
|
|
|
initialActions?: ModerationAction[];
|
2026-08-26 03:37:14 +07:00
|
|
|
initialCoverage?: ModerationCoverage;
|
2026-08-14 10:49:44 +07:00
|
|
|
}) {
|
2026-08-25 13:46:15 +07:00
|
|
|
const {
|
|
|
|
|
data: stats,
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
2026-08-26 03:37:14 +07:00
|
|
|
mutate,
|
2026-08-25 13:46:15 +07:00
|
|
|
} = useModerationStats(initialStats);
|
2026-08-15 17:53:48 +07:00
|
|
|
const { data: actions } = useModerationActions(
|
2026-08-26 03:37:14 +07:00
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
initialActions,
|
2026-08-15 17:53:48 +07:00
|
|
|
);
|
2026-08-26 03:37:14 +07:00
|
|
|
const { data: hourly } = useHourlyModeration();
|
2026-08-15 17:53:48 +07:00
|
|
|
const ambient = useAmbient();
|
|
|
|
|
|
2026-08-26 03:37:14 +07:00
|
|
|
const [filterMode, setFilterMode] = useState<"all" | "flagged" | "clean">(
|
|
|
|
|
"all",
|
2026-08-25 23:32:21 +07:00
|
|
|
);
|
|
|
|
|
|
2026-08-26 03:37:14 +07:00
|
|
|
const containerRef = useStaggerReveal<HTMLDivElement>(".mod-tile", {
|
|
|
|
|
stagger: 0.035,
|
|
|
|
|
y: 8,
|
|
|
|
|
dependencies: [stats],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const executed = stats?.executed ?? 0;
|
|
|
|
|
const failed = stats?.failed ?? 0;
|
|
|
|
|
const total = stats?.total || 1;
|
|
|
|
|
const failedRatio = failed / total;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (failedRatio > 0.15) ambient.set("vermilion", 0.5, "high failure rate");
|
|
|
|
|
else if (failedRatio > 0.05) ambient.set("amber", 0.35, "moderate issues");
|
|
|
|
|
else ambient.set("signal", 0.25, "moderation nominal");
|
|
|
|
|
}, [failedRatio, ambient]);
|
|
|
|
|
|
2026-08-25 13:46:15 +07:00
|
|
|
if (error && !stats)
|
2026-08-26 03:37:14 +07:00
|
|
|
return <ErrorState error={error} onRetry={() => void mutate()} />;
|
2026-08-18 09:21:12 +07:00
|
|
|
if (!stats && isLoading)
|
|
|
|
|
return (
|
2026-08-26 03:37:14 +07:00
|
|
|
<div className="space-y-4">
|
|
|
|
|
<SkeletonMetricRow cols={3} />
|
|
|
|
|
<SkeletonPanel rows={6} />
|
2026-08-24 16:32:16 +07:00
|
|
|
</div>
|
2026-08-18 09:21:12 +07:00
|
|
|
);
|
2026-08-26 03:37:14 +07:00
|
|
|
if (!stats) return null;
|
2026-08-15 17:53:48 +07:00
|
|
|
|
2026-08-14 10:49:44 +07:00
|
|
|
return (
|
2026-08-26 03:37:14 +07:00
|
|
|
<PageTransition>
|
|
|
|
|
<div ref={containerRef} className="space-y-4">
|
2026-08-26 14:57:08 +07:00
|
|
|
{/* Tactical HUD Header Bar */}
|
|
|
|
|
<div className="mod-tile flex flex-wrap items-center justify-between gap-3 border-b border-hairline pb-3">
|
2026-08-26 03:37:14 +07:00
|
|
|
<div className="flex items-center gap-2.5">
|
2026-08-25 23:32:21 +07:00
|
|
|
<span
|
2026-08-26 03:37:14 +07:00
|
|
|
className={`h-2 w-2 rounded-full ${
|
|
|
|
|
failedRatio > 0.1
|
2026-08-26 14:57:08 +07:00
|
|
|
? "bg-vermilion shadow-[0_0_8px_var(--color-vermilion-glow)]"
|
|
|
|
|
: "bg-signal shadow-[0_0_8px_var(--color-signal-glow)]"
|
2026-08-25 23:32:21 +07:00
|
|
|
}`}
|
|
|
|
|
/>
|
2026-08-26 14:57:08 +07:00
|
|
|
<h1 className="font-mono text-xs font-semibold tracking-wide text-ink uppercase">
|
2026-08-26 03:37:14 +07:00
|
|
|
Moderation Intelligence Console
|
|
|
|
|
</h1>
|
|
|
|
|
</div>
|
2026-08-26 14:57:08 +07:00
|
|
|
<div className="flex items-center gap-1.5 rounded-[6px] border border-hairline bg-surface-2 p-0.5">
|
2026-08-26 03:37:14 +07:00
|
|
|
{(["all", "flagged", "clean"] as const).map((mode) => (
|
|
|
|
|
<button
|
|
|
|
|
key={mode}
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setFilterMode(mode)}
|
|
|
|
|
className={`rounded-[4px] px-2.5 py-1 font-mono text-[10px] font-medium transition-all ${
|
|
|
|
|
filterMode === mode
|
2026-08-26 14:57:08 +07:00
|
|
|
? "bg-surface text-ink border border-hairline-focus shadow-xs"
|
|
|
|
|
: "text-ink-muted hover:text-ink"
|
2026-08-26 03:37:14 +07:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{mode.toUpperCase()}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
2026-08-25 23:32:21 +07:00
|
|
|
</div>
|
2026-08-24 16:32:16 +07:00
|
|
|
</div>
|
|
|
|
|
|
2026-08-26 03:37:14 +07:00
|
|
|
{/* Primary Metric Grid */}
|
|
|
|
|
<div className="mod-tile grid gap-3 sm:grid-cols-3">
|
|
|
|
|
<MetricTile
|
|
|
|
|
label="Total Audited"
|
|
|
|
|
value={formatNumber(stats.total)}
|
|
|
|
|
hint="Continuous LLM inspection"
|
|
|
|
|
icon={<Shield className="size-4" />}
|
|
|
|
|
/>
|
|
|
|
|
<MetricTile
|
|
|
|
|
label="Clean Executed"
|
|
|
|
|
value={formatNumber(stats.executed)}
|
|
|
|
|
hint={`${Math.round((executed / total) * 100)}% execution rate`}
|
|
|
|
|
tone="signal"
|
|
|
|
|
icon={<CheckCircle2 className="size-4" />}
|
|
|
|
|
/>
|
|
|
|
|
<MetricTile
|
|
|
|
|
label="Pending / Failed"
|
|
|
|
|
value={formatNumber(stats.pending + stats.failed)}
|
|
|
|
|
hint={`${stats.failed} failed items`}
|
|
|
|
|
tone={failedRatio > 0.05 ? "vermilion" : "neutral"}
|
|
|
|
|
icon={<AlertTriangle className="size-4" />}
|
2026-08-24 16:32:16 +07:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-26 03:37:14 +07:00
|
|
|
{/* Live Moderation Stream */}
|
|
|
|
|
<div className="mod-tile">
|
|
|
|
|
<GlassPanel>
|
|
|
|
|
<SectionHeader
|
|
|
|
|
eyebrow="Realtime Feed"
|
|
|
|
|
title="Live Stream Audit Log"
|
2026-08-24 16:32:16 +07:00
|
|
|
/>
|
2026-08-26 03:37:14 +07:00
|
|
|
<div className="mt-3">
|
|
|
|
|
<LiveModerationFeed
|
|
|
|
|
actions={
|
|
|
|
|
actions?.filter((r) => {
|
|
|
|
|
if (filterMode === "flagged") return r.status === "failed";
|
|
|
|
|
if (filterMode === "clean") return r.status === "executed";
|
|
|
|
|
return true;
|
|
|
|
|
}) ?? []
|
|
|
|
|
}
|
|
|
|
|
/>
|
2026-08-24 16:32:16 +07:00
|
|
|
</div>
|
2026-08-26 03:37:14 +07:00
|
|
|
</GlassPanel>
|
2026-08-15 17:53:48 +07:00
|
|
|
</div>
|
2026-08-26 03:37:14 +07:00
|
|
|
|
|
|
|
|
{/* Heatmap Section */}
|
|
|
|
|
{hourly && hourly.length > 0 && (
|
|
|
|
|
<div className="mod-tile">
|
|
|
|
|
<GlassPanel>
|
|
|
|
|
<SectionHeader
|
|
|
|
|
eyebrow="Distribution"
|
|
|
|
|
title="Hourly Incident Heatmap"
|
|
|
|
|
/>
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<ModerationHeatmap hours={hourly} />
|
|
|
|
|
</div>
|
|
|
|
|
</GlassPanel>
|
2026-08-18 15:11:01 +07:00
|
|
|
</div>
|
2026-08-24 16:32:16 +07:00
|
|
|
)}
|
2026-08-15 17:53:48 +07:00
|
|
|
</div>
|
2026-08-26 03:37:14 +07:00
|
|
|
</PageTransition>
|
2026-08-14 10:49:44 +07:00
|
|
|
);
|
|
|
|
|
}
|