mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
209 lines
6.2 KiB
TypeScript
209 lines
6.2 KiB
TypeScript
/**
|
||
* CLI Web Vite 构建配置
|
||
*
|
||
* 用于构建 CLI Web 的 SPA 前端(不含 Electron 依赖)。
|
||
* 输出到 dist-cli-web/,由 clb web 托管。
|
||
*
|
||
* 与 Electron renderer 构建的关键区别:
|
||
* - __IS_ELECTRON__ = false(使用 FetchAdapter 而非 window.chatApi)
|
||
* - 不包含 apps/desktop/preload 和 apps/desktop/main
|
||
* - 输出目录独立(dist-cli-web/ vs out/renderer/)
|
||
*/
|
||
|
||
import { resolve } from 'path'
|
||
import { readFileSync } from 'fs'
|
||
import { spawn, type ChildProcess } from 'child_process'
|
||
import * as net from 'net'
|
||
import { defineConfig, type Plugin } from 'vite'
|
||
import vue from '@vitejs/plugin-vue'
|
||
import ui from '@nuxt/ui/vite'
|
||
import { DEFAULT_API_PORT } from './packages/config/src/schema'
|
||
import { createChatlabStartCommand, terminateChatlabStartProcess } from './scripts/dev-server-command.mjs'
|
||
import { chatlabIconBundle } from './vite.icon-bundle.config'
|
||
|
||
const BACKEND_PORT = DEFAULT_API_PORT
|
||
|
||
function isPortInUse(port: number): Promise<boolean> {
|
||
return new Promise((resolve, reject) => {
|
||
const server = net.createServer()
|
||
server.once('error', (error: NodeJS.ErrnoException) => {
|
||
if (error.code === 'EADDRINUSE') {
|
||
resolve(true)
|
||
return
|
||
}
|
||
reject(error)
|
||
})
|
||
server.once('listening', () => {
|
||
server.close()
|
||
resolve(false)
|
||
})
|
||
server.listen(port, '127.0.0.1')
|
||
})
|
||
}
|
||
|
||
async function isChatlabBackendResponsive(port: number, timeoutMs = 800): Promise<boolean> {
|
||
try {
|
||
const responses = await Promise.all(
|
||
['/_web/sessions', '/_web/ai/global-chats'].map((path) =>
|
||
fetch(`http://127.0.0.1:${port}${path}`, {
|
||
signal: AbortSignal.timeout(timeoutMs),
|
||
})
|
||
)
|
||
)
|
||
return responses.every((response) => [200, 401, 403].includes(response.status))
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 自动启动 clb web 后端的插件
|
||
* 仅在 CHATLAB_AUTO_SERVE=1 时生效(由 dev:cli-web 脚本设置)
|
||
*/
|
||
function chatlabServePlugin(): Plugin {
|
||
let serverProcess: ChildProcess | null = null
|
||
let processCleanupRegistered = false
|
||
|
||
function unregisterProcessCleanup() {
|
||
if (!processCleanupRegistered) return
|
||
process.off('exit', stopServerProcess)
|
||
process.off('SIGINT', handleSigint)
|
||
process.off('SIGTERM', handleSigterm)
|
||
processCleanupRegistered = false
|
||
}
|
||
|
||
function stopServerProcess() {
|
||
if (!serverProcess) return
|
||
terminateChatlabStartProcess(serverProcess)
|
||
serverProcess = null
|
||
unregisterProcessCleanup()
|
||
}
|
||
|
||
function handleSigint() {
|
||
stopServerProcess()
|
||
process.exit(130)
|
||
}
|
||
|
||
function handleSigterm() {
|
||
stopServerProcess()
|
||
process.exit(143)
|
||
}
|
||
|
||
function registerProcessCleanup() {
|
||
if (processCleanupRegistered) return
|
||
process.once('exit', stopServerProcess)
|
||
process.once('SIGINT', handleSigint)
|
||
process.once('SIGTERM', handleSigterm)
|
||
processCleanupRegistered = true
|
||
}
|
||
|
||
return {
|
||
name: 'chatlab-start',
|
||
async configureServer(server) {
|
||
if (process.env.CHATLAB_AUTO_SERVE !== '1') return
|
||
|
||
const inUse = await isPortInUse(BACKEND_PORT)
|
||
if (inUse) {
|
||
const responsive = await isChatlabBackendResponsive(BACKEND_PORT)
|
||
if (responsive) {
|
||
console.log(`[clb web] Port ${BACKEND_PORT} already has a responsive ChatLab API, skipping`)
|
||
return
|
||
}
|
||
throw new Error(
|
||
`[clb web] Port ${BACKEND_PORT} is in use, but the ChatLab API is stale or incomplete. Stop the stale process and restart dev:cli-web.`
|
||
)
|
||
}
|
||
|
||
const serverDir = resolve(__dirname, 'apps/cli')
|
||
const startCommand = createChatlabStartCommand({
|
||
serverDir,
|
||
backendPort: BACKEND_PORT,
|
||
})
|
||
serverProcess = spawn(startCommand.command, startCommand.args, startCommand.options)
|
||
|
||
serverProcess.stdout?.on('data', (data: Buffer) => {
|
||
const line = data.toString().trim()
|
||
if (line) console.log(`[clb web] ${line}`)
|
||
})
|
||
serverProcess.stderr?.on('data', (data: Buffer) => {
|
||
const line = data.toString().trim()
|
||
if (line) console.error(`[clb web] ${line}`)
|
||
})
|
||
serverProcess.on('exit', (code) => {
|
||
if (code !== null && code !== 0) {
|
||
console.error(`[clb web] exited with code ${code}`)
|
||
}
|
||
serverProcess = null
|
||
unregisterProcessCleanup()
|
||
})
|
||
registerProcessCleanup()
|
||
},
|
||
// Vite closes and rebuilds its HTTP server during config hot reloads.
|
||
// Cleanup stays bound to the Vite process lifecycle so a reload cannot stop the CLI backend.
|
||
}
|
||
}
|
||
|
||
export default defineConfig({
|
||
root: 'src/',
|
||
base: '/',
|
||
resolve: {
|
||
alias: {
|
||
'@': resolve(__dirname, 'src/'),
|
||
'~': resolve(__dirname, 'src/'),
|
||
'@openchatlab': resolve(__dirname, 'packages'),
|
||
'@electron/shared': resolve(__dirname, 'apps/desktop/shared'),
|
||
'@electron/preload': resolve(__dirname, 'apps/desktop/preload'),
|
||
},
|
||
},
|
||
define: {
|
||
__IS_ELECTRON__: JSON.stringify(false),
|
||
__IS_WEB_WASM__: JSON.stringify(false),
|
||
__APP_VERSION__: JSON.stringify(JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')).version),
|
||
},
|
||
plugins: [
|
||
vue(),
|
||
ui({
|
||
ui: {
|
||
colors: {
|
||
primary: 'pink',
|
||
neutral: 'zinc',
|
||
},
|
||
},
|
||
}),
|
||
chatlabIconBundle(__dirname),
|
||
chatlabServePlugin(),
|
||
],
|
||
build: {
|
||
outDir: resolve(__dirname, 'dist-cli-web'),
|
||
emptyOutDir: true,
|
||
sourcemap: false,
|
||
rollupOptions: {
|
||
input: {
|
||
index: resolve(__dirname, 'src/index.html'),
|
||
},
|
||
output: {
|
||
manualChunks(id) {
|
||
if (id.includes('node_modules/echarts-wordcloud')) return 'vendor-echarts-wordcloud'
|
||
if (id.includes('node_modules/zrender')) return 'vendor-zrender'
|
||
if (id.includes('node_modules/echarts')) return 'vendor-echarts'
|
||
if (id.includes('node_modules/@zumer/snapdom')) return 'vendor-snapdom'
|
||
return undefined
|
||
},
|
||
},
|
||
},
|
||
},
|
||
server: {
|
||
host: '127.0.0.1',
|
||
port: 3100,
|
||
proxy: {
|
||
'/_web': `http://127.0.0.1:${BACKEND_PORT}`,
|
||
'/api': `http://127.0.0.1:${BACKEND_PORT}`,
|
||
'/_proxy/chatlab.fun': {
|
||
target: 'https://chatlab.fun',
|
||
changeOrigin: true,
|
||
rewrite: (p: string) => p.replace(/^\/_proxy\/chatlab\.fun/, ''),
|
||
},
|
||
},
|
||
},
|
||
})
|