mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(server): add static file hosting to chatlab serve
Phase 3A.3: Enable chatlab serve to host the Web SPA frontend alongside the HTTP API, creating a complete local web experience. - Add --web [dir] option to chatlab serve (defaults to dist-web/) - Use @fastify/static for efficient static file serving - SPA history fallback via setNotFoundHandler -> index.html - Skip web hosting gracefully if dist-web/ doesn't exist - Add webRoot option to HttpServerOptions / startHttpServer
This commit is contained in:
1 parent
17855858d7
commit
b1fbeb6f1d
4 files changed
+132
-5
No files matched your search
@@ -14,6 +14,7 @@
|
||||
"rebuild-sqlite": "npm rebuild better-sqlite3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^9.1.3",
|
||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||
"@openchatlab/config": "workspace:*",
|
||||
"@openchatlab/core": "workspace:*",
|
||||
|
||||
@@ -21,10 +21,7 @@ import {
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program
|
||||
.name('chatlab')
|
||||
.description('ChatLab - 聊天记录分析工具')
|
||||
.version('0.0.1')
|
||||
program.name('chatlab').description('ChatLab - 聊天记录分析工具').version('0.0.1')
|
||||
|
||||
// chatlab sessions - 列出所有会话
|
||||
program
|
||||
@@ -129,7 +126,9 @@ program
|
||||
if (options.format === 'json') {
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
} else {
|
||||
console.log(`搜索 "${keyword}" - 共 ${result.total} 条结果${result.hasMore ? '(显示前 ' + limit + ' 条)' : ''}:\n`)
|
||||
console.log(
|
||||
`搜索 "${keyword}" - 共 ${result.total} 条结果${result.hasMore ? '(显示前 ' + limit + ' 条)' : ''}:\n`
|
||||
)
|
||||
for (const msg of result.messages) {
|
||||
const time = new Date(msg.timestamp * 1000).toLocaleString()
|
||||
console.log(` [${time}] ${msg.senderName}: ${msg.content}`)
|
||||
@@ -275,20 +274,35 @@ program
|
||||
.option('--port <port>', '服务端口', '3210')
|
||||
.option('--host <host>', '监听地址', '127.0.0.1')
|
||||
.option('--token <token>', '自定义 Bearer Token(不指定则从配置文件读取或自动生成)')
|
||||
.option('--web [dir]', '托管 Web 前端静态资源(默认查找 dist-web/)')
|
||||
.action(async (options) => {
|
||||
const { startHttpServer } = await import('./http')
|
||||
const port = parseInt(options.port, 10)
|
||||
|
||||
let webRoot: string | undefined
|
||||
if (options.web !== undefined) {
|
||||
const webDir = typeof options.web === 'string' ? options.web : path.resolve(__dirname, '../../dist-web')
|
||||
if (fs.existsSync(webDir)) {
|
||||
webRoot = path.resolve(webDir)
|
||||
} else {
|
||||
console.warn(`警告: Web 目录不存在: ${webDir},将仅启动 API 服务`)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await startHttpServer({
|
||||
port,
|
||||
host: options.host,
|
||||
token: options.token || undefined,
|
||||
webRoot,
|
||||
})
|
||||
|
||||
console.log(`\nChatLab HTTP API 已启动`)
|
||||
console.log(` 地址: http://${info.host}:${info.port}`)
|
||||
console.log(` Token: ${info.token}`)
|
||||
if (webRoot) {
|
||||
console.log(` Web UI: http://${info.host}:${info.port}/`)
|
||||
}
|
||||
console.log(`\n示例:`)
|
||||
console.log(` curl -H "Authorization: Bearer ${info.token}" http://${info.host}:${info.port}/api/v1/status`)
|
||||
console.log(`\n按 Ctrl+C 停止服务\n`)
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface HttpServerOptions {
|
||||
port?: number
|
||||
host?: string
|
||||
token?: string
|
||||
/** dist-web/ 目录路径,启用后托管 Web SPA 静态资源 */
|
||||
webRoot?: string
|
||||
}
|
||||
|
||||
function resolveNativeBinding(): string | undefined {
|
||||
@@ -88,6 +90,20 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{
|
||||
registerSessionRoutes(server, dbManager)
|
||||
registerWebRoutes(server, dbManager)
|
||||
|
||||
// 托管 Web SPA 静态资源
|
||||
if (options?.webRoot && fs.existsSync(options.webRoot)) {
|
||||
const fastifyStatic = await import('@fastify/static')
|
||||
await server.register(fastifyStatic.default, {
|
||||
root: options.webRoot,
|
||||
prefix: '/',
|
||||
wildcard: false,
|
||||
})
|
||||
// SPA fallback: 所有非 API/非静态文件路由返回 index.html
|
||||
server.setNotFoundHandler(async (_request, reply) => {
|
||||
return reply.sendFile('index.html')
|
||||
})
|
||||
}
|
||||
|
||||
await server.listen({ port, host })
|
||||
|
||||
return { port, host, token }
|
||||
|
||||
Generated
+96
@@ -161,6 +161,9 @@ importers:
|
||||
|
||||
packages/server:
|
||||
dependencies:
|
||||
'@fastify/static':
|
||||
specifier: ^9.1.3
|
||||
version: 9.1.3
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.12.1
|
||||
version: 1.29.0(zod@3.25.76)
|
||||
@@ -879,6 +882,9 @@ packages:
|
||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@fastify/accept-negotiator@2.0.1':
|
||||
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
|
||||
|
||||
'@fastify/ajv-compiler@4.0.5':
|
||||
resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
|
||||
|
||||
@@ -897,6 +903,12 @@ packages:
|
||||
'@fastify/proxy-addr@5.1.0':
|
||||
resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
|
||||
|
||||
'@fastify/send@4.1.0':
|
||||
resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==}
|
||||
|
||||
'@fastify/static@9.1.3':
|
||||
resolution: {integrity: sha512-aXrYtsiryLhRxRNaxNqsn7FUISeb7rB9q4eHUPIot5aeQBLNahnz1m6thzm7JWC1poSGXS9XrX8DvuMivp2hkQ==}
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==}
|
||||
|
||||
@@ -1004,6 +1016,10 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@lukeed/ms@2.0.2':
|
||||
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@malept/cross-spawn-promise@2.0.0':
|
||||
resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
@@ -2368,6 +2384,10 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
balanced-match@4.0.4:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@@ -2415,6 +2435,10 @@ packages:
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
brace-expansion@5.0.6:
|
||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3127,6 +3151,9 @@ packages:
|
||||
resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
|
||||
hasBin: true
|
||||
|
||||
fastify-plugin@5.1.0:
|
||||
resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==}
|
||||
|
||||
fastify@5.8.4:
|
||||
resolution: {integrity: sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==}
|
||||
|
||||
@@ -3341,6 +3368,10 @@ packages:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
glob@13.0.6:
|
||||
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
@@ -3964,6 +3995,11 @@ packages:
|
||||
engines: {node: '>=4.0.0'}
|
||||
hasBin: true
|
||||
|
||||
mime@3.0.0:
|
||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3984,6 +4020,10 @@ packages:
|
||||
resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
@@ -4026,6 +4066,10 @@ packages:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass@7.1.3:
|
||||
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minizlib@3.1.0:
|
||||
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -4280,6 +4324,10 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
path-scurry@2.0.2:
|
||||
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
path-to-regexp@8.4.2:
|
||||
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||
|
||||
@@ -6357,6 +6405,8 @@ snapshots:
|
||||
'@eslint/core': 0.17.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@fastify/accept-negotiator@2.0.1': {}
|
||||
|
||||
'@fastify/ajv-compiler@4.0.5':
|
||||
dependencies:
|
||||
ajv: 8.18.0
|
||||
@@ -6380,6 +6430,23 @@ snapshots:
|
||||
'@fastify/forwarded': 3.0.1
|
||||
ipaddr.js: 2.3.0
|
||||
|
||||
'@fastify/send@4.1.0':
|
||||
dependencies:
|
||||
'@lukeed/ms': 2.0.2
|
||||
escape-html: 1.0.3
|
||||
fast-decode-uri-component: 1.0.1
|
||||
http-errors: 2.0.1
|
||||
mime: 3.0.0
|
||||
|
||||
'@fastify/static@9.1.3':
|
||||
dependencies:
|
||||
'@fastify/accept-negotiator': 2.0.1
|
||||
'@fastify/send': 4.1.0
|
||||
content-disposition: 1.1.0
|
||||
fastify-plugin: 5.1.0
|
||||
fastq: 1.20.1
|
||||
glob: 13.0.6
|
||||
|
||||
'@floating-ui/core@1.7.4':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
@@ -6503,6 +6570,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@lukeed/ms@2.0.2': {}
|
||||
|
||||
'@malept/cross-spawn-promise@2.0.0':
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
@@ -8206,6 +8275,8 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.9.19: {}
|
||||
@@ -8261,6 +8332,10 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
brace-expansion@5.0.6:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
@@ -9117,6 +9192,8 @@ snapshots:
|
||||
dependencies:
|
||||
strnum: 2.1.2
|
||||
|
||||
fastify-plugin@5.1.0: {}
|
||||
|
||||
fastify@5.8.4:
|
||||
dependencies:
|
||||
'@fastify/ajv-compiler': 4.0.5
|
||||
@@ -9416,6 +9493,12 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@13.0.6:
|
||||
dependencies:
|
||||
minimatch: 10.2.5
|
||||
minipass: 7.1.3
|
||||
path-scurry: 2.0.2
|
||||
|
||||
glob@7.2.3:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@@ -9984,6 +10067,8 @@ snapshots:
|
||||
|
||||
mime@2.6.0: {}
|
||||
|
||||
mime@3.0.0: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
@@ -9996,6 +10081,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@isaacs/brace-expansion': 5.0.1
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.6
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
@@ -10040,6 +10129,8 @@ snapshots:
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minipass@7.1.3: {}
|
||||
|
||||
minizlib@3.1.0:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
@@ -10286,6 +10377,11 @@ snapshots:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
path-scurry@2.0.2:
|
||||
dependencies:
|
||||
lru-cache: 11.2.5
|
||||
minipass: 7.1.3
|
||||
|
||||
path-to-regexp@8.4.2: {}
|
||||
|
||||
pathe@1.1.2: {}
|
||||
|
||||
Reference in new issue
Block a user