perf(portable): U盘启动加速 — 缓存搬本机 + 启动首屏 + 首轮预热

便携版从 U 盘启动慢,瓶颈在 U 盘随机小写 IO + 首屏无反馈 + 首轮冷启动。
移植 v2 u-clawx 4.0 的 4 个可在纯脚本层复刻的手段(均零依赖、静默失败):

- portable-cache.mjs: 把浏览器 user-data(几百 MB 随机小写)+ V8 编译缓存
  从 U 盘搬到本机 SSD(win junction / mac symlink 重定向 .openclaw/browser,
  NODE_COMPILE_CACHE 落本机)。UUID 隔离让换盘符仍命中同一份缓存。
  业务数据(openclaw.json/memory/账号)仍留 U 盘,便携性不变。
- loading.html: 双击即弹的启动首屏,自轮询 /ready,gateway 真就绪后自动跳
  Dashboard——天然规避"gateway 没起就开 Dashboard 拒连"(issue #46/#48)。
- prewarm.mjs: gateway 首轮预热,后台静默唤醒 config/model 子系统。
- Windows-Start.bat: 等 config-server 改写死 timeout 为动态轮询 18788。
- wait-gateway.bat 退居兜底(首屏页负责主跳转)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hfshfg
2026-06-17 16:38:05 +08:00
parent 29238fc423
commit f715aef9c9
7 changed files with 520 additions and 29 deletions

138
portable/lib/loading.html Normal file
View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-Claw 正在启动…</title>
<!--
启动首屏(移植自 v2 u-clawx 4.0 的 splash 思路)。
双击启动脚本后立刻打开本页,给用户即时反馈,消除"黑窗假死"体感。
本页每秒自查 gateway 是否就绪fetch /ready就绪后自动跳转 Dashboard。
端口通过 URL 查询参数传入loading.html?port=18789
(启动脚本打开时拼好。默认 18789。
-->
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.box {
text-align: center;
max-width: 460px;
padding: 40px;
}
.logo { font-size: 64px; line-height: 1; margin-bottom: 18px; }
h1 { font-size: 1.7em; font-weight: 600; margin-bottom: 8px; }
.tip { opacity: .85; font-size: 1.02em; line-height: 1.6; margin-bottom: 28px; }
.spinner {
width: 46px; height: 46px;
margin: 0 auto 24px;
border: 4px solid rgba(255,255,255,.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin .9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.bar {
height: 6px; width: 100%;
background: rgba(255,255,255,.2);
border-radius: 999px;
overflow: hidden;
margin-bottom: 14px;
}
.bar > i {
display: block; height: 100%; width: 30%;
background: #fff; border-radius: 999px;
animation: slide 1.6s ease-in-out infinite;
}
@keyframes slide {
0% { margin-left: -30%; }
100% { margin-left: 100%; }
}
.status { font-size: .95em; opacity: .9; min-height: 1.4em; }
.elapsed { font-size: .82em; opacity: .6; margin-top: 6px; }
.note {
margin-top: 30px; font-size: .82em; opacity: .65; line-height: 1.6;
}
a.manual { color: #fff; text-decoration: underline; opacity: .9; }
</style>
</head>
<body>
<div class="box">
<div class="logo">🦞</div>
<h1>U-Claw 正在启动</h1>
<div class="tip">首次从 U 盘启动需要展开内置组件,<br>稍等片刻,就绪后会自动打开。</div>
<div class="spinner"></div>
<div class="bar"><i></i></div>
<div class="status" id="status">正在等待 OpenClaw 网关…</div>
<div class="elapsed" id="elapsed"></div>
<div class="note">
慢 U 盘首次启动通常 3090 秒。<br>
如果迟迟没反应,可手动打开
<a class="manual" id="manual" href="#">控制台</a>
</div>
</div>
<script>
(function () {
var params = new URLSearchParams(location.search);
var port = parseInt(params.get('port'), 10) || 18789;
var token = params.get('token') || 'uclaw';
var base = 'http://127.0.0.1:' + port;
var dashboard = base + '/#token=' + encodeURIComponent(token);
var configCenter = 'http://127.0.0.1:18788/';
document.getElementById('manual').href = dashboard;
var startedAt = Date.now();
var redirected = false;
function fmt(ms) {
var s = Math.floor(ms / 1000);
return '已等待 ' + s + ' 秒';
}
function tickElapsed() {
document.getElementById('elapsed').textContent = fmt(Date.now() - startedAt);
}
setInterval(tickElapsed, 1000);
tickElapsed();
function goDashboard() {
if (redirected) return;
redirected = true;
document.getElementById('status').textContent = '网关已就绪,正在打开控制台…';
setTimeout(function () { location.replace(dashboard); }, 400);
}
function poll() {
if (redirected) return;
// /ready 就绪即跳。no-cors 下拿不到 status但 fetch 成功 resolve 就说明端口活了。
fetch(base + '/ready', { mode: 'no-cors', cache: 'no-store' })
.then(function () { goDashboard(); })
.catch(function () {
// 端口还没起,继续等
setTimeout(poll, 1000);
});
}
poll();
// 兜底5 分钟还没起来,提示去控制台
setTimeout(function () {
if (!redirected) {
document.getElementById('status').innerHTML =
'启动较慢,可先打开 <a class="manual" href="' + configCenter + '">配置中心</a> 检查';
}
}, 300000);
})();
</script>
</body>
</html>