'use client'; /** * Discord-style main chat panel. * Layout: header + message list + input + member sidebar */ import type { RoomResponse, RoomThreadResponse } from '@/client'; import type { MessageWithMeta } from '@/contexts'; import { cn } from '@/lib/utils'; import { Hash, Lock, Users, Search, ChevronLeft, AtSign, Pin, Settings, } from 'lucide-react'; import { useCallback, useEffect, useRef, useState, } from 'react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { MessageList } from './message/MessageList'; import { MessageInput, type MessageInputHandle } from './message/MessageInput'; import { RoomMessageEditDialog } from './RoomMessageEditDialog'; import { RoomMessageEditHistoryDialog } from './RoomMessageEditHistoryDialog'; import { RoomMentionPanel } from './RoomMentionPanel'; import { RoomThreadPanel } from './RoomThreadPanel'; import { RoomSettingsPanel } from './RoomSettingsPanel'; import { DiscordMemberList } from './DiscordMemberList'; import { useRoom } from '@/contexts'; // ─── Main Panel ────────────────────────────────────────────────────────── interface DiscordChatPanelProps { room: RoomResponse; isAdmin: boolean; onClose: () => void; onDelete: () => void; } export function DiscordChatPanel({ room, isAdmin, onClose, onDelete }: DiscordChatPanelProps) { const { messages, members, membersLoading, sendMessage, editMessage, revokeMessage, updateRoom, wsStatus, wsError, wsClient, threads, refreshThreads, roomAiConfigs, } = useRoom(); const messagesEndRef = useRef(null); const messageInputRef = useRef(null); const [replyingTo, setReplyingTo] = useState(null); const [editingMessage, setEditingMessage] = useState(null); const [editDialogOpen, setEditDialogOpen] = useState(false); const [editHistoryDialogOpen, setEditHistoryDialogOpen] = useState(false); const [selectedMessageForHistory, setSelectedMessageForHistory] = useState(''); const [showSettings, setShowSettings] = useState(false); const [showMentions, setShowMentions] = useState(false); const [showMemberList, setShowMemberList] = useState(true); const [activeThread, setActiveThread] = useState<{ thread: RoomThreadResponse; parentMessage: MessageWithMeta } | null>(null); const [isUpdatingRoom, setIsUpdatingRoom] = useState(false); const wsDotClass = wsStatus === 'open' ? 'connected' : wsStatus === 'connecting' ? 'connecting' : 'disconnected'; const handleSend = useCallback( (content: string) => { sendMessage(content, 'text', replyingTo?.id ?? undefined); setReplyingTo(null); }, [sendMessage, replyingTo], ); const handleEdit = useCallback((message: MessageWithMeta) => { setEditingMessage(message); setEditDialogOpen(true); }, []); const handleViewEditHistory = useCallback((message: MessageWithMeta) => { setSelectedMessageForHistory(message.id); setEditHistoryDialogOpen(true); }, []); const handleEditConfirm = useCallback( (newContent: string) => { if (!editingMessage) return; editMessage(editingMessage.id, newContent); setEditDialogOpen(false); setEditingMessage(null); toast.success('Message updated'); }, [editingMessage?.id, editMessage], ); const handleRevoke = useCallback( (message: MessageWithMeta) => { revokeMessage(message.id); toast.success('Message deleted'); }, [revokeMessage], ); const handleOpenThread = useCallback((message: MessageWithMeta) => { if (!message.thread_id) return; const thread = threads.find(t => t.id === message.thread_id); if (thread) setActiveThread({ thread, parentMessage: message }); }, [threads]); const handleCreateThread = useCallback(async (message: MessageWithMeta) => { if (!wsClient || message.thread_id) return; try { const thread = await wsClient.threadCreate(room.id, message.seq); setActiveThread({ thread, parentMessage: message }); refreshThreads(); } catch (err) { console.error('Failed to create thread:', err); toast.error('Failed to create thread'); } }, [wsClient, room.id, refreshThreads]); const handleUpdateRoom = useCallback( async (name: string, isPublic: boolean) => { setIsUpdatingRoom(true); try { await updateRoom(room.id, name, isPublic); toast.success('Room updated'); setShowSettings(false); } catch { toast.error('Failed to update room'); } finally { setIsUpdatingRoom(false); } }, [room.id, updateRoom], ); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages.length]); useEffect(() => { setReplyingTo(null); setEditingMessage(null); setEditDialogOpen(false); setShowSettings(false); setShowMentions(false); setActiveThread(null); }, [room.id]); return (
{/* ── Header ─────────────────────────────────────────────── */}
{room.public ? : }

{room.room_name}

{wsStatus !== 'open' && wsStatus !== 'idle' && ( {wsStatus === 'connecting' ? 'Connecting...' : wsError ?? 'Disconnected'} )}
{/* Settings — opens Room Settings */} {isAdmin && ( )} {isAdmin && ( )}
{/* ── Body ──────────────────────────────────────────────── */}
{ messageInputRef.current?.insertMention('user', userId, username); messageInputRef.current?.focus(); }} onOpenThread={handleOpenThread} onCreateThread={handleCreateThread} /> setReplyingTo(null)} />
{showMemberList && ( { const label = user_info?.username ?? user; const type = role === 'ai' ? 'ai' : 'user'; messageInputRef.current?.insertMention(type, user, label); messageInputRef.current?.focus(); }} aiConfigs={roomAiConfigs} /> )}
{/* ── Slide Panels ──────────────────────────────────────── */} {showSettings && ( )} {showMentions && ( )} {activeThread && ( setActiveThread(null)} /> )}
); }