// ─── Create instance screen ─── function CreateScreen({ setScreen, toast, instance, setInstance }) { const [loading, setLoading] = React.useState(false); async function go() { setLoading(true); try { const data = await apiRequest('/api/instance', { method: 'POST' }); setInstance(data); toast('云端电脑创建中...'); setScreen('booting'); } catch (ex) { if (ex.code === 'err_instance_exists') { try { const data = await apiRequest('/api/instance'); setInstance(data); setScreen(data.availability === 'ready' ? 'dashboard' : 'booting'); } catch { /* ignore */ } } else { toast(errText(ex.code || ex.message), 'error'); } } finally { setLoading(false); } } return (
provision.sh
READY

创建你的 OpenClaw 云端电脑

{'>'} 点击按钮,我们将启动一个专属容器
{'>'} 预计耗时 1~2 分钟

独立容器 专属工作空间
{'// 使用邀请制 · 无需信用卡'}
); } window.CreateScreen = CreateScreen; // ─── Booting screen ─── Poll /api/instance until availability === 'ready' function BootingScreen({ setScreen, instance, setInstance, toast }) { // Progress is approximate — derived from elapsed time (real provisioning takes 1-2 min). const [progress, setProgress] = React.useState(12); const [step, setStep] = React.useState(1); const [logs, setLogs] = React.useState([ { t: '00:00', msg: 'requesting container image', ok: true }, ]); React.useEffect(() => { let pollToken = 0; let progressIv = null; const myToken = ++pollToken; // Fake progress bar (real boot status is binary: loading vs ready) progressIv = setInterval(() => { setProgress(p => Math.min(95, p + 2 + Math.random() * 3)); }, 700); async function poll() { if (myToken !== pollToken) return; try { const data = await apiRequest('/api/instance'); if (myToken !== pollToken) return; setInstance(data); // Step progression based on status if (data.status === 'running' && step < 2) setStep(2); if (data.health_status === 'healthy' && step < 3) setStep(3); // Add log lines setLogs(l => { const last = l[l.length - 1]; const msg = `status=${data.status}${data.health_status ? ' health=' + data.health_status : ''}`; if (last && last.msg === msg) return l; const t = String(l.length * 3).padStart(2, '0'); return [...l, { t: `00:${t}`, msg, ok: data.status !== 'error' }]; }); if (data.availability === 'ready') { setProgress(100); clearInterval(progressIv); toast('云端电脑已就绪'); setTimeout(() => setScreen('dashboard'), 400); return; } if (data.status === 'error') { clearInterval(progressIv); toast('启动失败', 'error'); return; } } catch (ex) { if (ex.code === 'err_instance_not_found') { clearInterval(progressIv); setScreen('create'); return; } } setTimeout(poll, 2400); } setTimeout(poll, 800); return () => { pollToken++; clearInterval(progressIv); }; }, []); // eslint-disable-line const availability = instance?.availability || 'loading'; return (
bootstrap.log
BOOTING · {Math.floor(progress)}%
账号已登录
= 1 ? 'done' : '')}>
容器已创建
= 2 ? 'done' : '')}/>
= 2 ? (step >= 3 ? 'done' : 'active') : '')}>
{step >= 3 ? '✓' : '·'}
系统初始化
= 3 ? 'done' : '')}/>
{availability === 'ready' ? '✓' : '·'}
健康检查
STATUS: {instance?.status || 'provisioning'} {Math.floor(progress)}% · ETA {Math.max(5, 90 - Math.floor(progress * 0.9))}s
{logs.map((l, i) => (
[{l.t}] {l.ok ? '✓' : '✗'} {l.msg}
))}
[00:{logs.length*3}] {'waiting for ready...'}
); } window.BootingScreen = BootingScreen;