import { useCallback, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Icon } from "./icon"; import type { ToolInstance } from "../types/tool-instance"; import type { ToolType } from "../types/tool-type"; import { checkInstanceHealth, createInstance, deleteInstance, listInstances, recreateInstanceTunnel, restartInstance, startInstance, stopInstance, } from "../api/sessions"; import styles from "./features/session/InstanceList.module.css"; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000"; interface InstanceListProps { projectId: string; repoId: string; toolTypes: ToolType[]; } export const InstanceList = ({ projectId, repoId, toolTypes, }: InstanceListProps) => { const navigate = useNavigate(); const [instances, setInstances] = useState([]); const [loading, setLoading] = useState(false); const [showCreate, setShowCreate] = useState(false); const [selectedToolType, setSelectedToolType] = useState(""); const [displayName, setDisplayName] = useState(""); const [error, setError] = useState(null); // Stop confirmation const [stopConfirmId, setStopConfirmId] = useState(null); // Health check state const [healthStatus, setHealthStatus] = useState< Record >({}); const loadInstances = useCallback(async () => { setLoading(true); try { const data = await listInstances(projectId, repoId); setInstances(data); } catch { setError("Failed to load instances"); } finally { setLoading(false); } }, [projectId, repoId]); useEffect(() => { void loadInstances(); }, [loadInstances]); // Health check polling useEffect(() => { const runningInstances = instances.filter( (i) => i.status === "running" && i.url?.startsWith("http"), ); if (runningInstances.length === 0) return; const checkHealth = async () => { for (const instance of runningInstances) { try { const health = await checkInstanceHealth( projectId, repoId, instance.id, ); setHealthStatus((prev) => ({ ...prev, [instance.id]: { healthy: health.healthy, lastCheck: Date.now() }, })); } catch { setHealthStatus((prev) => ({ ...prev, [instance.id]: { healthy: false, lastCheck: Date.now() }, })); } } }; // Check immediately void checkHealth(); // Then every 30 seconds const interval = setInterval(() => void checkHealth(), 30000); return () => clearInterval(interval); }, [instances, projectId, repoId]); const handleCreate = async () => { if (!selectedToolType) return; setError(null); try { await createInstance( projectId, repoId, selectedToolType, displayName || undefined, ); setShowCreate(false); setSelectedToolType(""); setDisplayName(""); await loadInstances(); } catch { setError("Failed to create instance"); } }; const handleStart = async (instanceId: string) => { try { await startInstance(projectId, repoId, instanceId); await loadInstances(); } catch { setError("Failed to start instance"); } }; const handleStop = async (instanceId: string) => { try { await stopInstance(projectId, repoId, instanceId); setStopConfirmId(null); await loadInstances(); } catch { setError("Failed to stop instance"); } }; const handleRestart = async (instanceId: string) => { try { await restartInstance(projectId, repoId, instanceId); await loadInstances(); } catch { setError("Failed to restart instance"); } }; const handleDelete = async (instanceId: string) => { if (!confirm("Are you sure you want to delete this instance?")) return; try { await deleteInstance(projectId, repoId, instanceId); // Update state immediately instead of reloading setInstances((prev) => prev.filter((i) => i.id !== instanceId)); } catch { setError("Failed to delete instance"); } }; const handleRecreateTunnel = async (instanceId: string) => { try { await recreateInstanceTunnel(projectId, repoId, instanceId); await loadInstances(); } catch { setError("Failed to recreate tunnel"); } }; const getStatusColor = (status: string) => { switch (status) { case "running": return "var(--success)"; case "error": return "var(--danger)"; case "pending": case "building": return "var(--warning)"; default: return "var(--muted)"; } }; const isTunnelUnhealthy = (instance: ToolInstance) => { if (instance.status !== "running") return false; if (!instance.url?.startsWith("http")) return false; const health = healthStatus[instance.id]; if (!health) return false; return !health.healthy; }; return (

Tool Instances

{error &&
{error}
} {loading ? (

Loading instances...

) : instances.length === 0 ? (

No instances yet. Launch a tool to get started.

) : (
{instances.map((instance) => (
{instance.display_name || instance.tool_type_name || "Unnamed Instance"}
{instance.status} {isTunnelUnhealthy(instance) && ( tunnel error )}
{instance.status === "running" && instance.url && instance.tool_type_interfaces.includes("web") && ( <> Open {isTunnelUnhealthy(instance) && ( )} )} {instance.status === "running" && instance.tool_type_interfaces.includes("terminal") && ( )} {instance.status !== "running" && ( )} {instance.status === "running" && ( <> {stopConfirmId === instance.id ? (
Stop?
) : ( )} )}
))}
)} {showCreate && (

Launch Tool

)}
); };