diff --git a/services/frontend/src/App.tsx b/services/frontend/src/App.tsx index 687c4b5..fac480c 100644 --- a/services/frontend/src/App.tsx +++ b/services/frontend/src/App.tsx @@ -11,7 +11,9 @@ import { } from "./features/messages/hooks/useMessages"; import { type ActiveSpeaker, + type Channel, getAppConfig, + getTextChannels, type MediaState, type MessageRecord, } from "./shared/api/client"; @@ -60,6 +62,8 @@ export default function App() { !!localStorage.getItem("admin-password"), ); const [monitorGuildId, setMonitorGuildId] = useState(""); + const [textChannels, setTextChannels] = useState([]); + const [selectedMessageChannel, setSelectedMessageChannel] = useState(""); const audio = useAudioPlayback(); const activeTab = uiState.activeTab || "live"; @@ -113,7 +117,7 @@ export default function App() { }, onAttachmentUploaded: () => messages - .fetchMessages(monitorGuildId || undefined) + .fetchMessages(monitorGuildId || undefined, selectedMessageChannel || undefined) .catch(() => undefined), onMediaState: (state) => media.setMediaState(state as MediaState), onVoiceRecordingUploaded: (d) => @@ -141,20 +145,38 @@ export default function App() { voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined); }, [selectedVoiceGuild, voice.loadVoiceChannels]); - // Auto-fetch messages for the monitor guild (all channels) + // Load text channels when monitor guild changes (Messages tab) useEffect(() => { if (monitorGuildId) - messages.fetchMessages(monitorGuildId).catch(() => undefined); + getTextChannels(monitorGuildId) + .then(setTextChannels) + .catch(() => undefined); + }, [monitorGuildId]); + + // Auto-fetch messages for the monitor guild + useEffect(() => { + if (monitorGuildId) + messages + .fetchMessages(monitorGuildId, selectedMessageChannel || undefined) + .catch(() => undefined); }, [monitorGuildId, messages.fetchMessages]); + // Re-fetch when channel filter changes + useEffect(() => { + if (monitorGuildId) + messages + .fetchMessages(monitorGuildId, selectedMessageChannel || undefined) + .catch(() => undefined); + }, [selectedMessageChannel, monitorGuildId, messages.fetchMessages]); + // Periodic refetch — keeps dashboard in sync even if WS events missed useEffect(() => { if (!monitorGuildId) return; const interval = setInterval(() => { - messages.fetchMessages(monitorGuildId).catch(() => undefined); + messages.fetchMessages(monitorGuildId, selectedMessageChannel || undefined).catch(() => undefined); }, 15_000); return () => clearInterval(interval); - }, [monitorGuildId, messages.fetchMessages]); + }, [monitorGuildId, messages.fetchMessages, selectedMessageChannel]); return ( ) : ( diff --git a/services/frontend/src/features/messages/hooks/useMessages.ts b/services/frontend/src/features/messages/hooks/useMessages.ts index 3740d01..198558f 100644 --- a/services/frontend/src/features/messages/hooks/useMessages.ts +++ b/services/frontend/src/features/messages/hooks/useMessages.ts @@ -113,6 +113,7 @@ export function useMessages() { ); const { count } = await reanalyzeErrorBatch({ guildId: currentGuild.current ?? undefined, + channelId: currentChannel.current, }); return count; }, []); diff --git a/services/frontend/src/features/messages/index.tsx b/services/frontend/src/features/messages/index.tsx index d682e80..fb1c651 100644 --- a/services/frontend/src/features/messages/index.tsx +++ b/services/frontend/src/features/messages/index.tsx @@ -1,7 +1,7 @@ import { motion } from "framer-motion"; -import { Filter, RotateCw, Search, X } from "lucide-react"; +import { Filter, Hash, RotateCw, Search, X } from "lucide-react"; import { useMemo, useState } from "react"; -import type { MessageRecord } from "../../shared/api/client"; +import type { Channel, MessageRecord } from "../../shared/api/client"; import { cardItem, cardStagger } from "../../shared/hooks/useFramerStagger"; import { Badge, @@ -11,6 +11,11 @@ import { CardHeader, CardTitle, Input, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, Tabs, TabsContent, TabsList, @@ -27,6 +32,9 @@ interface MessagesPanelProps { onLoadMore?: () => void; hasMore?: boolean; loadingMore?: boolean; + textChannels?: Channel[]; + selectedChannel?: string; + onChannelChange?: (channelId: string) => void; } type AiFilter = "all" | "clean" | "flagged" | "error" | "pending"; @@ -39,6 +47,9 @@ export function MessagesPanel({ onLoadMore, hasMore, loadingMore, + textChannels, + selectedChannel, + onChannelChange, }: MessagesPanelProps) { const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); @@ -118,6 +129,27 @@ export function MessagesPanel({ Messages are automatically captured from all text channels in the monitored guild. Real-time updates arrive via WebSocket.

+ {textChannels && textChannels.length > 0 && ( +
+ + +
+ )}