"use client";
import { memo, useState, useCallback, useEffect, useRef } from "react";
import { IconBrandVscode, IconLoader2, IconAlertCircle } from "@tabler/icons-react";
import { useTheme } from "next-themes";
import { Button } from "@kandev/ui/button";
import { PanelRoot, PanelBody } from "./panel-primitives";
import { useAppStore } from "@/components/state-provider";
import { startVscode, getVscodeStatus, type VscodeStatus } from "@/lib/api/domains/vscode-api";
import { getBackendConfig } from "@/lib/config";
function VscodeProgress({
status,
message,
error,
onRetry,
}: {
status: VscodeStatus["status"];
message?: string;
error?: string;
onRetry: () => void;
}) {
if (status === "error") {
return (
Failed to start VS Code
{error &&
{error}
}
);
}
return (
Starting VS Code Server
{message &&
{message}
}
);
}
function VscodeIframe({ url }: { url: string }) {
const [loaded, setLoaded] = useState(false);
return (
);
}
function VscodeIdle() {
return (
VS Code Server
Waiting for an active session...
);
}
function VscodePanelBody({
status,
isRunning,
isProgress,
iframeUrl,
onRetry,
}: {
status: VscodeStatus;
isRunning: boolean;
isProgress: boolean;
iframeUrl: string;
onRetry: () => void;
}) {
if (isRunning) {
return ;
}
if (isProgress) {
return (
);
}
return ;
}
/** Build the base iframe URL from the VS Code proxy path. */
function buildBaseUrl(proxyPath: string): string {
const { apiBaseUrl } = getBackendConfig();
return `${apiBaseUrl}${proxyPath}`;
}
/** Manages auto-start, polling, and navigation for the VS Code panel. */
function useVscodeLifecycle() {
const activeSessionId = useAppStore((state) => state.tasks.activeSessionId);
const { resolvedTheme } = useTheme();
const [status, setStatus] = useState({ status: "stopped" });
const startedRef = useRef(false);
// Auto-start on mount or session change
useEffect(() => {
if (!activeSessionId) return;
startedRef.current = false;
let cancelled = false;
getVscodeStatus(activeSessionId)
.then((s) => {
if (cancelled) return;
setStatus(s);
if (s.status === "stopped" && !startedRef.current) {
startedRef.current = true;
const theme = resolvedTheme === "light" ? "light" : "dark";
startVscode(activeSessionId, theme)
.then((result) => {
if (!cancelled) setStatus(result);
})
.catch(() => {
if (!cancelled) setStatus({ status: "error", error: "Failed to start VS Code" });
});
}
})
.catch(() => {
if (!cancelled) setStatus({ status: "error", error: "Failed to get VS Code status" });
});
return () => {
cancelled = true;
};
}, [activeSessionId, resolvedTheme]);
// Poll for status while installing or starting
useEffect(() => {
if (!activeSessionId) return;
if (status.status !== "installing" && status.status !== "starting") return;
const interval = setInterval(() => {
getVscodeStatus(activeSessionId).then(setStatus);
}, 2000);
return () => clearInterval(interval);
}, [activeSessionId, status.status]);
const handleRetry = useCallback(() => {
if (!activeSessionId) return;
startedRef.current = true;
const theme = resolvedTheme === "light" ? "light" : "dark";
setStatus({ status: "installing", message: "Retrying..." });
startVscode(activeSessionId, theme)
.then(setStatus)
.catch(() => {
setStatus({ status: "error", error: "Failed to start VS Code" });
});
}, [activeSessionId, resolvedTheme]);
const iframeUrl = status.status === "running" && status.url ? buildBaseUrl(status.url) : null;
return {
status,
iframeUrl,
handleRetry,
};
}
type VscodePanelProps = {
panelId: string;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const VscodePanel = memo(function VscodePanel({ panelId }: VscodePanelProps) {
const { status, iframeUrl, handleRetry } = useVscodeLifecycle();
const isRunning = status.status === "running" && iframeUrl;
const isProgress =
status.status === "installing" || status.status === "starting" || status.status === "error";
return (
);
});