import { useState, useCallback } from "react"; import { stopInstance, deleteInstance, startInstance, recreateInstanceTunnel, } from "../api/sessions"; import type { Session } from "../api/sessions"; interface UseInstanceActionsOptions { onRefresh: () => Promise; } interface UseInstanceActionsReturn { loadingSessionId: string | null; dirtyDeleteSession: Session | null; dirtyDeleteFiles: string[]; handleOpen: (session: Session) => void; handleStart: (session: Session) => Promise; handleStop: (session: Session) => Promise; handleDelete: (session: Session) => Promise; handleForceDelete: (session: Session) => Promise; handleRecreateTunnel: (session: Session) => Promise; clearDirtyDelete: () => void; } export function useInstanceActions( options: UseInstanceActionsOptions, ): UseInstanceActionsReturn { const { onRefresh } = options; const [loadingSessionId, setLoadingSessionId] = useState(null); const [dirtyDeleteSession, setDirtyDeleteSession] = useState( null, ); const [dirtyDeleteFiles, setDirtyDeleteFiles] = useState([]); const handleOpen = useCallback((session: Session) => { if (session.url) { window.open(session.url, "_blank", "noopener,noreferrer"); return; } if (session.tool_type_interfaces?.includes("terminal")) { window.location.href = `/instances/${session.id}/terminal`; return; } window.location.href = `/projects/${session.project_id}`; }, []); const handleStart = useCallback( async (session: Session) => { if (loadingSessionId === session.id) return; setLoadingSessionId(session.id); try { await startInstance( session.project_id, session.repository_id, session.id, ); await onRefresh(); } catch { // ignore } finally { setLoadingSessionId(null); } }, [loadingSessionId, onRefresh], ); const handleStop = useCallback( async (session: Session) => { if (loadingSessionId === session.id) return; setLoadingSessionId(session.id); try { await stopInstance( session.project_id, session.repository_id, session.id, ); await onRefresh(); } catch { // ignore } finally { setLoadingSessionId(null); } }, [loadingSessionId, onRefresh], ); const handleDelete = useCallback( async (session: Session) => { if (loadingSessionId === session.id) return; setLoadingSessionId(session.id); try { await deleteInstance( session.project_id, session.repository_id, session.id, ); setDirtyDeleteSession(null); setDirtyDeleteFiles([]); await onRefresh(); } catch (error) { const axiosError = error as { response?: { status?: number; data?: { detail?: { changed_files?: string[] } }; }; }; if (axiosError.response?.status === 409) { const detail = axiosError.response.data?.detail; if (detail?.changed_files) { setDirtyDeleteSession(session); setDirtyDeleteFiles(detail.changed_files); return; } } } finally { setLoadingSessionId(null); } }, [loadingSessionId, onRefresh], ); const handleForceDelete = useCallback( async (session: Session) => { if (loadingSessionId === session.id) return; setLoadingSessionId(session.id); try { await deleteInstance( session.project_id, session.repository_id, session.id, true, ); setDirtyDeleteSession(null); setDirtyDeleteFiles([]); await onRefresh(); } catch { // ignore } finally { setLoadingSessionId(null); } }, [loadingSessionId, onRefresh], ); const handleRecreateTunnel = useCallback( async (session: Session) => { if (loadingSessionId === session.id) return; setLoadingSessionId(session.id); try { await recreateInstanceTunnel( session.project_id, session.repository_id, session.id, ); await onRefresh(); } catch (err) { const message = (err as { response?: { data?: { detail?: string } } })?.response?.data ?.detail || "Failed to recreate tunnel"; alert(message); } finally { setLoadingSessionId(null); } }, [loadingSessionId, onRefresh], ); const clearDirtyDelete = useCallback(() => { setDirtyDeleteSession(null); setDirtyDeleteFiles([]); }, []); return { loadingSessionId, dirtyDeleteSession, dirtyDeleteFiles, handleOpen, handleStart, handleStop, handleDelete, handleForceDelete, handleRecreateTunnel, clearDirtyDelete, }; }