chore(services): update components based on recent changes

Changes:
 services/frontend/src/App.tsx | 37 +++++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)
This commit is contained in:
MythEclipse
2026-06-13 16:54:29 +07:00
parent e76a874f11
commit 5459b13b03
+31 -6
View File
@@ -19,6 +19,7 @@ import {
import { useAudioPlayback } from "./shared/hooks/useAudioPlayback"; import { useAudioPlayback } from "./shared/hooks/useAudioPlayback";
import { useAudioTransmit } from "./shared/hooks/useAudioTransmit"; import { useAudioTransmit } from "./shared/hooks/useAudioTransmit";
import { useUIState } from "./shared/hooks/useUIState"; import { useUIState } from "./shared/hooks/useUIState";
import { createLogger } from "./shared/lib/logger.js";
import { MobileTabBar } from "./shared/ui/MobileTabBar"; import { MobileTabBar } from "./shared/ui/MobileTabBar";
import { useDashboardSocket } from "./shared/ws/socket"; import { useDashboardSocket } from "./shared/ws/socket";
import { DashboardLayout } from "./widgets/DashboardLayout"; import { DashboardLayout } from "./widgets/DashboardLayout";
@@ -28,7 +29,10 @@ export default function App() {
const voice = useVoiceControl(); const voice = useVoiceControl();
const media = useMediaControl(); const media = useMediaControl();
const messages = useMessages(); const messages = useMessages();
const [activeSpeakers, setActiveSpeakers] = useState<ActiveSpeaker[]>([]); const [activeSpeakers, setActiveSpeakers] = useState<
(ActiveSpeaker & { heardAt?: number })[]
>([]);
const logger = useMemo(() => createLogger("app"), []);
const [isAuthenticated, setIsAuthenticated] = useState( const [isAuthenticated, setIsAuthenticated] = useState(
!!localStorage.getItem("admin-password"), !!localStorage.getItem("admin-password"),
); );
@@ -50,26 +54,33 @@ export default function App() {
// Update speaker list from incremental voice_active_user events // Update speaker list from incremental voice_active_user events
const updateSpeakerList = ( const updateSpeakerList = (
prev: ActiveSpeaker[], prev: (ActiveSpeaker & { heardAt?: number })[],
data: Partial<ActiveSpeaker> & { userId?: string; id?: string; speaking: boolean }, data: Partial<ActiveSpeaker> & { userId?: string; id?: string; speaking: boolean },
): ActiveSpeaker[] => { ): (ActiveSpeaker & { heardAt?: number })[] => {
const key = data.userId ?? data.id; const key = data.userId ?? data.id;
if (!key) return prev; if (!key) return prev;
const now = Date.now();
const idx = prev.findIndex( const idx = prev.findIndex(
(s) => (s.userId ?? s.id) === key, (s) => (s.userId ?? s.id) === key,
); );
if (idx >= 0) { if (idx >= 0) {
const next = [...prev]; const next = [...prev];
next[idx] = { ...next[idx], ...data }; next[idx] = { ...next[idx], ...data, heardAt: now };
return next; return next;
} }
return [...prev, data as ActiveSpeaker]; return [...prev, { ...data, heardAt: now } as ActiveSpeaker & { heardAt?: number }];
}; };
const socket = useDashboardSocket({ const socket = useDashboardSocket({
onVoicePcmData: (d) => onVoicePcmData: (d) =>
audio.handleIncomingPcm(d as { userId: string; pcm: string }), audio.handleIncomingPcm(d as { userId: string; pcm: string }),
onUserState: (users) => setActiveSpeakers(users as ActiveSpeaker[]), onUserState: (users) =>
setActiveSpeakers(
(users as (ActiveSpeaker & { heardAt?: number })[]).map((u) => ({
...u,
heardAt: Date.now(),
})),
),
onVoiceActiveUser: (data) => onVoiceActiveUser: (data) =>
setActiveSpeakers((prev) => setActiveSpeakers((prev) =>
updateSpeakerList( updateSpeakerList(
@@ -167,6 +178,20 @@ export default function App() {
return () => clearInterval(interval); return () => clearInterval(interval);
}, [monitorGuildId, messages.fetchMessages]); }, [monitorGuildId, messages.fetchMessages]);
// Stale speaker pruning — remove speakers not heard from in 30s
useEffect(() => {
const interval = setInterval(() => {
setActiveSpeakers((prev) => {
const now = Date.now();
const pruned = prev.filter(
(s) => s.speaking || (s.heardAt && now - s.heardAt < 30_000),
);
return pruned.length < prev.length ? pruned : prev;
});
}, 30_000);
return () => clearInterval(interval);
}, []);
return ( return (
<DashboardLayout <DashboardLayout
activeTab={activeTab} activeTab={activeTab}