fix: 修复托盘菜单语法错误与激活页缺失,完善 Windows 打包流程

- electron/main.cjs: 修复托盘菜单损坏的合并代码(SyntaxError),托盘 3D 意识空间与 ipc 路由改用正确的 backendPort 变量
- 新增 activation.html 激活页(对接 /activate 接口,支持自动识别/8 个服务商)
- scripts/package-win.ps1: 打包前切换 better-sqlite3 到 Electron ABI、打包后还原 Node ABI,避免 dev 后端崩溃;内置 npmmirror 工具链镜像
- scripts/gen-icons.ps1 + build/icon.ico: 生成安装包图标(electron-builder 必需)
- package.json: build 脚本改用 package-win.ps1(不再依赖 VS 工具链)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-08 01:54:08 +08:00
parent bf73366e81
commit a57c360b06
10 changed files with 432 additions and 47 deletions

4
.gitignore vendored
View File

@@ -143,3 +143,7 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IDE
.idea/
.vscode/

265
activation.html Normal file
View File

@@ -0,0 +1,265 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>激活 · Bailongma</title>
<style>
:root {
--bg: #0b0e14;
--panel: rgba(255, 255, 255, 0.04);
--border: rgba(255, 255, 255, 0.1);
--text: #e6e9f0;
--muted: #8b93a7;
--accent: #4f8cff;
--accent-hover: #6b9eff;
--ok: #34c98e;
--err: #ff5f6d;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
min-height: 100vh;
display: flex; align-items: center; justify-content: center;
background: radial-gradient(1200px 600px at 20% 0%, #16203a 0%, var(--bg) 55%);
font-family: "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
padding: 24px;
}
.card {
width: 100%; max-width: 460px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px;
backdrop-filter: blur(8px);
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.logo { font-size: 22px; font-weight: 700; letter-spacing: 1px; margin-bottom: 4px; }
.logo span { color: var(--accent); }
.sub { color: var(--muted); font-size: 13px; margin-bottom: 24px; line-height: 1.6; }
label { display: block; font-size: 13px; color: var(--muted); margin: 14px 0 6px; }
input, select {
width: 100%;
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 14px;
padding: 10px 12px;
outline: none;
transition: border-color .15s;
}
input:focus, select:focus { border-color: var(--accent); }
.key-row { position: relative; }
.key-row input { padding-right: 56px; }
.toggle {
position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
background: none; border: none; color: var(--muted); cursor: pointer;
font-size: 12px; padding: 6px;
}
.toggle:hover { color: var(--text); }
.btn {
width: 100%;
margin-top: 24px;
background: var(--accent);
border: none; border-radius: 8px;
color: #fff; font-size: 15px; font-weight: 600;
padding: 12px;
cursor: pointer;
transition: background .15s;
}
.btn:hover { background: var(--accent-hover); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.msg { display: none; font-size: 13px; margin-top: 14px; line-height: 1.5; padding: 10px 12px; border-radius: 8px; }
.msg.ok { display: block; background: rgba(52, 201, 142, .1); color: var(--ok); border: 1px solid rgba(52, 201, 142, .3); }
.msg.err { display: block; background: rgba(255, 95, 109, .1); color: var(--err); border: 1px solid rgba(255, 95, 109, .3); }
.hint { font-size: 12px; color: var(--muted); margin-top: 8px; line-height: 1.6; }
.footer { margin-top: 20px; text-align: center; font-size: 12px; color: var(--muted); }
.already { display: none; text-align: center; padding: 8px 0 4px; }
.already .badge { color: var(--ok); font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.already a { color: var(--accent); text-decoration: none; font-size: 13px; }
.already a:hover { text-decoration: underline; }
.spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,.3); border-top-color: #fff; border-radius: 50%; animation: spin .8s linear infinite; vertical-align: -2px; margin-right: 8px; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="card">
<div class="logo">白龙马 <span>Bailongma</span></div>
<div class="sub">数字意识框架 · 首次运行需激活。填入任意支持的 LLM API Key 即可开始。</div>
<div id="formArea">
<label for="agentName">给它起个名字(可选)</label>
<input id="agentName" type="text" maxlength="32" placeholder="默认:小白龙" autocomplete="off">
<label for="provider">LLM 服务商</label>
<select id="provider"></select>
<div id="customRow" style="display:none">
<label for="baseURL">API 地址 (Base URL)</label>
<input id="baseURL" type="text" placeholder="https://your-endpoint.com/v1" autocomplete="off">
</div>
<label for="model">模型</label>
<select id="model"></select>
<label for="apiKey">API Key</label>
<div class="key-row">
<input id="apiKey" type="password" placeholder="sk-..." autocomplete="off" spellcheck="false">
<button class="toggle" id="toggleKey" type="button">显示</button>
</div>
<button class="btn" id="submitBtn">激活</button>
<div class="msg" id="msg"></div>
<div class="hint">Key 只会保存在本机 config.json 中,不会上传到任何第三方服务器。选择「自动识别」时系统会逐个探测各服务商。</div>
</div>
<div class="already" id="alreadyArea">
<div class="badge">✓ 已激活</div>
<div id="alreadyInfo"></div>
<a href="/">进入主界面 →</a>
</div>
<div class="footer">Bailongma v2.1.300</div>
</div>
<script>
(async () => {
const $ = id => document.getElementById(id)
const msg = $('msg')
const showMsg = (text, ok) => {
msg.textContent = text
msg.className = 'msg ' + (ok ? 'ok' : 'err')
}
// Load provider list + current activation status
let providers = {}
let status = null
try {
const [settings, statusRes] = await Promise.all([
fetch('/settings').then(r => r.json()),
fetch('/activation-status').then(r => r.json()),
])
providers = settings.providers || {}
status = statusRes
} catch (err) {
showMsg('无法连接后端服务,请确认 Bailongma 正在运行。', false)
return
}
const providerSelect = $('provider')
const modelSelect = $('model')
const customRow = $('customRow')
// Provider dropdown: auto-detect first, then every configured provider
const providerOptions = [['auto', '自动识别 (Auto)']]
for (const [name, cfg] of Object.entries(providers)) {
providerOptions.push([name, cfg.label || name])
}
providerSelect.innerHTML = providerOptions
.map(([value, label]) => `<option value="${value}">${label}</option>`)
.join('')
function fillModels(provider) {
const cfg = providers[provider]
modelSelect.innerHTML = ''
if (!cfg || !cfg.models || !cfg.models.length) {
const opt = document.createElement('option')
opt.value = ''
opt.textContent = '(自定义模型,激活时填写 Base URL'
modelSelect.appendChild(opt)
return
}
for (const m of cfg.models) {
const opt = document.createElement('option')
opt.value = m.id
opt.textContent = m.label || m.id
if (m.deprecated) opt.textContent += '(已弃用)'
if (m.id === cfg.defaultModel) opt.selected = true
modelSelect.appendChild(opt)
}
customRow.style.display = 'none'
}
providerSelect.addEventListener('change', () => {
const value = providerSelect.value
if (value === 'custom') {
customRow.style.display = 'block'
modelSelect.innerHTML = ''
const opt = document.createElement('option')
opt.value = ''
opt.textContent = '(自定义模型名)'
modelSelect.appendChild(opt)
} else {
fillModels(value === 'auto' ? Object.keys(providers)[0] || null : value)
}
})
// Prefill current status if already activated
if (status && status.activated) {
$('alreadyArea').style.display = 'block'
$('alreadyInfo').textContent = status.provider + ' · ' + (status.model || '')
}
const prefill = status && status.provider && status.provider !== 'auto'
? status.provider : 'auto'
providerSelect.value = providerOptions.some(([v]) => v === prefill) ? prefill : 'auto'
fillModels(prefill === 'auto' ? (Object.keys(providers)[0] || null) : prefill)
$('toggleKey').addEventListener('click', () => {
const input = $('apiKey')
const show = input.type === 'password'
input.type = show ? 'text' : 'password'
$('toggleKey').textContent = show ? '隐藏' : '显示'
})
$('submitBtn').addEventListener('click', async () => {
const btn = $('submitBtn')
const apiKey = $('apiKey').value.trim()
const agentName = $('agentName').value.trim()
const provider = providerSelect.value
if (!apiKey) {
showMsg('请先填写 API Key。', false)
return
}
btn.disabled = true
btn.innerHTML = '<span class="spinner"></span>验证中…'
showMsg('', true)
const body = { apiKey, provider, agentName }
if (provider === 'custom') {
body.model = $('model').value.trim()
body.baseURL = $('baseURL').value.trim()
if (!body.model || !body.baseURL) {
btn.disabled = false
btn.textContent = '激活'
showMsg('自定义端点需要填写模型名和 Base URL。', false)
return
}
} else if (provider !== 'auto') {
body.model = $('model').value
}
try {
const res = await fetch('/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const data = await res.json()
if (data.ok) {
showMsg('✓ 激活成功!正在进入主界面…', true)
setTimeout(() => { window.location.href = '/' }, 1200)
} else {
showMsg('激活失败:' + (data.error || '未知错误'), false)
}
} catch (err) {
showMsg('激活失败:无法连接后端 (' + err.message + ')', false)
} finally {
btn.disabled = false
btn.textContent = '激活'
}
})
})()
</script>
</body>
</html>

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -246,19 +246,19 @@ function setupTray() {
},
},
{ type: 'separator' },
{
{
label: '3D 意识空间',
click: () => {
if (mainWindow) {
mainWindow.loadURL(`http://127.0.0.1:`+port+`/consciousness-3d`)
mainWindow.loadURL(`http://127.0.0.1:${backendPort}/consciousness-3d`)
mainWindow.show()
mainWindow.focus()
}
},
},
{ type: 'separator' }, label: '退出',
{ type: 'separator' },
{
label: '退出',
click: () => {
app.isQuiting = true
app.quit()
@@ -474,7 +474,7 @@ app.whenReady().then(async () => {
ipcMain.on('open-consciousness-3d', () => {
if (mainWindow) {
mainWindow.loadURL(`http://127.0.0.1:`+port+`/consciousness-3d`)
mainWindow.loadURL(`http://127.0.0.1:${backendPort}/consciousness-3d`)
}
})

55
package-lock.json generated
View File

@@ -839,6 +839,7 @@
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -1070,7 +1071,6 @@
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"archiver-utils": "^2.1.0",
"async": "^3.2.4",
@@ -1090,7 +1090,6 @@
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"glob": "^7.1.4",
"graceful-fs": "^4.2.0",
@@ -1113,7 +1112,6 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -1129,8 +1127,7 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1",
@@ -1138,7 +1135,6 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -1802,7 +1798,6 @@
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2",
@@ -1927,7 +1922,6 @@
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"crc32": "bin/crc32.njs"
},
@@ -1941,7 +1935,6 @@
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^3.4.0"
@@ -2291,6 +2284,7 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2568,6 +2562,7 @@
"integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "25.1.8",
"builder-util": "25.1.7",
@@ -2763,7 +2758,6 @@
"integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "25.1.8",
"archiver": "^5.3.1",
@@ -2777,7 +2771,6 @@
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -2793,7 +2786,6 @@
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"universalify": "^2.0.0"
},
@@ -2807,7 +2799,6 @@
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 10.0.0"
}
@@ -3892,8 +3883,7 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/isbinaryfile": {
"version": "5.0.7",
@@ -4028,7 +4018,6 @@
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"readable-stream": "^2.0.5"
},
@@ -4042,7 +4031,6 @@
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -4058,8 +4046,7 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1",
@@ -4067,7 +4054,6 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -4084,16 +4070,14 @@
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
@@ -4106,8 +4090,7 @@
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
@@ -4121,16 +4104,14 @@
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/log-symbols": {
"version": "4.1.0",
@@ -4660,7 +4641,6 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4914,6 +4894,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5010,8 +4991,7 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
@@ -5134,7 +5114,6 @@
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"minimatch": "^5.1.0"
}
@@ -5144,8 +5123,7 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.1.0",
@@ -5153,7 +5131,6 @@
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -5164,7 +5141,6 @@
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -6096,6 +6072,7 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -6198,7 +6175,6 @@
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2",
@@ -6214,7 +6190,6 @@
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"glob": "^7.2.3",
"graceful-fs": "^4.2.0",

View File

@@ -14,7 +14,7 @@
"start:backend": "node --env-file=.env src/index.js",
"start:backend:lan": "powershell -ExecutionPolicy Bypass -File scripts/start-lan.ps1 backend",
"dev": "node --env-file=.env --watch src/index.js",
"build": "powershell -ExecutionPolicy Bypass -File scripts/prebuild-clean.ps1 && electron-rebuild -f -w better-sqlite3 -v 33.4.11 && electron-builder --win",
"build": "powershell -ExecutionPolicy Bypass -File scripts/prebuild-clean.ps1 && powershell -ExecutionPolicy Bypass -File scripts/package-win.ps1",
"publish": "electron-builder --win --publish always",
"postinstall": "electron-builder install-app-deps",
"test:rule-context": "electron ./src/test-rule-context.js",

85
scripts/gen-icons.ps1 Normal file
View File

@@ -0,0 +1,85 @@
# gen-icons.ps1 — Generate build/icon.ico from an existing logo PNG.
# Produces a multi-size PNG-compressed ICO (16/24/32/48/64/128/256), usable by
# electron-builder (win.icon / installerHeaderIcon) and NSIS on Win10+.
param(
[string]$Source = "images/bailongma-logo.png",
[string]$OutDir = "build"
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Drawing
$srcPath = Join-Path (Split-Path $PSScriptRoot) $Source
$outPath = Join-Path (Split-Path $PSScriptRoot) "$OutDir\icon.ico"
if (-not (Test-Path $srcPath)) { Write-Error "Source image not found: $srcPath" }
$src = [System.Drawing.Image]::FromFile((Resolve-Path $srcPath))
try {
# Square crop to the shorter side so the icon is not distorted.
$side = [Math]::Min($src.Width, $src.Height)
$cropX = [int](($src.Width - $side) / 2)
$cropY = [int](($src.Height - $side) / 2)
$square = New-Object System.Drawing.Bitmap($side, $side)
$g0 = [System.Drawing.Graphics]::FromImage($square)
try {
$g0.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$g0.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$g0.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g0.DrawImage($src, (New-Object System.Drawing.Rectangle(0, 0, $side, $side)),
(New-Object System.Drawing.Rectangle($cropX, $cropY, $side, $side)),
[System.Drawing.GraphicsUnit]::Pixel)
} finally { $g0.Dispose() }
$sizes = @(16, 24, 32, 48, 64, 128, 256)
$pngBlobs = @()
foreach ($s in $sizes) {
$bmp = New-Object System.Drawing.Bitmap($s, $s, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$g = [System.Drawing.Graphics]::FromImage($bmp)
try {
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g.Clear([System.Drawing.Color]::Transparent)
$g.DrawImage($square, 0, 0, $s, $s)
} finally { $g.Dispose() }
$ms = New-Object System.IO.MemoryStream
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
$pngBlobs += , $ms.ToArray()
$ms.Dispose()
$bmp.Dispose()
}
$square.Dispose()
# Assemble ICO: ICONDIR + ICONDIRENTRY per image + PNG blobs.
$fs = [System.IO.File]::Create($outPath)
$w = New-Object System.IO.BinaryWriter($fs)
try {
$w.Write([UInt16]0) # reserved
$w.Write([UInt16]1) # type: icon
$w.Write([UInt16]$pngBlobs.Count) # image count
$offset = 6 + 16 * $pngBlobs.Count
foreach ($i in 0..($pngBlobs.Count - 1)) {
$blob = $pngBlobs[$i]
$size = $sizes[$i]
$w.Write([Byte]($(if ($size -ge 256) { 0 } else { $size }))) # width (0 = 256)
$w.Write([Byte]($(if ($size -ge 256) { 0 } else { $size }))) # height (0 = 256)
$w.Write([Byte]0) # color count
$w.Write([Byte]0) # reserved
$w.Write([UInt16]1) # planes
$w.Write([UInt16]32) # bit count
$w.Write([UInt32]$blob.Length) # bytes in resource
$w.Write([UInt32]$offset) # image offset
$offset += $blob.Length
}
foreach ($blob in $pngBlobs) { $w.Write($blob) }
$w.Flush()
} finally {
$w.Dispose()
$fs.Dispose()
}
Write-Host "[gen-icons] wrote $outPath ($((Get-Item $outPath).Length) bytes, $($pngBlobs.Count) sizes)"
} finally {
$src.Dispose()
}

56
scripts/package-win.ps1 Normal file
View File

@@ -0,0 +1,56 @@
# package-win.ps1 — Build the Windows NSIS installer.
#
# better-sqlite3 is a native module and its binary must match the runtime ABI:
# - packaged app / `npm start` → Electron ABI (v130 for Electron 33)
# - `npm run start:backend` → Node ABI
# This script swaps the prebuilt binary to Electron ABI before packaging and
# restores the Node ABI afterwards, so dev backend mode keeps working.
#
# No compiler needed: prebuild-install downloads prebuilt binaries (uses the
# npm prebuild cache when GitHub is unreachable).
#
# Usage: powershell -ExecutionPolicy Bypass -File scripts/package-win.ps1
param([string]$ElectronVersion = "33.4.11")
$ErrorActionPreference = "Stop"
$root = Split-Path $PSScriptRoot
Set-Location $root
function Invoke-PrebuildInstall {
param([string]$Runtime, [string]$Target)
Push-Location "node_modules\better-sqlite3"
try {
if ($Runtime -eq "electron") {
npx prebuild-install --runtime=electron --target=$Target
} else {
npx prebuild-install
}
if ($LASTEXITCODE -ne 0) { throw "prebuild-install ($Runtime) failed with exit code $LASTEXITCODE" }
} finally {
Pop-Location
}
}
# 1) Swap better-sqlite3 to the Electron-ABI prebuilt binary.
Invoke-PrebuildInstall -Runtime electron -Target $ElectronVersion
try {
# 2) Package. Point electron-builder at the local electron dist to skip the
# GitHub download, and mirror NSIS/winCodeSign through npmmirror (GitHub
# is unreachable on CN networks).
$env:ELECTRON_BUILDER_BINARIES_MIRROR = "https://cdn.npmmirror.com/binaries/electron-builder-binaries/"
$dist = Join-Path $root "node_modules\electron\dist"
npx electron-builder --win "-c.electronDist=$dist"
if ($LASTEXITCODE -ne 0) { throw "electron-builder failed with exit code $LASTEXITCODE" }
Write-Host "[package] NSIS installer built:" -ForegroundColor Green
Get-ChildItem "$root\dist\*.exe" | ForEach-Object { Write-Host " $_" -ForegroundColor Green }
} finally {
# 3) Restore the Node-ABI binary so `npm run start:backend` / `npm run dev` keep working.
try {
Invoke-PrebuildInstall -Runtime node
Write-Host "[package] better-sqlite3 restored to Node ABI" -ForegroundColor Cyan
} catch {
Write-Host "[package] WARN: failed to restore Node prebuild: $($_.Exception.Message)" -ForegroundColor Yellow
}
}

View File

@@ -1141,6 +1141,7 @@ export function startAPI(port = 3721, { getStateSnapshot = null, onActivated = n
res.end('brain-ui.html not found')
}
return
}
// GET /consciousness-3d - 3D意识空间可视化
@@ -1160,7 +1161,6 @@ export function startAPI(port = 3721, { getStateSnapshot = null, onActivated = n
}
return
}
}
if (req.method === 'GET' && url.pathname === '/systemPrompt.html') {
try {