fix(fe): remove remaining voice/recording/media leftovers from navigation, dashboard, and ws layer
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
ChevronRight,
|
||||
Hash,
|
||||
MessageSquare,
|
||||
Mic,
|
||||
Shield,
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
@@ -160,12 +159,6 @@ export function DashboardView({
|
||||
hint={`${formatNumber(s.total_users)} total profiles`}
|
||||
icon={<Users className="size-4" />}
|
||||
/>
|
||||
<MetricTile
|
||||
label="Voice Recordings"
|
||||
value={formatNumber(s.total_voice_recordings)}
|
||||
hint="Archived audio sessions"
|
||||
icon={<Mic className="size-4" />}
|
||||
/>
|
||||
<MetricTile
|
||||
label="Mod Queue"
|
||||
value={formatNumber(mod.pending + mod.processing)}
|
||||
|
||||
@@ -567,11 +567,6 @@
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
@keyframes eq {
|
||||
0%, 100% { transform: scaleY(0.35); }
|
||||
50% { transform: scaleY(1); }
|
||||
}
|
||||
|
||||
@keyframes fade-up {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
@@ -634,12 +629,6 @@
|
||||
animation: shimmer 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Live equalizer bars (mini-player / recording player). */
|
||||
.animate-eq {
|
||||
transform-origin: bottom;
|
||||
animation: eq 0.9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Entrance for popovers, toasts, mini-player. */
|
||||
.animate-fade-up { animation: fade-up 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
|
||||
.animate-toast-in { animation: toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
|
||||
@@ -694,7 +683,6 @@
|
||||
.animate-spin-disc,
|
||||
.animate-pulse-ring,
|
||||
.animate-shimmer,
|
||||
.animate-eq,
|
||||
.animate-fade-up,
|
||||
.animate-toast-in,
|
||||
.animate-stagger,
|
||||
|
||||
@@ -25,8 +25,7 @@ const bricolage = Bricolage_Grotesque({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "GMW — Discord Moderation Console",
|
||||
description:
|
||||
"AI-powered Discord moderation, voice monitoring, and media control console",
|
||||
description: "AI-powered Discord moderation console",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
|
||||
@@ -2,4 +2,3 @@ export { AreaActivity } from "./area-activity";
|
||||
export { Donut } from "./donut";
|
||||
export { RadialGauge } from "./radial-gauge";
|
||||
export { Sparkline } from "./sparkline";
|
||||
export { Equalizer } from "./waveform";
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/** Live equalizer bars. `bars` are 0..1 levels. */
|
||||
export function Equalizer({
|
||||
bars,
|
||||
color = "var(--color-signal)",
|
||||
className,
|
||||
}: {
|
||||
bars: number[];
|
||||
color?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex h-10 items-end gap-[3px]", className)}>
|
||||
{bars.length === 0 ? (
|
||||
<div className="flex w-full items-end gap-[3px]">
|
||||
{Array.from({ length: 28 }).map((_, i) => (
|
||||
<span
|
||||
key={`eq-${i}`}
|
||||
className="flex-1 rounded-full bg-hairline"
|
||||
style={{ height: "12%" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
bars.map((b, i) => (
|
||||
<span
|
||||
key={`bar-${i}`}
|
||||
className="flex-1 rounded-full"
|
||||
style={{
|
||||
height: `${Math.max(6, b * 100)}%`,
|
||||
background: color,
|
||||
boxShadow: b > 0.05 ? `0 0 8px ${color}` : "none",
|
||||
transition: "height 90ms linear",
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
Headphones,
|
||||
LayoutDashboard,
|
||||
type LucideIcon,
|
||||
MessageSquare,
|
||||
Mic,
|
||||
Music,
|
||||
Search,
|
||||
Shield,
|
||||
Users,
|
||||
@@ -40,24 +37,6 @@ export const navItems: NavItem[] = [
|
||||
icon: Users,
|
||||
matchPrefix: "/users",
|
||||
},
|
||||
{
|
||||
href: "/voice",
|
||||
label: "Voice",
|
||||
icon: Mic,
|
||||
matchPrefix: "/voice",
|
||||
},
|
||||
{
|
||||
href: "/media",
|
||||
label: "Media",
|
||||
icon: Music,
|
||||
matchPrefix: "/media",
|
||||
},
|
||||
{
|
||||
href: "/recordings",
|
||||
label: "Recordings",
|
||||
icon: Headphones,
|
||||
matchPrefix: "/recordings",
|
||||
},
|
||||
{
|
||||
href: "/moderation",
|
||||
label: "Moderation",
|
||||
|
||||
@@ -7,7 +7,6 @@ export interface DashboardStats {
|
||||
total_clean: number;
|
||||
total_warned: number;
|
||||
total_error: number;
|
||||
total_voice_recordings: number;
|
||||
total_profiles: number;
|
||||
today_messages: number;
|
||||
today_flagged: number;
|
||||
|
||||
@@ -2,13 +2,9 @@ export type DashboardTab = "messages" | "live" | "dashboard";
|
||||
|
||||
export interface UiState {
|
||||
selected_guild?: string | null;
|
||||
selected_voice_guild?: string | null;
|
||||
selected_voice_channel?: string | null;
|
||||
selected_text_guild?: string | null;
|
||||
selected_text_channel?: string | null;
|
||||
active_tab?: DashboardTab | null;
|
||||
is_listening?: boolean | null;
|
||||
is_streaming?: boolean | null;
|
||||
}
|
||||
|
||||
export interface ChatbotResponse {
|
||||
|
||||
@@ -9,24 +9,20 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { toast } from "@/components/primitives";
|
||||
import { WsConnection } from "./connection";
|
||||
import type { PcmChunk, WsEventHandler, WsEventType, WsStatus } from "./types";
|
||||
import type { WsEventHandler, WsEventType, WsStatus } from "./types";
|
||||
|
||||
interface WsContextValue {
|
||||
status: WsStatus;
|
||||
connect: () => void;
|
||||
disconnect: () => void;
|
||||
sendText: (text: string) => void;
|
||||
sendBinary: (data: ArrayBufferLike) => void;
|
||||
/** Subscribe to a typed WS event. Returns unsubscribe function. */
|
||||
on: <E extends WsEventType>(
|
||||
eventType: E,
|
||||
handler: WsEventHandler<E>,
|
||||
) => () => void;
|
||||
/** Subscribe to binary PCM events. Returns unsubscribe function. */
|
||||
onPcm: (handler: (chunk: PcmChunk) => void) => () => void;
|
||||
}
|
||||
|
||||
const WsContext = createContext<WsContextValue | null>(null);
|
||||
@@ -40,7 +36,6 @@ export function WsProvider({
|
||||
}) {
|
||||
const connRef = useRef<WsConnection | null>(null);
|
||||
const [status, setStatus] = useState<WsStatus>("disconnected");
|
||||
const { mutate } = useSWRConfig();
|
||||
// Tracks whether we've ever been connected — used to suppress the
|
||||
// "reconnecting" toast on initial page load.
|
||||
const wasConnected = useRef(false);
|
||||
@@ -48,7 +43,6 @@ export function WsProvider({
|
||||
// Event handler registry — Ref so listeners survive re-renders without reconnect
|
||||
// Using unknown as internal store; typed at the subscribe interface
|
||||
const handlersRef = useRef<Record<string, Set<(data: unknown) => void>>>({});
|
||||
const pcmHandlersRef = useRef<Set<(chunk: PcmChunk) => void>>(new Set());
|
||||
|
||||
const handleJsonEvent = useCallback((json: string) => {
|
||||
try {
|
||||
@@ -66,17 +60,6 @@ export function WsProvider({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleBinaryEvent = useCallback((buffer: ArrayBuffer) => {
|
||||
if (buffer.byteLength < 4 || pcmHandlersRef.current.size === 0) return;
|
||||
|
||||
const view = new DataView(buffer);
|
||||
const userIdHash = view.getUint32(0, true);
|
||||
const samples = new Int16Array(buffer, 4);
|
||||
|
||||
const chunk: PcmChunk = { userIdHash, samples };
|
||||
pcmHandlersRef.current.forEach((h) => h(chunk));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const conn = new WsConnection(url);
|
||||
connRef.current = conn;
|
||||
@@ -97,9 +80,6 @@ export function WsProvider({
|
||||
wasConnected.current = false;
|
||||
} else if (s === "connected") {
|
||||
wasConnected.current = true;
|
||||
// After reconnect, force-refetch voice status immediately so
|
||||
// the UI converges faster instead of waiting up to 4s for SWR poll.
|
||||
void mutate(["voice-status"]);
|
||||
} else if (s === "error" && !wasConnected.current) {
|
||||
toast({
|
||||
title: "Connection error",
|
||||
@@ -112,8 +92,6 @@ export function WsProvider({
|
||||
const unsubEvent = conn.onEvent((event) => {
|
||||
if (event.type === "text") {
|
||||
handleJsonEvent(event.data);
|
||||
} else {
|
||||
handleBinaryEvent(event.data);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,7 +104,7 @@ export function WsProvider({
|
||||
unsubEvent();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [url, handleBinaryEvent, handleJsonEvent, mutate]);
|
||||
}, [url, handleJsonEvent]);
|
||||
|
||||
const subscribe = useCallback(
|
||||
<E extends WsEventType>(_eventType: E, handler: WsEventHandler<E>) => {
|
||||
@@ -144,23 +122,12 @@ export function WsProvider({
|
||||
[],
|
||||
);
|
||||
|
||||
const subscribePcm = useCallback((handler: (chunk: PcmChunk) => void) => {
|
||||
pcmHandlersRef.current.add(handler);
|
||||
return () => {
|
||||
pcmHandlersRef.current.delete(handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const connect = useCallback(() => connRef.current?.connect(), []);
|
||||
const disconnect = useCallback(() => connRef.current?.disconnect(), []);
|
||||
const sendText = useCallback(
|
||||
(text: string) => connRef.current?.sendText(text),
|
||||
[],
|
||||
);
|
||||
const sendBinary = useCallback(
|
||||
(data: ArrayBufferLike) => connRef.current?.sendBinary(data),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<WsContext.Provider
|
||||
@@ -169,9 +136,7 @@ export function WsProvider({
|
||||
connect,
|
||||
disconnect,
|
||||
sendText,
|
||||
sendBinary,
|
||||
on: subscribe,
|
||||
onPcm: subscribePcm,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -62,10 +62,3 @@ export type WsEventType = keyof WsEventMap;
|
||||
export type WsEventHandler<E extends WsEventType = WsEventType> = (
|
||||
data: WsEventMap[E],
|
||||
) => void;
|
||||
|
||||
// ── Binary PCM ─────────────────────────────────────────────
|
||||
|
||||
export interface PcmChunk {
|
||||
userIdHash: number;
|
||||
samples: Int16Array;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user