import { useEffect, useState } from "react"; import { MoreHorizontal, MessageSquare, Pause, Play, Square, OctagonAlert } from "lucide-react"; import { toast } from "sonner"; import { api } from "@/api/client"; import type { AdminSession } from "@/api/types"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { getPrimaryPlaybackAction, isSessionPaused } from "./adminSessionActionModel"; type SessionActionKind = "pause" | "resume" | "stop" | "terminate" | "message"; interface AdminSessionActionsProps { session: AdminSession; compact?: boolean; } type SessionCommandResponse = { command_id: string; status: string; }; const successMessages: Record, string> = { pause: "Pause command sent", resume: "Resume command sent", stop: "Stop command sent", terminate: "Terminate command sent", }; const fallbackMessages: Record, string> = { pause: "Pause could not reach the player directly. Silo will end the session shortly instead.", resume: "Resume could not reach the player directly. Silo will end the session shortly instead.", stop: "Stop could not reach the player directly. Silo will end the session shortly instead.", terminate: "Terminate could not reach the player directly. Silo will end the session shortly instead.", }; const unsupportedPlaybackControlCopy = "This session does not support live pause, resume, or messages. Stop and Terminate can still end playback."; export function AdminSessionActions({ session, compact = false }: AdminSessionActionsProps) { const [pendingAction, setPendingAction] = useState(null); const [messageOpen, setMessageOpen] = useState(false); const [message, setMessage] = useState(""); const [optimisticPaused, setOptimisticPaused] = useState(null); const supportsPlaybackControl = session.has_playback_control !== false; useEffect(() => { setOptimisticPaused(null); }, [session.session_id, session.is_paused]); useEffect(() => { if (!supportsPlaybackControl) { setMessageOpen(false); } }, [supportsPlaybackControl]); const paused = optimisticPaused ?? isSessionPaused(session); const primaryAction = paused ? { action: "resume" as const, label: "Resume" } : getPrimaryPlaybackAction(session); async function runAction(action: Exclude) { setPendingAction(action); try { const response = await api( `/admin/sessions/${session.session_id}/${action}`, { method: "POST", }, ); if (response.status === "dispatched" && action === "pause") { setOptimisticPaused(true); } else if (response.status === "dispatched" && action === "resume") { setOptimisticPaused(false); } toast.success( response.status === "fallback_scheduled" ? fallbackMessages[action] : successMessages[action], ); } catch (error) { toast.error(error instanceof Error ? error.message : "Failed to send session command"); } finally { setPendingAction(null); } } async function sendMessage() { const trimmed = message.trim(); if (!trimmed) { toast.error("Message is required"); return; } setPendingAction("message"); try { await api(`/admin/sessions/${session.session_id}/message`, { method: "POST", body: JSON.stringify({ message: trimmed }), }); toast.success("Message sent"); setMessage(""); setMessageOpen(false); } catch (error) { toast.error(error instanceof Error ? error.message : "Failed to send message"); } finally { setPendingAction(null); } } return ( <> {supportsPlaybackControl ? "Playback Actions" : "Limited Playback Actions"} {supportsPlaybackControl ? ( void runAction(primaryAction.action)}> {primaryAction.action === "pause" ? ( ) : ( )} {primaryAction.label} ) : ( <>
{unsupportedPlaybackControlCopy}
)} void runAction("stop")}> Stop {supportsPlaybackControl ? ( setMessageOpen(true)}> Message… ) : null} void runAction("terminate")}> Terminate
setMessageOpen(supportsPlaybackControl && nextOpen)} > Send Message Send a custom message to {session.username || `User #${session.user_id}`} during playback.