feat(messages): add text channel filtering for message browser

This commit is contained in:
MythEclipse
2026-06-03 20:31:23 +07:00
parent a39c7a91c2
commit 79c2e31b17
3 changed files with 65 additions and 7 deletions
+30 -5
View File
@@ -11,7 +11,9 @@ import {
} from "./features/messages/hooks/useMessages"; } from "./features/messages/hooks/useMessages";
import { import {
type ActiveSpeaker, type ActiveSpeaker,
type Channel,
getAppConfig, getAppConfig,
getTextChannels,
type MediaState, type MediaState,
type MessageRecord, type MessageRecord,
} from "./shared/api/client"; } from "./shared/api/client";
@@ -60,6 +62,8 @@ export default function App() {
!!localStorage.getItem("admin-password"), !!localStorage.getItem("admin-password"),
); );
const [monitorGuildId, setMonitorGuildId] = useState(""); const [monitorGuildId, setMonitorGuildId] = useState("");
const [textChannels, setTextChannels] = useState<Channel[]>([]);
const [selectedMessageChannel, setSelectedMessageChannel] = useState<string>("");
const audio = useAudioPlayback(); const audio = useAudioPlayback();
const activeTab = uiState.activeTab || "live"; const activeTab = uiState.activeTab || "live";
@@ -113,7 +117,7 @@ export default function App() {
}, },
onAttachmentUploaded: () => onAttachmentUploaded: () =>
messages messages
.fetchMessages(monitorGuildId || undefined) .fetchMessages(monitorGuildId || undefined, selectedMessageChannel || undefined)
.catch(() => undefined), .catch(() => undefined),
onMediaState: (state) => media.setMediaState(state as MediaState), onMediaState: (state) => media.setMediaState(state as MediaState),
onVoiceRecordingUploaded: (d) => onVoiceRecordingUploaded: (d) =>
@@ -141,20 +145,38 @@ export default function App() {
voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined); voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined);
}, [selectedVoiceGuild, voice.loadVoiceChannels]); }, [selectedVoiceGuild, voice.loadVoiceChannels]);
// Auto-fetch messages for the monitor guild (all channels) // Load text channels when monitor guild changes (Messages tab)
useEffect(() => { useEffect(() => {
if (monitorGuildId) 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]); }, [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 // Periodic refetch — keeps dashboard in sync even if WS events missed
useEffect(() => { useEffect(() => {
if (!monitorGuildId) return; if (!monitorGuildId) return;
const interval = setInterval(() => { const interval = setInterval(() => {
messages.fetchMessages(monitorGuildId).catch(() => undefined); messages.fetchMessages(monitorGuildId, selectedMessageChannel || undefined).catch(() => undefined);
}, 15_000); }, 15_000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [monitorGuildId, messages.fetchMessages]); }, [monitorGuildId, messages.fetchMessages, selectedMessageChannel]);
return ( return (
<DashboardLayout <DashboardLayout
@@ -212,6 +234,9 @@ export default function App() {
onLoadMore={messages.loadMore} onLoadMore={messages.loadMore}
hasMore={messages.hasMore} hasMore={messages.hasMore}
loadingMore={messages.loadingMore} loadingMore={messages.loadingMore}
textChannels={textChannels}
selectedChannel={selectedMessageChannel}
onChannelChange={setSelectedMessageChannel}
/> />
) : ( ) : (
<AnalyticsErrorBoundary> <AnalyticsErrorBoundary>
@@ -113,6 +113,7 @@ export function useMessages() {
); );
const { count } = await reanalyzeErrorBatch({ const { count } = await reanalyzeErrorBatch({
guildId: currentGuild.current ?? undefined, guildId: currentGuild.current ?? undefined,
channelId: currentChannel.current,
}); });
return count; return count;
}, []); }, []);
@@ -1,7 +1,7 @@
import { motion } from "framer-motion"; 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 { 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 { cardItem, cardStagger } from "../../shared/hooks/useFramerStagger";
import { import {
Badge, Badge,
@@ -11,6 +11,11 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
Input, Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Tabs, Tabs,
TabsContent, TabsContent,
TabsList, TabsList,
@@ -27,6 +32,9 @@ interface MessagesPanelProps {
onLoadMore?: () => void; onLoadMore?: () => void;
hasMore?: boolean; hasMore?: boolean;
loadingMore?: boolean; loadingMore?: boolean;
textChannels?: Channel[];
selectedChannel?: string;
onChannelChange?: (channelId: string) => void;
} }
type AiFilter = "all" | "clean" | "flagged" | "error" | "pending"; type AiFilter = "all" | "clean" | "flagged" | "error" | "pending";
@@ -39,6 +47,9 @@ export function MessagesPanel({
onLoadMore, onLoadMore,
hasMore, hasMore,
loadingMore, loadingMore,
textChannels,
selectedChannel,
onChannelChange,
}: MessagesPanelProps) { }: MessagesPanelProps) {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState<MessageRecord[]>([]); const [searchResults, setSearchResults] = useState<MessageRecord[]>([]);
@@ -118,6 +129,27 @@ export function MessagesPanel({
Messages are automatically captured from all text channels in the Messages are automatically captured from all text channels in the
monitored guild. Real-time updates arrive via WebSocket. monitored guild. Real-time updates arrive via WebSocket.
</p> </p>
{textChannels && textChannels.length > 0 && (
<div className="mt-3 flex items-center gap-2">
<Hash className="h-4 w-4 text-muted-foreground shrink-0" />
<Select
value={selectedChannel || ""}
onValueChange={(val) => onChannelChange?.(val)}
>
<SelectTrigger className="w-full max-w-xs h-9 text-sm">
<SelectValue placeholder="All channels" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__all">All channels</SelectItem>
{textChannels.map((ch) => (
<SelectItem key={ch.id} value={ch.id}>
# {ch.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
</motion.div> </motion.div>