Some checks failed
Tests / test (push) Has been cancelled
按 U-Claw-海外化改造方案.md 与 范围决策记录.md 实施。这是 fork,不回上游: 海外版删掉的正是上游的中国市场默认值。 阶段 0 地基 - 下载源全部改国际:脚本/CI 61 处 + lockfile 880 条 npmmirror URL 归零 (lockfile 那 880 条是 npm 的 resolved 字段,脚本层参数化根本绕不过它) - 移除 install.ps1 里三个第三方 GitHub 加速代理,bundle 改直连 + SHA256 校验 (原来只检查"文件大于 1MB"就解压运行) - 技能内容与分发分离:skills/manifest.json 单一来源,install.sh 1170→658 行、 install.ps1 721→546 行,两者技能内容归零 实测原来是三份不一致:skills-cn 完整、install.sh 约 40%、install.ps1 约 17%, 且 7 个通用技能只有 U 盘版有 —— 一键安装的用户一个能用的技能都没有 - Node 版本三种(v22.14/16/22.1)统一,新建 NODE_VERSION 单一来源 - Config 页三份合一。portable/Config.html 用根相对路径调 API 却只从 file:// 打开, 保存功能已静默失效两个月;现缩为 120 行重定向壳 - 测试接入 CI(此前 node --test 无人运行,所有断言形同虚设) 阶段 1 双语可用 - 浏览器侧 i18n:JSON 为源、生成经典 script(file:// 下 fetch 本地 JSON 被拦) 语言跟盘走不跟机器走:启动器写 data/.openclaw/locale.js - 8 处硬编码 lang="zh-CN" 归零,data-i18n 覆盖 213 处,词条 en/zh 各 279 条 - B3 单框 Key:12 张模型卡 → 一个输入框,前缀识别 provider, 服务端 /api/test-key 发 1-token 请求实测,错误映射成人话 Key 填错到得知:从"直到对话失败"降到 ≤1 秒 - 区域格式 SG:DD/MM/YYYY、12 小时、S$、Asia/Singapore (ICU 在 en-SG 下把 SGD 渲染成裸 $,与美元无法区分,故自行拼 S$) - README 内容分叉而非翻译,§1.3 证据清单逐条清零 阶段 2 降门槛 - 启动逻辑上移 lib/start.mjs:Windows-Start.bat 220→28 行、 Mac-Start.command 235→33 行 修掉 Mac 侧两个 bug:控制台端口硬编码 18788(回落时打开死页)、 微信插件从未在 Mac 上安装 - U 盘根目录 23 → 3 个可点文件,其余进 advanced/ - 首启向导:语言 → 用途(7 角色,manifest 驱动)→ 密钥,答过不再问 - 三档界面,Simple 档隐藏一切技术名词 - 自动自愈:启动失败先自查自修,修不好导出脱敏诊断包 (Doctor 从"用户要知道去点的工具"变成后台机制) 阶段 3 技能库 - 19 个英文技能,planned 归零。sg-weather / sg-transport 的端点均实测过 - SkillHub 从 56 张手写第三方卡片改为 manifest 生成:703→125 行,中文归零 其他 - origin.json 收拢所有运行时地址,tests/origin.test.mjs 保证迁移不会漏 - portable/ 下用户可见中文归零(由断言保证) - 82 项测试 未验证(本机无 Windows / 无 pwsh): - install.ps1、setup.ps1 约 210 行改动从未经 PowerShell 解析器 - 完整启动路径仅在假 node + 假 openclaw 上冒烟 - 8 个 .bat 的盘根推导仅静态断言 详见 U盘实测清单.md 受阻: - 隐藏黑窗口 —— 需代码签名证书(.vbs 已被 Windows 弃用,替代方案都要签名) - 场景卡 —— OpenClaw 上游 Dashboard 无预填 prompt 接口 - 官网 36 条 —— 上游 2026-04-14 拆到私有仓库,无权限
201 lines
7.1 KiB
HTML
201 lines
7.1 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title data-i18n="loading.title">Starting U-Claw</title>
|
||
<!--
|
||
启动首屏(移植自 v2 u-clawx 4.0 的 splash 思路)。
|
||
双击启动脚本后立刻打开本页,给用户即时反馈,消除"黑窗假死"体感。
|
||
本页每秒自查 gateway 是否就绪(fetch /ready)。
|
||
|
||
就绪后【不自动跳转】,而是停在选择页,给两个大按钮:
|
||
🚀 进入 Dashboard(开始聊天)
|
||
⚙️ 配置 / 改模型(打开配置中心)
|
||
让用户自己决定去聊天还是改配置——避免"配过模型就被冲进 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; }
|
||
|
||
/* —— 就绪后的选择页 —— */
|
||
.ready-wrap { display: none; }
|
||
body.ready .loading-wrap { display: none; }
|
||
body.ready .ready-wrap { display: block; }
|
||
.actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
margin-top: 26px;
|
||
}
|
||
.btn {
|
||
display: block;
|
||
text-decoration: none;
|
||
color: #fff;
|
||
border-radius: 14px;
|
||
padding: 20px 22px;
|
||
font-size: 1.15em;
|
||
font-weight: 600;
|
||
line-height: 1.35;
|
||
cursor: pointer;
|
||
border: 2px solid rgba(255,255,255,.35);
|
||
background: rgba(255,255,255,.12);
|
||
transition: transform .12s ease, background .2s ease, box-shadow .2s ease;
|
||
}
|
||
.btn:hover { transform: translateY(-2px); background: rgba(255,255,255,.22); box-shadow: 0 8px 24px rgba(0,0,0,.18); }
|
||
.btn:active { transform: translateY(0); }
|
||
.btn .sub { display: block; font-size: .68em; font-weight: 400; opacity: .8; margin-top: 4px; }
|
||
.btn-primary { background: #ff7a45; border-color: #ff7a45; }
|
||
.btn-primary:hover { background: #ff6a30; }
|
||
.foot { margin-top: 24px; font-size: .82em; opacity: .65; line-height: 1.6; }
|
||
</style>
|
||
<script src="../data/.openclaw/locale.js"></script>
|
||
<script src="i18n/messages.js"></script>
|
||
<script src="i18n/i18n.js"></script>
|
||
</head>
|
||
<body>
|
||
<div class="box">
|
||
|
||
<!-- Starting -->
|
||
<div class="loading-wrap">
|
||
<div class="logo">🦞</div>
|
||
<h1 data-i18n="loading.starting">Starting U-Claw…</h1>
|
||
<div class="tip" data-i18n="loading.first_run">First run from a USB drive unpacks the bundled components. Give it a moment — you will be told when it is ready.</div>
|
||
<div class="spinner"></div>
|
||
<div class="bar"><i></i></div>
|
||
<div class="status" id="status" data-i18n="loading.waiting_gateway">Waiting for U-Claw to come up…</div>
|
||
<div class="elapsed" id="elapsed"></div>
|
||
<div class="note">
|
||
<span data-i18n="loading.slow_drive_note">A slow drive usually takes 30–90 seconds. If nothing happens for a long time, open Settings:</span>
|
||
<a class="manual" id="manual" href="#" data-i18n="loading.open_settings">⚙️ Settings</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Ready: let the user choose -->
|
||
<div class="ready-wrap">
|
||
<div class="logo">🦞</div>
|
||
<h1 data-i18n="loading.ready">U-Claw is ready</h1>
|
||
<div class="tip" data-i18n="loading.choose_next">Pick what to do next — start chatting, or set up your model and channels first.</div>
|
||
<div class="actions">
|
||
<a class="btn btn-primary" id="goDash"><span data-i18n="loading.open_dashboard">🚀 Start chatting</span>
|
||
</a>
|
||
<a class="btn" id="goConfig"><span data-i18n="loading.open_settings">⚙️ Settings</span>
|
||
<span class="sub" data-i18n="loading.settings_hint">Add or change your API key, and connect chat apps.</span>
|
||
</a>
|
||
</div>
|
||
<div class="foot" data-i18n="loading.no_model_hint">No model set up yet? Open Settings first to add a key.</div>
|
||
</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 = configCenter;
|
||
document.getElementById('goDash').href = dashboard;
|
||
document.getElementById('goConfig').href = configCenter;
|
||
|
||
var startedAt = Date.now();
|
||
var ready = false;
|
||
|
||
function fmt(ms) {
|
||
var s = Math.floor(ms / 1000);
|
||
return window.UClawI18n ? window.UClawI18n.t('loading.elapsed', { seconds: s }) : s + 's';
|
||
}
|
||
|
||
function tickElapsed() {
|
||
if (ready) return;
|
||
document.getElementById('elapsed').textContent = fmt(Date.now() - startedAt);
|
||
}
|
||
setInterval(tickElapsed, 1000);
|
||
tickElapsed();
|
||
|
||
function showChoice() {
|
||
if (ready) return;
|
||
ready = true;
|
||
document.body.classList.add('ready');
|
||
}
|
||
|
||
function poll() {
|
||
if (ready) return;
|
||
// /ready 就绪即显示选择页。no-cors 下拿不到 status,但 fetch 成功 resolve 就说明端口活了。
|
||
fetch(base + '/ready', { mode: 'no-cors', cache: 'no-store' })
|
||
.then(function () { showChoice(); })
|
||
.catch(function () {
|
||
// 端口还没起,继续等
|
||
setTimeout(poll, 1000);
|
||
});
|
||
}
|
||
poll();
|
||
|
||
// 兜底:5 分钟还没起来,提示去控制台
|
||
setTimeout(function () {
|
||
if (!ready) {
|
||
var label = window.UClawI18n ? window.UClawI18n.t('loading.open_settings') : 'Settings';
|
||
var slow = window.UClawI18n ? window.UClawI18n.t('loading.taking_long') : 'This is taking longer than usual. You can open Settings while you wait:';
|
||
document.getElementById('status').innerHTML =
|
||
slow + ' <a class="manual" href="' + configCenter + '">' + label + '</a>';
|
||
}
|
||
}, 300000);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|