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