chore(web): add Web WASM development workflow

This commit is contained in:
digua committed 2026-07-20 17:24:55 +08:00
1 parent 8ca8b4f623
commit 73c6de364e
17 files changed
+74 -42

No files matched your search

+1 -1
View File
@@ -363,7 +363,7 @@ jobs:
name: ChatLab-cli-build name: ChatLab-cli-build
path: | path: |
apps/cli/dist apps/cli/dist
apps/cli/dist-web apps/cli/dist-cli-web
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
+1 -1
View File
@@ -23,7 +23,7 @@ error-log-*.txt
# Project # Project
dist/ dist/
dist-web/ dist-cli-web/
dist-web-wasm/ dist-web-wasm/
docs/.vitepress/cache/ docs/.vitepress/cache/
docs/.vitepress/dist/ docs/.vitepress/dist/
+1
View File
@@ -50,6 +50,7 @@
## 代码规范 ## 代码规范
- 平台术语:内部交流、开发菜单和平台级代码统一使用 `CLI Web`(Node 后端 + Web UI)与 `Web WASM`(纯浏览器运行)。只说 `Web` 且上下文无法区分时,默认指 `Web WASM`。浏览器 Worker、OPFS 和浏览器 adapter 等技术能力使用 `Browser Runtime` 命名,它不是独立平台。详细边界见 `.docs/rules/platform-naming.md`。
- 多语言:代码中的日志、注释、AI 工具描述、错误消息等非 UI 文本默认使用英文。当有运行时 locale 可用时(如工具返回结果、AI 看到的文本),应通过 `isChineseLocale(locale)` 等机制支持中英双语。数据清洗中与聊天平台格式匹配的标签(如 `[分享]`、`[图片]`)保持原始语言不变。UI 文案的国际化遵循 `.docs/rules/i18n.md` - 多语言:代码中的日志、注释、AI 工具描述、错误消息等非 UI 文本默认使用英文。当有运行时 locale 可用时(如工具返回结果、AI 看到的文本),应通过 `isChineseLocale(locale)` 等机制支持中英双语。数据清洗中与聊天平台格式匹配的标签(如 `[分享]`、`[图片]`)保持原始语言不变。UI 文案的国际化遵循 `.docs/rules/i18n.md`
- i18n 复用性:新增 UI 文案 key 前,先判断是否是通用动作、状态、提示或组件文案;能复用的优先放在 `common.*` 等共享命名空间,避免在具体业务模块(如 `members.*`、`records.*`)重复定义同义 key。只有明确绑定业务语境、无法自然复用的文案才放到模块命名空间。 - i18n 复用性:新增 UI 文案 key 前,先判断是否是通用动作、状态、提示或组件文案;能复用的优先放在 `common.*` 等共享命名空间,避免在具体业务模块(如 `members.*`、`records.*`)重复定义同义 key。只有明确绑定业务语境、无法自然复用的文案才放到模块命名空间。
+2 -1
View File
@@ -113,7 +113,8 @@ Or launch a specific target directly:
```bash ```bash
pnpm dev:desktop # Electron desktop app pnpm dev:desktop # Electron desktop app
pnpm dev:web # Web frontend + local server pnpm dev:cli-web # CLI Web frontend + local server
pnpm dev:web-wasm # Browser-only Web WASM
pnpm dev:serve # CLI server only pnpm dev:serve # CLI server only
pnpm docs:dev # Docs site pnpm docs:dev # Docs site
``` ```
+2 -1
View File
@@ -113,7 +113,8 @@ pnpm dev
```bash ```bash
pnpm dev:desktop # Electron 桌面端 pnpm dev:desktop # Electron 桌面端
pnpm dev:web # 前端 + 本地服务 pnpm dev:cli-web # CLI Web 前端 + 本地服务
pnpm dev:web-wasm # 纯浏览器 Web WASM
pnpm dev:serve # 仅 CLI 服务端 pnpm dev:serve # 仅 CLI 服务端
pnpm docs:dev # 文档站 pnpm docs:dev # 文档站
``` ```
+3 -3
View File
@@ -11,7 +11,7 @@
"files": [ "files": [
"bin/", "bin/",
"dist/", "dist/",
"dist-web/", "dist-cli-web/",
"LICENSE" "LICENSE"
], ],
"engines": { "engines": {
@@ -36,8 +36,8 @@
"scripts": { "scripts": {
"cli": "node scripts/ensure-native.mjs && tsx src/cli.ts", "cli": "node scripts/ensure-native.mjs && tsx src/cli.ts",
"build": "tsup", "build": "tsup",
"build:full": "pnpm -w run build:web && pnpm run build && pnpm run prepare:web", "build:full": "pnpm -w run build:cli-web && pnpm run build && pnpm run prepare:cli-web",
"prepare:web": "rm -rf dist-web && cp -r ../../dist-web dist-web", "prepare:cli-web": "rm -rf dist-cli-web && cp -r ../../dist-cli-web dist-cli-web",
"ensure-native": "node scripts/ensure-native.mjs", "ensure-native": "node scripts/ensure-native.mjs",
"rebuild-native": "bash scripts/rebuild-native.sh", "rebuild-native": "bash scripts/rebuild-native.sh",
"rebuild-sqlite": "npm rebuild better-sqlite3", "rebuild-sqlite": "npm rebuild better-sqlite3",
+2 -2
View File
@@ -140,11 +140,11 @@ program
let webRoot: string | undefined let webRoot: string | undefined
if (!options.headless) { if (!options.headless) {
const webDir = resolveCliPath('dist-web') const webDir = resolveCliPath('dist-cli-web')
if (fs.existsSync(webDir)) { if (fs.existsSync(webDir)) {
webRoot = webDir webRoot = webDir
} else { } else {
console.warn('Warning: dist-web/ not found, starting in API-only mode') console.warn('Warning: dist-cli-web/ not found, starting in API-only mode')
} }
} }
+2 -2
View File
@@ -46,7 +46,7 @@ export interface HttpServerOptions {
port?: number port?: number
host?: string host?: string
token?: string token?: string
/** dist-web/ 目录路径,启用后托管 Web SPA 静态资源 */ /** dist-cli-web/ 目录路径,启用后托管 CLI Web SPA 静态资源 */
webRoot?: string webRoot?: string
/** When true, /_web/* also requires Bearer token (for server/headless deployments) */ /** When true, /_web/* also requires Bearer token (for server/headless deployments) */
requireAuth?: boolean requireAuth?: boolean
@@ -202,7 +202,7 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{
if (options?.webRoot && fs.existsSync(options.webRoot)) { if (options?.webRoot && fs.existsSync(options.webRoot)) {
// 注册反向代理:将 /_proxy/chatlab.fun/* 转发至 https://chatlab.fun, // 注册反向代理:将 /_proxy/chatlab.fun/* 转发至 https://chatlab.fun,
// 行为与 vite dev proxy 一致,解决浏览器 CORS 问题(见 vite.web.config.mts:138-144)。 // 行为与 vite dev proxy 一致,解决浏览器 CORS 问题(见 vite.cli-web.config.mts)。
// 必须在 @fastify/static 之前注册,确保显式路由优先于静态文件/SPA fallback。 // 必须在 @fastify/static 之前注册,确保显式路由优先于静态文件/SPA fallback。
registerProxyRoutes(server) registerProxyRoutes(server)
const fastifyStatic = await import('@fastify/static') const fastifyStatic = await import('@fastify/static')
+3 -7
View File
@@ -2,7 +2,7 @@
* 反向代理路由 — /_proxy/chatlab.fun/* * 反向代理路由 — /_proxy/chatlab.fun/*
* *
* 在 Web 生产模式(chatlab start)下,将 /_proxy/chatlab.fun/* 代理到 * 在 Web 生产模式(chatlab start)下,将 /_proxy/chatlab.fun/* 代理到
* https://chatlab.fun,行为与 vite.web.config.mts 中的 dev proxy 保持一致, * https://chatlab.fun,行为与 vite.cli-web.config.mts 中的 dev proxy 保持一致,
* 以解决浏览器 CORS 限制。 * 以解决浏览器 CORS 限制。
* *
* 目标 host 硬编码为 https://chatlab.fun,不允许外部指定,避免开放代理风险。 * 目标 host 硬编码为 https://chatlab.fun,不允许外部指定,避免开放代理风险。
@@ -15,12 +15,8 @@ const PROXY_TARGET = 'https://chatlab.fun'
export function registerProxyRoutes(server: FastifyInstance): void { export function registerProxyRoutes(server: FastifyInstance): void {
server.get<{ Params: { '*': string } }>('/_proxy/chatlab.fun/*', async (request, reply) => { server.get<{ Params: { '*': string } }>('/_proxy/chatlab.fun/*', async (request, reply) => {
const subPath = request.params['*'] ?? '' const subPath = request.params['*'] ?? ''
const queryString = new URLSearchParams( const queryString = new URLSearchParams(request.query as Record<string, string>).toString()
request.query as Record<string, string>, const targetUrl = queryString ? `${PROXY_TARGET}/${subPath}?${queryString}` : `${PROXY_TARGET}/${subPath}`
).toString()
const targetUrl = queryString
? `${PROXY_TARGET}/${subPath}?${queryString}`
: `${PROXY_TARGET}/${subPath}`
try { try {
const upstream = await fetch(targetUrl, { const upstream = await fetch(targetUrl, {
+14 -4
View File
@@ -28,12 +28,14 @@ pnpm install
| 命令 | 用途 | | 命令 | 用途 |
| --- | --- | | --- | --- |
| `pnpm dev` | 交互式选择开发目标 | | `pnpm dev` | 交互式选择 Desktop、CLI Web、CLI Web 的 API Server、Web WASM 或文档站开发目标 |
| `pnpm dev:desktop` | 启动 Electron 桌面端开发模式 | | `pnpm dev:desktop` | 启动 Electron 桌面端开发模式 |
| `pnpm dev:web` | 启动 CLI Web UI 开发模式 | | `pnpm dev:cli-web` | 启动 CLI Web(Node 后端 + Web UI)开发模式,默认访问 `http://127.0.0.1:3100/` |
| `pnpm dev:web-wasm` | 启动 Web WASM(纯浏览器运行),默认访问 `http://127.0.0.1:3130/` |
| `pnpm docs:dev` | 启动公开文档站开发模式 | | `pnpm docs:dev` | 启动公开文档站开发模式 |
| `pnpm build:desktop` | 构建桌面端 | | `pnpm build:desktop` | 构建桌面端 |
| `pnpm build:web` | 构建 Web UI | | `pnpm build:cli-web` | 构建 CLI Web UI |
| `pnpm build:web-wasm` | 构建 Web WASM |
| `pnpm docs:build` | 构建公开文档站 | | `pnpm docs:build` | 构建公开文档站 |
| `pnpm run type-check:all` | 运行前端和 Node 侧类型检查 | | `pnpm run type-check:all` | 运行前端和 Node 侧类型检查 |
| `pnpm lint` | 运行 ESLint 并自动修复 | | `pnpm lint` | 运行 ESLint 并自动修复 |
@@ -41,6 +43,14 @@ pnpm install
小范围改动优先对修改文件或相关子项目做定向检查;跨模块、发布或架构类改动再跑全量检查。 小范围改动优先对修改文件或相关子项目做定向检查;跨模块、发布或架构类改动再跑全量检查。
## 平台术语
- **CLI Web**:由 `chatlab start` 运行,包含 Node.js 后端和 Web UI。
- **Web WASM**:无 Node.js 后端,解析、存储和查询均在浏览器内运行。
- “Web”是两者的上位概念。上下文无法区分时,默认指 **Web WASM**。
- “后端”或“API Server”只指 Node.js 进程,不等于完整的 CLI Web。
- **Browser Runtime** 只表示 Worker、OPFS、SQLite WASM 和浏览器 adapter 等技术能力,不是另一个平台名称。
## 目录职责 ## 目录职责
| 路径 | 职责 | | 路径 | 职责 |
@@ -58,7 +68,7 @@ pnpm install
## 架构边界 ## 架构边界
ChatLab 同时维护 Electron 桌面端和 CLI Web 端。涉及共享业务逻辑时,优先把逻辑放到 `packages/node-runtime/src/services/` 或 `packages/core/`,入口层只做薄适配。 ChatLab 同时维护 Electron 桌面端、CLI Web 和 Web WASM。涉及共享业务逻辑时,优先把逻辑放到 `packages/node-runtime/src/services/` 或 `packages/core/`,入口层只做薄适配。
- 不要在 Electron IPC handler 或 CLI HTTP route 中重复实现复杂业务流程。 - 不要在 Electron IPC handler 或 CLI HTTP route 中重复实现复杂业务流程。
- 不要在入口层绕过 `packages/core/` 直接写成员合并、删除、别名更新等核心 SQL 写操作。 - 不要在入口层绕过 `packages/core/` 直接写成员合并、删除、别名更新等核心 SQL 写操作。
+14 -4
View File
@@ -28,12 +28,14 @@ pnpm install
| Command | Purpose | | Command | Purpose |
| --- | --- | | --- | --- |
| `pnpm dev` | Select a development target interactively | | `pnpm dev` | Select Desktop, CLI Web, Web WASM, API Server, or docs interactively |
| `pnpm dev:desktop` | Start the Electron desktop app in development mode | | `pnpm dev:desktop` | Start the Electron desktop app in development mode |
| `pnpm dev:web` | Start the CLI Web UI in development mode | | `pnpm dev:cli-web` | Start CLI Web (Node backend + Web UI) in development mode at `http://127.0.0.1:3100/` by default |
| `pnpm dev:web-wasm` | Start Web WASM (browser-only runtime) at `http://127.0.0.1:3130/` by default |
| `pnpm docs:dev` | Start the public docs site locally | | `pnpm docs:dev` | Start the public docs site locally |
| `pnpm build:desktop` | Build the desktop app | | `pnpm build:desktop` | Build the desktop app |
| `pnpm build:web` | Build the Web UI | | `pnpm build:cli-web` | Build the CLI Web UI |
| `pnpm build:web-wasm` | Build Web WASM |
| `pnpm docs:build` | Build the public docs site | | `pnpm docs:build` | Build the public docs site |
| `pnpm run type-check:all` | Run both web and Node type checks | | `pnpm run type-check:all` | Run both web and Node type checks |
| `pnpm lint` | Run ESLint with auto-fix | | `pnpm lint` | Run ESLint with auto-fix |
@@ -41,6 +43,14 @@ pnpm install
For small changes, prefer targeted checks for the files or package you changed. For cross-module, release, or architecture changes, run the broader checks. For small changes, prefer targeted checks for the files or package you changed. For cross-module, release, or architecture changes, run the broader checks.
## Platform Terminology
- **CLI Web** runs through `chatlab start` and includes a Node.js backend plus the Web UI.
- **Web WASM** has no Node.js backend; parsing, storage, and queries run in the browser.
- "Web" is the umbrella term. When context cannot distinguish the platform, it defaults to **Web WASM**.
- "Backend" and "API Server" refer only to the Node.js process, not the complete CLI Web platform.
- **Browser Runtime** refers only to technical capabilities such as Workers, OPFS, SQLite WASM, and browser adapters; it is not another platform name.
## Repository Structure ## Repository Structure
| Path | Responsibility | | Path | Responsibility |
@@ -58,7 +68,7 @@ For small changes, prefer targeted checks for the files or package you changed.
## Architecture Boundaries ## Architecture Boundaries
ChatLab maintains both an Electron desktop app and a CLI Web app. When changing shared business behavior, put the logic in `packages/node-runtime/src/services/` or `packages/core/` first, and keep entry points thin. ChatLab maintains the Electron desktop app, CLI Web, and Web WASM. When changing shared business behavior, put the logic in `packages/node-runtime/src/services/` or `packages/core/` first, and keep entry points thin.
- Do not duplicate complex business flows inside Electron IPC handlers or CLI HTTP routes. - Do not duplicate complex business flows inside Electron IPC handlers or CLI HTTP routes.
- Do not bypass `packages/core/` in entry points to write core SQL operations such as member merge, delete, or alias updates. - Do not bypass `packages/core/` in entry points to write core SQL operations such as member merge, delete, or alias updates.
+2 -2
View File
@@ -34,10 +34,10 @@
"build:win": "pnpm --filter @openchatlab/desktop build:win", "build:win": "pnpm --filter @openchatlab/desktop build:win",
"build:skill-validator": "node scripts/build-chatlab-skill-validator.mjs", "build:skill-validator": "node scripts/build-chatlab-skill-validator.mjs",
"dev:serve": "bash scripts/dev-serve.sh", "dev:serve": "bash scripts/dev-serve.sh",
"dev:web": "pnpm run ensure:server-native && CHATLAB_AUTO_SERVE=1 vite --config vite.web.config.mts", "dev:cli-web": "pnpm run ensure:server-native && CHATLAB_AUTO_SERVE=1 vite --config vite.cli-web.config.mts",
"dev:web-wasm": "vite --config vite.web-wasm.config.mts", "dev:web-wasm": "vite --config vite.web-wasm.config.mts",
"ensure:server-native": "pnpm --filter chatlab-cli run ensure-native", "ensure:server-native": "pnpm --filter chatlab-cli run ensure-native",
"build:web": "vite build --config vite.web.config.mts", "build:cli-web": "vite build --config vite.cli-web.config.mts",
"build:web-wasm": "vite build --config vite.web-wasm.config.mts", "build:web-wasm": "vite build --config vite.web-wasm.config.mts",
"docs:dev": "pnpm --filter @openchatlab/docs dev", "docs:dev": "pnpm --filter @openchatlab/docs dev",
"docs:build": "pnpm --filter @openchatlab/docs build", "docs:build": "pnpm --filter @openchatlab/docs build",
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* Merge routes — shared across CLI, Electron, and web-serve. * Merge routes — shared across CLI, Electron, and CLI Web.
* *
* Parse supports dual-mode: * Parse supports dual-mode:
* - multipart/form-data → file upload (web/CLI) * - multipart/form-data → file upload (web/CLI)
+11 -1
View File
@@ -8,7 +8,17 @@ import ts from 'typescript'
const repoRoot = path.resolve(import.meta.dirname, '..') const repoRoot = path.resolve(import.meta.dirname, '..')
const sourceExtensions = new Set(['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx', '.vue']) const sourceExtensions = new Set(['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx', '.vue'])
const ignoredDirectories = new Set(['.docs', '.git', 'coverage', 'dist', 'dist-web', 'node_modules', 'out', 'target']) const ignoredDirectories = new Set([
'.docs',
'.git',
'coverage',
'dist',
'dist-cli-web',
'dist-web-wasm',
'node_modules',
'out',
'target',
])
const ignoredPaths = new Set([ const ignoredPaths = new Set([
path.join(repoRoot, 'apps', 'desktop', 'native'), path.join(repoRoot, 'apps', 'desktop', 'native'),
path.join(repoRoot, 'docs', '.vitepress', 'cache'), path.join(repoRoot, 'docs', '.vitepress', 'cache'),
+3 -2
View File
@@ -19,8 +19,9 @@ const cacheFile = join(rootDir, 'node_modules', '.cache', 'dev-mode')
const options = [ const options = [
{ key: 'desktop', label: 'Desktop (Electron)', command: 'pnpm run dev:desktop' }, { key: 'desktop', label: 'Desktop (Electron)', command: 'pnpm run dev:desktop' },
{ key: 'web', label: 'Web (CLI start + frontend)', command: 'pnpm run dev:web' }, { key: 'cli-web', label: 'CLI Web (Node + frontend)', command: 'pnpm run dev:cli-web' },
{ key: 'server', label: 'Server Only (API backend)', command: 'pnpm run dev:serve' }, { key: 'server', label: 'API Server (CLI Web backend only)', command: 'pnpm run dev:serve' },
{ key: 'web-wasm', label: 'Web WASM (Browser only)', command: 'pnpm run dev:web-wasm' },
{ key: 'docs', label: 'Docs (VitePress)', command: 'pnpm run docs:dev' }, { key: 'docs', label: 'Docs (VitePress)', command: 'pnpm run docs:dev' },
] ]
+2 -1
View File
@@ -11,7 +11,8 @@ const SKIP_DIRS = new Set([
'.docs', '.docs',
'node_modules', 'node_modules',
'dist', 'dist',
'dist-web', 'dist-cli-web',
'dist-web-wasm',
'out', 'out',
'build', 'build',
'coverage', 'coverage',
@@ -1,13 +1,13 @@
/** /**
* Web 版 Vite 构建配置 * CLI Web Vite 构建配置
* *
* 用于构建 CLI Web 的 SPA 前端(不含 Electron 依赖)。 * 用于构建 CLI Web 的 SPA 前端(不含 Electron 依赖)。
* 输出到 dist-web/,由 chatlab start 托管。 * 输出到 dist-cli-web/,由 chatlab start 托管。
* *
* 与 Electron renderer 构建的关键区别: * 与 Electron renderer 构建的关键区别:
* - __IS_ELECTRON__ = false(使用 FetchAdapter 而非 window.chatApi) * - __IS_ELECTRON__ = false(使用 FetchAdapter 而非 window.chatApi)
* - 不包含 apps/desktop/preload 和 apps/desktop/main * - 不包含 apps/desktop/preload 和 apps/desktop/main
* - 输出目录独立(dist-web/ vs out/renderer/) * - 输出目录独立(dist-cli-web/ vs out/renderer/)
*/ */
import { resolve } from 'path' import { resolve } from 'path'
@@ -54,7 +54,7 @@ async function isChatlabBackendResponsive(port: number, timeoutMs = 800): Promis
/** /**
* 自动启动 chatlab start 后端的插件 * 自动启动 chatlab start 后端的插件
* 仅在 CHATLAB_AUTO_SERVE=1 时生效(由 dev:web 脚本设置) * 仅在 CHATLAB_AUTO_SERVE=1 时生效(由 dev:cli-web 脚本设置)
*/ */
function chatlabServePlugin(): Plugin { function chatlabServePlugin(): Plugin {
let serverProcess: ChildProcess | null = null let serverProcess: ChildProcess | null = null
@@ -106,7 +106,7 @@ function chatlabServePlugin(): Plugin {
return return
} }
throw new Error( throw new Error(
`[chatlab start] Port ${BACKEND_PORT} is in use, but ChatLab API did not respond. Stop the stale process and restart dev:web.` `[chatlab start] Port ${BACKEND_PORT} is in use, but ChatLab API did not respond. Stop the stale process and restart dev:cli-web.`
) )
} }
@@ -155,7 +155,7 @@ export default defineConfig({
}, },
define: { define: {
__IS_ELECTRON__: JSON.stringify(false), __IS_ELECTRON__: JSON.stringify(false),
__IS_BROWSER_STANDALONE__: JSON.stringify(false), __IS_WEB_WASM__: JSON.stringify(false),
__APP_VERSION__: JSON.stringify(JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')).version), __APP_VERSION__: JSON.stringify(JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')).version),
}, },
plugins: [ plugins: [
@@ -172,7 +172,7 @@ export default defineConfig({
chatlabServePlugin(), chatlabServePlugin(),
], ],
build: { build: {
outDir: resolve(__dirname, 'dist-web'), outDir: resolve(__dirname, 'dist-cli-web'),
emptyOutDir: true, emptyOutDir: true,
sourcemap: false, sourcemap: false,
rollupOptions: { rollupOptions: {
@@ -191,10 +191,11 @@ export default defineConfig({
}, },
}, },
server: { server: {
host: '127.0.0.1',
port: 3100, port: 3100,
proxy: { proxy: {
'/_web': `http://localhost:${BACKEND_PORT}`, '/_web': `http://127.0.0.1:${BACKEND_PORT}`,
'/api': `http://localhost:${BACKEND_PORT}`, '/api': `http://127.0.0.1:${BACKEND_PORT}`,
'/_proxy/chatlab.fun': { '/_proxy/chatlab.fun': {
target: 'https://chatlab.fun', target: 'https://chatlab.fun',
changeOrigin: true, changeOrigin: true,