File size: 8,551 Bytes
5a0b87c 179e028 5a0b87c b4d7558 5a0b87c b36da9a 15b7bb6 b4d7558 5a0b87c 179e028 15b7bb6 5a0b87c 179e028 b36da9a 179e028 b36da9a 179e028 6e44748 5a0b87c 179e028 15b7bb6 179e028 15b7bb6 179e028 5a0b87c 179e028 5a0b87c 15b7bb6 179e028 b36da9a 179e028 15b7bb6 179e028 6e44748 179e028 15b7bb6 5a0b87c 15b7bb6 179e028 15b7bb6 6e44748 15b7bb6 5a0b87c 2e54937 b36da9a 2e54937 dbba276 2e54937 b36da9a 2e54937 6e44748 b36da9a 6e44748 b36da9a 2e54937 5a0b87c 6e44748 5a0b87c 6e44748 5a0b87c 15b7bb6 5a0b87c 6e44748 15b7bb6 5a0b87c 15b7bb6 5a0b87c 15b7bb6 b4d7558 5a0b87c b4d7558 5a0b87c 15b7bb6 6e44748 5a0b87c 15b7bb6 5a0b87c b4d7558 5a0b87c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | "use client";
import { useEffect, useState } from "react";
import { Loader2, ServerCrash, Smartphone } from "lucide-react";
import { EmulatorPane } from "./EmulatorPane";
import { useEmulator } from "@/hooks/useEmulator";
interface VSCodeFrameProps {
workspaceId: string;
}
export function VSCodeFrame({ workspaceId }: VSCodeFrameProps) {
const [status, setStatus] = useState<"loading" | "ready" | "error">("loading");
const [androidPort, setAndroidPort] = useState<string | null>(null);
const [appetizeUrl, setAppetizeUrl] = useState<string | null>(null);
const [buildLogs, setBuildLogs] = useState<string[]>([]);
const {
isOpen,
platform,
refreshKey,
isLoading,
setIsOpen,
toggleOpen,
changePlatform,
refreshIframe,
} = useEmulator("android");
useEffect(() => {
let isMounted = true;
let events: EventSource | null = null;
const checkStatus = async () => {
try {
const res = await fetch(`/api/workspace/status?id=${workspaceId}`);
const data = await res.json();
if (data.ready && isMounted) {
setStatus("ready");
return true;
}
} catch (e) {
console.error("Status probe failed:", e);
}
return false;
};
const init = async () => {
const isReady = await checkStatus();
if (isReady || !isMounted) return;
events = new EventSource(`/api/workspace/stream?id=${workspaceId}&withAndroid=true`);
const handleLog = (e: Event) => {
if (!isMounted) return;
const me = e as MessageEvent;
try {
const msg = JSON.parse(me.data);
setBuildLogs((prev) => [...prev, msg]);
} catch {
setBuildLogs((prev) => [...prev, me.data]);
}
};
const handleReady = (e: Event) => {
if (!isMounted) return;
const me = e as MessageEvent;
try {
const data = JSON.parse(me.data);
if (data.success) {
if (data.appetizeUrl) setAppetizeUrl(data.appetizeUrl);
if (data.androidPort) {
setAndroidPort(String(data.androidPort));
setIsOpen(true);
}
setStatus("ready"); // Instant ready instead of 1.5s delay
} else {
setStatus("error");
}
} catch (e: unknown) {
console.error(`[WATCHDOG:ERR] ${e instanceof Error ? e.message : String(e)}`);
}
if (events) {
events.close();
}
};
const handleError = () => {
if (isMounted) setStatus("error");
if (events) {
events.close();
}
};
events.addEventListener("log", handleLog);
events.addEventListener("ready", handleReady);
events.addEventListener("error", handleError);
};
void init();
return () => {
isMounted = false;
if (events) {
events.close();
events = null;
}
};
}, [workspaceId, setIsOpen]);
if (status === "loading") {
return (
<div className="flex flex-col items-center pt-24 w-full h-full bg-[#050505] text-(--text-muted) space-y-6 overflow-hidden relative">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(57,211,83,0.03)_0%,transparent_70%)] pointer-events-none" />
<div className="z-10 flex flex-col items-center gap-4">
<Loader2 size={32} className="animate-spin text-(--accent)" />
<p className="text-sm font-bold tracking-widest uppercase animate-pulse text-(--accent)">Syncing Environment...</p>
</div>
<div className="w-full max-w-2xl bg-[#0a0a0a] rounded-xl p-4 font-mono text-[11px] overflow-y-auto h-72 z-10 border border-[#222] shadow-[inset_0_0_40px_rgba(0,0,0,0.8)] relative group">
<div className="absolute top-4 right-4 text-[#333] group-hover:text-zinc-600 transition-colors pointer-events-none uppercase text-[10px] tracking-widest font-bold flex items-center gap-2">
<Smartphone size={12} />
CodeVerse Studio Engine
</div>
{buildLogs.map((log, i) => (
<div key={i} className="mb-0.5 leading-relaxed tracking-tight text-zinc-400">
<span className="text-(--accent) mr-2 opacity-50">❯</span>
{log}
</div>
))}
<div className="animate-pulse text-(--accent) mt-2 font-bold">_</div>
</div>
<div className="text-[10px] opacity-40 mt-4 italic z-10 font-mono tracking-tighter">
[ORCHESTRATOR] Binding LSPs, mapping virtual volumes, and hydrating Nix profile.
</div>
</div>
);
}
if (status === "error") {
return (
<div className="flex flex-col items-center justify-center w-full h-full bg-(--bg) text-(--error) space-y-4 p-8 text-center">
<div className="p-4 bg-(--error)/10 rounded-full mb-2">
<ServerCrash size={48} className="text-(--error)" />
</div>
<h3 className="text-lg font-bold text-(--text)">Deployment Engine Failure</h3>
<p className="text-sm opacity-80 max-w-md leading-relaxed">
The orchestration layer failed to provision workspace <span className="font-mono text-xs bg-(--border) px-1 rounded">{workspaceId}</span>.
This usually occurs due to Docker socket timeouts or resource exhaustion in limited cloud environments.
</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-6 py-2 bg-(--error) text-white rounded-lg text-sm font-medium hover:opacity-90 transition-all"
>
Retry Sequence
</button>
</div>
);
}
const targetUrl = `/workspace/${encodeURIComponent(workspaceId)}/`;
return (
<div className="w-full h-full flex overflow-hidden bg-(--bg)">
<div className={`relative h-full transition-all duration-500 ease-in-out ${isOpen ? 'w-[60%]' : 'w-full'}`}>
<iframe
src={targetUrl}
className="w-full h-full border-0 bg-(--bg)"
allow="clipboard-read; clipboard-write; display-capture"
title="CodeVerse Remote Engine"
/>
{!isOpen && (
<button
onClick={toggleOpen}
className="absolute bottom-6 right-6 p-4 bg-(--accent) text-white rounded-full shadow-2xl hover:opacity-90 hover:scale-110 active:scale-95 transition-all z-50 flex items-center justify-center border border-white/20"
title="Open Built-in Emulators"
>
<Smartphone size={22} />
</button>
)}
</div>
{/* Emulator Side Panel */}
{isOpen && (
<div className="w-[40%] h-full flex flex-col min-w-[360px] border-l border-(--border-subtle) bg-(--bg-2) animate-in slide-in-from-right duration-300">
<EmulatorPane
platform={platform}
setPlatform={changePlatform}
refreshKey={refreshKey}
isLoading={isLoading}
onRefresh={refreshIframe}
onClose={() => setIsOpen(false)}
androidPort={androidPort}
appetizeUrl={appetizeUrl}
workspaceId={workspaceId}
/>
</div>
)}
</div>
);
}
|