fix(portable): keep config center visible on startup
This commit is contained in:
@@ -168,7 +168,7 @@ input:focus { border-color: #ff6b35; }
|
||||
<span class="check">✓</span>
|
||||
<h4>虾盘云 <span class="tag hot">首选</span><span class="tag">中转站</span></h4>
|
||||
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
||||
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank">→ 注册并获取 API Key</a>
|
||||
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank" data-action-label="注册 / 充值 / 获取 API Key">→ 注册 / 充值 / 获取 API Key</a>
|
||||
</div>
|
||||
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-chat">
|
||||
<span class="check">✓</span>
|
||||
@@ -255,7 +255,7 @@ input:focus { border-color: #ff6b35; }
|
||||
<p class="desc" id="step2desc">请填写对应模型的 API Key</p>
|
||||
|
||||
<div class="api-info" id="buyInfo">
|
||||
<h4>📌 如何获取 API Key?</h4>
|
||||
<h4>📌 获取 / 充值 API Key</h4>
|
||||
<div class="provider-links" id="providerLinks"></div>
|
||||
</div>
|
||||
|
||||
@@ -477,7 +477,7 @@ function setupStep2() {
|
||||
linksDiv.innerHTML = '';
|
||||
if (buyLink) {
|
||||
linksDiv.innerHTML = `
|
||||
<a href="${buyLink.href}" target="_blank">🔑 ${name} — 获取 API Key</a>
|
||||
<a href="${buyLink.href}" target="_blank">🔑 ${name} — ${buyLink.dataset.actionLabel || '获取 API Key'}</a>
|
||||
`;
|
||||
}
|
||||
// Add common alternatives
|
||||
@@ -498,7 +498,7 @@ function togglePw() {
|
||||
function buildOpenClawConfig(provider, base, model, apiKey) {
|
||||
// Base config that OpenClaw expects
|
||||
const baseConfig = {
|
||||
gateway: { auth: { mode: 'token', token: 'uclaw', scopes: ['operator.admin'] } },
|
||||
gateway: { auth: { mode: 'token', token: 'uclaw', scopes: ['operator.admin'] }, remote: { token: 'uclaw' } },
|
||||
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
||||
meta: { lastTouchedVersion: '2026.3.13', lastTouchedAt: new Date().toISOString() }
|
||||
};
|
||||
|
||||
@@ -166,35 +166,23 @@ OPENCLAW_MJS="$CORE_DIR/node_modules/openclaw/openclaw.mjs"
|
||||
"$NODE_BIN" "$OPENCLAW_MJS" gateway run --allow-unconfigured --force --port $PORT &
|
||||
GW_PID=$!
|
||||
|
||||
# ---- 11. 是否已配置模型?openclaw.json 含 "providers" 即视为已配置 (issue #24) ----
|
||||
# 已配置:只开 Dashboard,不再每次弹配置页。未配置(首次):开 Config Center 引导填 Key。
|
||||
MODEL_CONFIGURED=0
|
||||
if grep -q '"providers"' "$CONFIG_FILE" 2>/dev/null; then
|
||||
MODEL_CONFIGURED=1
|
||||
fi
|
||||
|
||||
# ---- 12. 立刻打开"启动首屏",给用户即时反馈(移植自 4.0 splash)----
|
||||
# 首屏 loading.html 自己轮询 /ready,gateway 真就绪后自动跳 Dashboard——天然解决
|
||||
# "gateway 没起就开 Dashboard 拒连"的问题(同 Windows issue #46/#48)。
|
||||
# ---- 11. 立刻打开"启动首屏",给用户即时反馈(移植自 4.0 splash)----
|
||||
# 首屏 loading.html 自己轮询 /ready,就绪后停在选择页,不再自动冲进 Dashboard。
|
||||
echo -e " ${YELLOW}首次启动需准备运行环境,约 30-90 秒,请稍候...${NC}"
|
||||
# 用 file:// URL 确保 query string(?port=)能传给浏览器;裸路径 open 会把整串当文件名。
|
||||
open "file://$UCLAW_DIR/lib/loading.html?port=$PORT&token=uclaw" 2>/dev/null || true
|
||||
# 首次未配置:再开 Config Center 引导填 Key
|
||||
if [ "$MODEL_CONFIGURED" != "1" ]; then
|
||||
# 每次都打开 Config Center,方便改模型、充值/获取 Key、连接微信等渠道。
|
||||
open "http://127.0.0.1:18788/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ---- 12b. gateway 首轮预热(后台、静默、非阻塞)----
|
||||
# ---- 11b. gateway 首轮预热(后台、静默、非阻塞)----
|
||||
# 就绪后先唤醒 config/model 子系统,用户首次点发送时不再等。移植自 4.0 first-turn-prewarm。
|
||||
"$NODE_BIN" "$UCLAW_DIR/lib/prewarm.mjs" "$PORT" uclaw >/dev/null 2>&1 &
|
||||
|
||||
# ---- 12c. 兜底:万一首屏页的 file:// fetch 被浏览器拦,仍轮询端口后开 Dashboard ----
|
||||
# ---- 11c. 兜底:万一首屏页的 file:// fetch 被浏览器拦,仍静默轮询端口 ----
|
||||
# 慢盘首启可达 90s+,轮询上限覆盖这段。最多 ~3 分钟(180×1s)。
|
||||
(
|
||||
for i in $(seq 1 180); do
|
||||
if curl -s -o /dev/null "http://127.0.0.1:$PORT/" 2>/dev/null; then
|
||||
# 首屏页通常已自己跳转;这里仅作兜底,open 同一 URL 浏览器会复用已有标签。
|
||||
[ "$MODEL_CONFIGURED" = "1" ] && open "http://127.0.0.1:$PORT/#token=uclaw" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
@@ -49,11 +49,11 @@ if not exist "%DATA_DIR%\memory" mkdir "%DATA_DIR%\memory"
|
||||
if not exist "%DATA_DIR%\backups" mkdir "%DATA_DIR%\backups"
|
||||
if not exist "%DATA_DIR%\logs" mkdir "%DATA_DIR%\logs"
|
||||
|
||||
REM ── 加速:把"重 IO、可重建"的缓存从 U 盘搬到本机硬盘 ──────────────────
|
||||
REM portable-cache.mjs 算出本机缓存目录(%LOCALAPPDATA%\U-Claw\slot,UUID 隔离,
|
||||
REM 换盘符仍复用),并把 .openclaw\browser 做成 junction 指向本机盘。
|
||||
REM 浏览器 user-data(几百 MB 随机小写)和 V8 编译缓存因此落本机 SSD,不再拖慢 U 盘。
|
||||
REM 静默失败:取不到就跳过,缓存留 U 盘,照常启动。
|
||||
REM Startup cache acceleration.
|
||||
REM portable-cache.mjs chooses a local-disk cache slot and redirects
|
||||
REM .openclaw\browser there with a junction when possible.
|
||||
REM Browser user data and V8 compile cache stay off the USB drive.
|
||||
REM If this fails, startup continues with the cache on the USB drive.
|
||||
for /f "usebackq tokens=1,* delims==" %%a in (`""%NODE_BIN%" "%UCLAW_DIR%lib\portable-cache.mjs" "%STATE_DIR%" "%UCLAW_DIR%" 2^>nul"`) do (
|
||||
if "%%a"=="UCLAW_COMPILE_CACHE_DIR" set "NODE_COMPILE_CACHE=%%b"
|
||||
if "%%a"=="UCLAW_CACHE_ROOT" set "UCLAW_CACHE_ROOT=%%b"
|
||||
@@ -89,7 +89,7 @@ if not exist "%CORE_DIR%\node_modules" (
|
||||
echo File system: NTFS recommended. exFAT/FAT32 will be very slow.
|
||||
echo.
|
||||
cd /d "%CORE_DIR%"
|
||||
REM 把 npm 缓存留在盘内,避免污染系统 %APPDATA%\npm-cache(拔盘不留痕)
|
||||
REM Keep npm cache inside the portable app instead of system APPDATA.
|
||||
set "npm_config_cache=%APP_DIR%\.npm-cache"
|
||||
call "%NPM_BIN%" install --registry=https://registry.npmmirror.com --ignore-scripts --no-audit --no-fund --omit=dev
|
||||
echo.
|
||||
@@ -144,9 +144,9 @@ echo Starting Config Center on port 18788...
|
||||
set "CONFIG_SERVER=%UCLAW_DIR%config-server"
|
||||
start /B "" "%NODE_BIN%" "%CONFIG_SERVER%\server.js" >nul 2>&1
|
||||
|
||||
REM 等 Config Server 就绪 —— 动态探测而非写死等待。
|
||||
REM 它通常 <1s 就起来,写死 timeout /t 2 是白等。改为每 ~0.3s 探测 18788,
|
||||
REM 最多 ~6s(20 次)兜底。监听到就立刻往下走。
|
||||
REM Wait for Config Server with polling instead of a fixed delay.
|
||||
REM It is usually ready in under one second. Poll about every 0.3 seconds,
|
||||
REM up to about 6 seconds, then continue anyway.
|
||||
set /a CFG_TRIES=0
|
||||
:wait_config
|
||||
netstat -an | findstr ":18788 " | findstr "LISTENING" >nul 2>&1
|
||||
@@ -157,36 +157,25 @@ ping -n 1 -w 300 127.0.0.1 >nul 2>&1
|
||||
goto :wait_config
|
||||
:config_ready
|
||||
|
||||
REM IMPORTANT: 不要在 gateway 启动前就开 Dashboard 浏览器!
|
||||
REM 慢 U 盘上 OpenClaw 首次启动要 staging bundled deps(几十秒),
|
||||
REM 过早打开 http://127.0.0.1:18789 会"拒绝连接",是 issue #46/#48 的根因。
|
||||
REM 方案:立刻打开本地"启动首屏"loading.html,给用户即时反馈(移植自 4.0 splash)。
|
||||
REM 首屏自己轮询 /ready,gateway 真就绪后自动跳 Dashboard——天然解决拒连。
|
||||
REM Do not open Dashboard before the gateway is ready.
|
||||
REM Slow USB drives may need tens of seconds to stage bundled deps.
|
||||
REM Open the local startup page now, and always open Config Center too.
|
||||
REM Config Center lets users change models, recharge/get keys, and connect channels.
|
||||
|
||||
REM 是否已配置模型?openclaw.json 含 providers 即视为已配置 (issue #24)。
|
||||
REM 未配置(首次):先开 Config Center 引导填 Key。
|
||||
set "MODEL_CONFIGURED="
|
||||
findstr /C:"providers" "%STATE_DIR%\openclaw.json" >nul 2>&1
|
||||
if %errorlevel%==0 set "MODEL_CONFIGURED=1"
|
||||
|
||||
REM 立刻开启动首屏(带 port 参数,就绪后自动跳 Dashboard)
|
||||
REM 用 file:/// URL(正斜杠)+ 转义 & ,确保 query string 能传给浏览器。
|
||||
REM Open startup page with the gateway port and token in the query string.
|
||||
echo Opening startup screen...
|
||||
set "LOADING_PATH=%UCLAW_DIR%lib\loading.html"
|
||||
set "LOADING_URL=file:///%LOADING_PATH:\=/%?port=%PORT%&token=uclaw"
|
||||
start "" "%LOADING_URL%"
|
||||
|
||||
if not defined MODEL_CONFIGURED (
|
||||
echo First-time setup - opening Config Center...
|
||||
echo Opening Config Center...
|
||||
start "" http://127.0.0.1:18788/
|
||||
)
|
||||
|
||||
REM 后台等待器兜底:万一首屏页没起作用(浏览器拦本地 fetch 等),
|
||||
REM 仍轮询端口,gateway LISTENING 后开 Dashboard。
|
||||
REM Fallback watcher: if the startup page cannot poll from file URLs,
|
||||
REM keep polling and reopen Config Center after the gateway is ready.
|
||||
start /B "" cmd /c ""%UCLAW_DIR%lib\wait-gateway.bat" %PORT%"
|
||||
|
||||
REM gateway 首轮预热(后台、静默、非阻塞):就绪后先唤醒 config/model 子系统,
|
||||
REM 用户首次点发送时不再等。移植自 4.0 first-turn-prewarm。
|
||||
REM Prewarm gateway in the background after it becomes ready.
|
||||
start /B "" "%NODE_BIN%" "%UCLAW_DIR%lib\prewarm.mjs" %PORT% uclaw >nul 2>&1
|
||||
|
||||
echo.
|
||||
@@ -194,7 +183,7 @@ echo ========================================
|
||||
echo Starting OpenClaw Gateway on port %PORT%...
|
||||
echo First run on a USB drive may take 30-90 seconds
|
||||
echo (unpacking bundled components). Please wait;
|
||||
echo the Dashboard opens automatically when ready.
|
||||
echo Config Center is open for model, key, recharge, and channel setup.
|
||||
echo DO NOT close this window while using U-Claw!
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
@@ -178,9 +178,15 @@ input:focus { border-color: #ff6b35; }
|
||||
<!-- Step 1: Select Model -->
|
||||
<div class="section active" id="step1">
|
||||
<h2>选择 AI 模型</h2>
|
||||
<p class="desc">推荐国内用户用 MiniMax / Kimi / DeepSeek</p>
|
||||
<p class="desc">最省心:用「虾盘云」一个 Key 调用国内外全部大模型(中转站)。也可用下面各家自己的官方 Key。</p>
|
||||
|
||||
<div class="model-grid">
|
||||
<div class="model-card" data-provider="uclaw-cloud" data-base="https://api.u-claw.org/v1" data-model="deepseek-v4-flash">
|
||||
<span class="check">✓</span>
|
||||
<h4>虾盘云 <span class="tag hot">首选</span><span class="tag">中转站</span></h4>
|
||||
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
||||
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank" data-action-label="注册 / 充值 / 获取 API Key">→ 注册 / 充值 / 获取 API Key</a>
|
||||
</div>
|
||||
<div class="model-card" data-provider="minimax" data-base="https://api.minimax.chat/v1" data-model="MiniMax-Text-01">
|
||||
<span class="check">✓</span>
|
||||
<h4>MiniMax <span class="tag hot">推荐</span><span class="tag cn">国内</span></h4>
|
||||
@@ -259,7 +265,7 @@ input:focus { border-color: #ff6b35; }
|
||||
<p class="desc" id="step2desc">请填写对应模型的 API Key</p>
|
||||
|
||||
<div class="api-info" id="buyInfo">
|
||||
<h4>📌 如何获取 API Key?</h4>
|
||||
<h4>📌 获取 / 充值 API Key</h4>
|
||||
<div class="provider-links" id="providerLinks"></div>
|
||||
</div>
|
||||
|
||||
@@ -456,7 +462,7 @@ function goStep(n) {
|
||||
}
|
||||
|
||||
const PROVIDER_NAMES = {
|
||||
minimax: 'MiniMax', kimi: 'Kimi (月之暗面)', deepseek: 'DeepSeek',
|
||||
'uclaw-cloud': '虾盘云', minimax: 'MiniMax', kimi: 'Kimi (月之暗面)', deepseek: 'DeepSeek',
|
||||
zai: '智谱 GLM', qwen: '通义千问', doubao: '豆包',
|
||||
openai: 'OpenAI', anthropic: 'Anthropic Claude', groq: 'Groq',
|
||||
siliconflow: '硅基流动', custom: '自定义'
|
||||
@@ -474,7 +480,8 @@ function setupStep2() {
|
||||
var linksDiv = document.getElementById('providerLinks');
|
||||
linksDiv.innerHTML = '';
|
||||
if (buyLink) {
|
||||
linksDiv.innerHTML = '<a href="' + buyLink.href + '" target="_blank">🔑 ' + name + ' — 获取 API Key</a>';
|
||||
var action = buyLink.dataset.actionLabel || '获取 API Key';
|
||||
linksDiv.innerHTML = '<a href="' + buyLink.href + '" target="_blank">🔑 ' + name + ' — ' + action + '</a>';
|
||||
}
|
||||
linksDiv.innerHTML += '<a href="https://platform.minimaxi.com/" target="_blank">🔑 MiniMax — 国内推荐,注册送额度</a>' +
|
||||
'<a href="https://open.bigmodel.cn/" target="_blank">🔑 智谱 GLM — 有免费额度</a>' +
|
||||
@@ -492,7 +499,7 @@ function togglePw(id) {
|
||||
// --- Build OpenClaw config ---
|
||||
function buildConfig(provider, base, model, apiKey) {
|
||||
var baseConfig = {
|
||||
gateway: { auth: { mode: 'token', token: 'uclaw' } },
|
||||
gateway: { auth: { mode: 'token', token: 'uclaw' }, remote: { token: 'uclaw' } },
|
||||
commands: { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' },
|
||||
meta: { lastTouchedVersion: '2026.3.13', lastTouchedAt: new Date().toISOString() }
|
||||
};
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
<!--
|
||||
启动首屏(移植自 v2 u-clawx 4.0 的 splash 思路)。
|
||||
双击启动脚本后立刻打开本页,给用户即时反馈,消除"黑窗假死"体感。
|
||||
本页每秒自查 gateway 是否就绪(fetch /ready),就绪后自动跳转 Dashboard。
|
||||
本页每秒自查 gateway 是否就绪(fetch /ready)。
|
||||
|
||||
就绪后【不自动跳转】,而是停在选择页,给两个大按钮:
|
||||
🚀 进入 Dashboard(开始聊天)
|
||||
⚙️ 配置 / 改模型(打开配置中心)
|
||||
让用户自己决定去聊天还是改配置——避免"配过模型就被冲进 Dashboard、想改配置还得自己翻标签"。
|
||||
|
||||
端口通过 URL 查询参数传入:loading.html?port=18789
|
||||
(启动脚本打开时拼好。默认 18789。)
|
||||
@@ -63,24 +68,76 @@ h1 { font-size: 1.7em; font-weight: 600; margin-bottom: 8px; }
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
|
||||
<!-- 启动中 -->
|
||||
<div class="loading-wrap">
|
||||
<div class="logo">🦞</div>
|
||||
<h1>U-Claw 正在启动</h1>
|
||||
<div class="tip">首次从 U 盘启动需要展开内置组件,<br>稍等片刻,就绪后会自动打开。</div>
|
||||
<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 盘首次启动通常 30–90 秒。<br>
|
||||
如果迟迟没反应,可手动打开
|
||||
<a class="manual" id="manual" href="#">控制台</a>。
|
||||
如果迟迟没反应,可先打开
|
||||
<a class="manual" id="manual" href="#">配置中心</a>。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 就绪后:让用户自己选 -->
|
||||
<div class="ready-wrap">
|
||||
<div class="logo">🦞</div>
|
||||
<h1>U-Claw 已就绪</h1>
|
||||
<div class="tip">选择下一步 — 直接开始聊天,或先去配置模型和渠道。</div>
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" id="goDash">🚀 进入 Dashboard
|
||||
<span class="sub">开始和 AI 聊天</span>
|
||||
</a>
|
||||
<a class="btn" id="goConfig">⚙️ 配置 / 改模型
|
||||
<span class="sub">填写或更换模型 Key、充值、连接微信等渠道</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="foot">没配过模型或余额不足?先点「配置 / 改模型」处理 Key 或充值。</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var params = new URLSearchParams(location.search);
|
||||
@@ -90,10 +147,12 @@ a.manual { color: #fff; text-decoration: underline; opacity: .9; }
|
||||
var dashboard = base + '/#token=' + encodeURIComponent(token);
|
||||
var configCenter = 'http://127.0.0.1:18788/';
|
||||
|
||||
document.getElementById('manual').href = dashboard;
|
||||
document.getElementById('manual').href = configCenter;
|
||||
document.getElementById('goDash').href = dashboard;
|
||||
document.getElementById('goConfig').href = configCenter;
|
||||
|
||||
var startedAt = Date.now();
|
||||
var redirected = false;
|
||||
var ready = false;
|
||||
|
||||
function fmt(ms) {
|
||||
var s = Math.floor(ms / 1000);
|
||||
@@ -101,23 +160,23 @@ a.manual { color: #fff; text-decoration: underline; opacity: .9; }
|
||||
}
|
||||
|
||||
function tickElapsed() {
|
||||
if (ready) return;
|
||||
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 showChoice() {
|
||||
if (ready) return;
|
||||
ready = true;
|
||||
document.body.classList.add('ready');
|
||||
}
|
||||
|
||||
function poll() {
|
||||
if (redirected) return;
|
||||
// /ready 就绪即跳。no-cors 下拿不到 status,但 fetch 成功 resolve 就说明端口活了。
|
||||
if (ready) return;
|
||||
// /ready 就绪即显示选择页。no-cors 下拿不到 status,但 fetch 成功 resolve 就说明端口活了。
|
||||
fetch(base + '/ready', { mode: 'no-cors', cache: 'no-store' })
|
||||
.then(function () { goDashboard(); })
|
||||
.then(function () { showChoice(); })
|
||||
.catch(function () {
|
||||
// 端口还没起,继续等
|
||||
setTimeout(poll, 1000);
|
||||
@@ -127,7 +186,7 @@ a.manual { color: #fff; text-decoration: underline; opacity: .9; }
|
||||
|
||||
// 兜底:5 分钟还没起来,提示去控制台
|
||||
setTimeout(function () {
|
||||
if (!redirected) {
|
||||
if (!ready) {
|
||||
document.getElementById('status').innerHTML =
|
||||
'启动较慢,可先打开 <a class="manual" href="' + configCenter + '">配置中心</a> 检查';
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
@echo off
|
||||
REM wait-gateway.bat - open Dashboard only after the Gateway is actually listening.
|
||||
REM Fixes issue #46/#48: on slow USB drives the gateway needs tens of seconds to
|
||||
REM stage bundled deps on first run; opening http://127.0.0.1:PORT before it is
|
||||
REM LISTENING shows "connection refused" and users think it is broken.
|
||||
REM wait-gateway.bat - fallback watcher after Windows-Start opens loading.html.
|
||||
REM Slow USB drives can need tens of seconds before the gateway listens.
|
||||
REM
|
||||
REM 角色变更:现在 Windows-Start.bat 会先开 lib\loading.html 启动首屏,由首屏
|
||||
REM 自己轮询 /ready 并自动跳 Dashboard。本脚本退居"兜底"——万一首屏页的
|
||||
REM file:// fetch 被浏览器拦住没跳成,这里仍轮询端口、就绪后开 Dashboard。
|
||||
REM Windows-Start opens Config Center immediately, so the ready path only
|
||||
REM exits quietly. On timeout, reopen Config Center as a recovery hint.
|
||||
REM Usage (called in background by Windows-Start.bat): wait-gateway.bat PORT
|
||||
REM Polls every 2s, up to 150 tries (~5 min). Opens the browser once ready.
|
||||
REM Polls every 2 seconds, up to about 5 minutes.
|
||||
|
||||
set "PORT=%~1"
|
||||
if "%PORT%"=="" set "PORT=18789"
|
||||
@@ -23,8 +20,6 @@ timeout /t 2 /nobreak >nul
|
||||
goto :wait_loop
|
||||
|
||||
:ready
|
||||
timeout /t 1 /nobreak >nul
|
||||
start "" http://127.0.0.1:%PORT%/#token=uclaw
|
||||
exit /b 0
|
||||
|
||||
:timeout
|
||||
|
||||
@@ -45,6 +45,41 @@ test('portable Windows launchers disable OpenClaw bonjour discovery', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Windows startup keeps Config Center available even after model setup', () => {
|
||||
const script = readRepoFile('portable', 'Windows-Start.bat');
|
||||
|
||||
assert.match(
|
||||
script,
|
||||
/Opening Config Center[\s\S]*start "" http:\/\/127\.0\.0\.1:18788\//,
|
||||
'Windows-Start.bat should always open Config Center for model/channel changes',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
script,
|
||||
/if not defined MODEL_CONFIGURED/,
|
||||
'Config Center should not be gated on first-time setup only',
|
||||
);
|
||||
});
|
||||
|
||||
test('Windows gateway fallback does not force-open Dashboard', () => {
|
||||
const script = readRepoFile('portable', 'lib', 'wait-gateway.bat');
|
||||
|
||||
assert.match(
|
||||
script,
|
||||
/:timeout[\s\S]*start "" http:\/\/127\.0\.0\.1:18788\//,
|
||||
'wait-gateway.bat should return users to Config Center on timeout',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
script,
|
||||
/#token=uclaw/,
|
||||
'fallback should not push configured users straight into Dashboard',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
lineOf(script, ':ready') + '\n' + lineOf(script, 'exit /b 0'),
|
||||
/start ""/,
|
||||
'ready fallback should not open duplicate browser tabs',
|
||||
);
|
||||
});
|
||||
|
||||
test('PowerShell installer generated start.bat disables OpenClaw bonjour discovery', () => {
|
||||
const script = readRepoFile('install', 'install.ps1');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user