import React, { useState, useEffect, useCallback } from "react"; import Paper from "@mui/material/Paper"; import List from "@mui/material/List"; import Button from "@mui/material/Button"; import IconButton from "@mui/material/IconButton"; import Dialog from "@mui/material/Dialog"; import DialogTitle from "@mui/material/DialogTitle"; import DialogContent from "@mui/material/DialogContent"; import DialogActions from "@mui/material/DialogActions"; import SettingsIcon from "@mui/icons-material/Settings"; import "./UserList.css"; import { MediaControl, MediaAgent, Peer } from "./MediaControl"; import Box from "@mui/material/Box"; import { Session } from "./GlobalContext"; import useWebSocket from "react-use-websocket"; import { ApiClient } from "./api-client"; import BotConfig from "./BotConfig"; type User = { name: string; session_id: string; live: boolean; local: boolean /* Client side variable */; protected?: boolean; has_media?: boolean; // Whether this user provides audio/video streams bot_run_id?: string; bot_provider_id?: string; bot_instance_id?: string; // For bot instances }; type UserListProps = { socketUrl: string; session: Session; lobbyId: string; }; const UserList: React.FC = (props: UserListProps) => { const { socketUrl, session, lobbyId } = props; const [users, setUsers] = useState(null); const [peers, setPeers] = useState>({}); const [videoClass, setVideoClass] = useState("Large"); // Bot configuration state const [botConfigDialogOpen, setBotConfigDialogOpen] = useState(false); const [selectedBotForConfig, setSelectedBotForConfig] = useState(null); const [leavingBots, setLeavingBots] = useState>(new Set()); const apiClient = new ApiClient(); const handleBotLeave = async (user: User) => { if (!user.bot_instance_id) return; setLeavingBots((prev) => new Set(prev).add(user.session_id)); try { await apiClient.createRequestBotLeaveLobby(user.bot_instance_id); console.log(`Bot ${user.name} leave requested successfully`); } catch (error) { console.error("Failed to request bot leave:", error); } finally { setLeavingBots((prev) => { const newSet = new Set(prev); newSet.delete(user.session_id); return newSet; }); } }; // Bot configuration handlers const handleOpenBotConfig = (user: User) => { setSelectedBotForConfig(user); setBotConfigDialogOpen(true); }; const handleCloseBotConfig = () => { setBotConfigDialogOpen(false); setSelectedBotForConfig(null); }; const sortUsers = useCallback( (A: any, B: any) => { if (!session) { return 0; } /* active user first */ if (A.name === session.name) { return -1; } if (B.name === session.name) { return +1; } /* Sort active users first */ if (A.name && !B.name) { return -1; } if (B.name && !A.name) { return +1; } /* Otherwise, sort by color */ if (A.color && B.color) { return A.color.localeCompare(B.color); } return 0; }, [session] ); // Use the WebSocket hook for lobby events with automatic reconnection const { sendJsonMessage } = useWebSocket(socketUrl, { share: true, shouldReconnect: (closeEvent) => true, // Auto-reconnect on connection loss reconnectInterval: 5000, onMessage: (event: MessageEvent) => { if (!session) { return; } const message = JSON.parse(event.data); const data: any = message.data; switch (message.type) { case "lobby_state": type LobbyStateData = { participants: User[]; }; const lobby_state = data as LobbyStateData; console.log(`users - lobby_state`, lobby_state.participants); lobby_state.participants.forEach((user) => { user.local = user.session_id === session.id; }); lobby_state.participants.sort(sortUsers); setVideoClass(lobby_state.participants.length <= 2 ? "Medium" : "Small"); setUsers(lobby_state.participants); break; default: break; } }, }); useEffect(() => { if (users !== null) { return; } sendJsonMessage({ type: "list_users", }); }, [users, sendJsonMessage]); return ( {users?.map((user) => (
{user.name ? user.name : user.session_id}
{user.protected && (
🔒
)} {user.bot_instance_id && (
🤖
)}
{user.bot_instance_id && ( {user.bot_run_id && ( handleOpenBotConfig(user)} style={{ width: "24px", height: "24px", fontSize: "0.7em" }} title="Configure bot" > )} )}
{user.name && !user.live &&
}
{user.name && user.live && peers[user.session_id] && (user.local || user.has_media !== false) ? ( ) : user.name && user.live && user.has_media === false ? (
💬 Chat Only
) : ( )}
))}
{/* Bot Configuration Dialog */} Configure Bot {selectedBotForConfig && ( { console.log("Bot configuration updated:", config); // Configuration updates are handled via WebSocket, so we don't need to do anything special here }} /> )}
); }; export { UserList };