"use client"; import { Flag, Image, Loader2, RefreshCw, Search } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { GlassCard } from "@/components/glass/card"; import { GlassPanel } from "@/components/glass/panel"; import { SubNav } from "@/components/layout/sub-nav"; import { extractFirstImage } from "@/components/messages/message-card"; import { MessageDetailView } from "@/components/messages/message-detail-view"; import { MessageList } from "@/components/messages/message-list"; import { SearchOverlay } from "@/components/messages/search-overlay"; import { EmptyState, ErrorState, LoadingSkeleton } from "@/components/shared"; import { GuildSelector } from "@/components/shared/guild-selector"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useImages, useLoadMore, useMessageDetail, useMessages, useMessagesHasMore, useMessagesWsSync, useReanalyze, useReanalyzeBatch, useReview, useTextChannels, } from "@/hooks"; import { renderMessageContent } from "@/lib/format"; import type { MessageRecord } from "@/lib/types"; import { cn } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; type MessagesTab = "all" | "images" | "review"; export default function MessagesPage() { const router = useRouter(); const searchParams = useSearchParams(); const [guildId, setGuildId] = useState(searchParams.get("guild") || ""); const [selectedChannel, setSelectedChannel] = useState( searchParams.get("channel") || "", ); const [detailId, setDetailId] = useState( searchParams.get("selected"), ); const [tab, setTab] = useState( (searchParams.get("tab") as MessagesTab) || "all", ); const [searchOpen, setSearchOpen] = useState(false); const ws = useWebSocket(); const { data: channels = [] } = useTextChannels(guildId); const { data: messages, isLoading, error, refetch, } = useMessages(guildId, selectedChannel || undefined); const { data: cursorData } = useMessagesHasMore( guildId, selectedChannel || undefined, ); const loadMoreMut = useLoadMore(); const { data: images } = useImages(guildId); const { data: reviews } = useReview(selectedChannel || undefined); const reanalyzeMut = useReanalyze(); const reanalyzeBatchMut = useReanalyzeBatch(); const { message: detailMessage, attachments: detailAttachments, loading: detailLoading, } = useMessageDetail(detailId); useMessagesWsSync(ws, guildId); // Sync state to URL useEffect(() => { const params = new URLSearchParams(); if (guildId) params.set("guild", guildId); if (selectedChannel) params.set("channel", selectedChannel); if (detailId) params.set("selected", detailId); if (tab !== "all") params.set("tab", tab); router.replace(`/messages?${params.toString()}`, { scroll: false }); }, [guildId, selectedChannel, detailId, tab, router]); // Global Cmd+K search trigger useEffect(() => { const handleKey = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setSearchOpen(true); } }; document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); }, []); const handleLoadMore = useCallback(() => { if (!cursorData?.cursor || loadMoreMut.isPending) return; loadMoreMut.mutate({ guildId, channelId: selectedChannel || undefined, cursor: cursorData.cursor, }); }, [cursorData, loadMoreMut, guildId, selectedChannel]); const handleGuildChange = useCallback((g: string) => { setGuildId(g); setSelectedChannel(""); setDetailId(null); }, []); const subNavTabs = [ { id: "all", label: "All", icon: null }, { id: "images", label: "Images", icon: }, { id: "review", label: "Review", icon: }, ]; const currentMessages = messages ?? []; return (
{/* ── Controls bar ── */}
{channels.length > 0 && ( )}
{/* ── Sub navigation ── */} setTab(t as MessagesTab)} /> {/* ── Split pane ── */} {error ? ( ) : isLoading ? ( ) : (
{/* Left pane */}
{tab === "all" && ( reanalyzeMut.mutate(id)} hasMore={cursorData?.hasMore} onLoadMore={handleLoadMore} isLoadingMore={loadMoreMut.isPending} /> )} {tab === "images" && ( )} {tab === "review" && ( )}
{/* Right pane — message detail */} {detailId && (
{detailLoading ? ( ) : detailMessage ? (
) : null}
)}
)} {/* ── Search overlay ── */} setSearchOpen(false)} onSelect={(id) => { setDetailId(id); setTab("all"); }} />
); } // ── Inline ImageGrid (glass-styled) ──────────────── function ImageGrid({ items, onSelect, }: { items: MessageRecord[]; onSelect: (id: string) => void; }) { return (
{items.map((item) => { const imgUrl = extractFirstImage(item.metadata); return ( ); })} {items.length === 0 && ( )}
); } // ── Inline ReviewList (glass-styled) ──────────────── function ReviewList({ items, onSelect, }: { items: MessageRecord[]; onSelect: (id: string) => void; }) { return (
{items.map((item) => ( onSelect(item.id)} >

{renderMessageContent(item.content, item.metadata) || item.id}

))} {items.length === 0 && ( )}
); }