"use client"; import { useRef, useState } from "react"; import { loadSavedControllers, normalizeControllerUrl, saveSavedControllers, type SavedController, } from "@/lib/api/controllers"; import { setApiKey, setStoredBackendUrl } from "@/lib/api/connection"; export interface ControllerDeployBridge { start(options: { mode?: "ssh" | "local"; host?: string; port?: number; }): Promise<{ ok: boolean; url?: string; apiKey?: string; error?: string }>; onLog(listener: (line: string) => void): () => void; } const getDeployBridge = (): ControllerDeployBridge | null => { if (typeof window === "undefined") return null; return ( (window as unknown as { localStudioDesktop?: { controllerDeploy?: ControllerDeployBridge } }) .localStudioDesktop?.controllerDeploy ?? null ); }; /** * Save an installed controller and make it the active one, the same writes the * settings page performs: the saved list, the stored backend URL, the runtime * key, and the server-side settings cookie. */ export const adoptDeployedController = async (controller: SavedController): Promise => { const url = normalizeControllerUrl(controller.url); if (!url) return; const existing = loadSavedControllers(); if (!existing.some((entry) => normalizeControllerUrl(entry.url) === url)) { saveSavedControllers([...existing, { ...controller, url }]); } setApiKey(controller.apiKey ?? ""); setStoredBackendUrl(url); try { await fetch("/api/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ backendUrl: url, apiKey: controller.apiKey ?? "" }), }); } catch { // The client-side writes above already point the app at the controller. } if (typeof window !== "undefined") window.dispatchEvent(new Event("storage")); }; /** * One in-flight deploy, shared by the settings panel and the setup gate: * streamed installer lines, terminal error or success, and the resulting * controller handed to the caller. */ export function useControllerDeploy() { const bridge = getDeployBridge(); const [running, setRunning] = useState(false); const [lines, setLines] = useState([]); const [error, setError] = useState(null); const [done, setDone] = useState(null); const unsubscribeRef = useRef<(() => void) | null>(null); const run = async ( options: { mode?: "ssh" | "local"; host?: string }, name: string, ): Promise => { if (!bridge || running) return null; setRunning(true); setError(null); setDone(null); setLines([]); unsubscribeRef.current?.(); unsubscribeRef.current = bridge.onLog((line) => setLines((current) => [...current.slice(-40), line]), ); try { const result = await bridge.start(options); if (result.ok && result.url) { setDone(`Controller running at ${result.url} — added to your list.`); return { url: result.url, apiKey: result.apiKey, name }; } setError(result.error ?? "Deploy failed"); return null; } catch (err) { setError(err instanceof Error ? err.message : "Deploy failed"); return null; } finally { unsubscribeRef.current?.(); unsubscribeRef.current = null; setRunning(false); } }; return { available: Boolean(bridge), running, lines, error, done, run }; }