123ba0d0fa
- auth/jwt/header 认证流程更新,会话与令牌处理增强 - 通信(ChatWindow/ConversationList/Network)更新 - tauri backend.rs/remote.rs、tauri.conf.json 更新 - pineagents app 路由(agent_gate/opc)与配置更新
300 lines
12 KiB
TypeScript
300 lines
12 KiB
TypeScript
/// <reference types="vitest" />
|
||
import { defineConfig, loadEnv, type Plugin } from "vite";
|
||
import react from "@vitejs/plugin-react";
|
||
import path from "path";
|
||
import fs from "fs";
|
||
|
||
/**
|
||
* 从项目根目录 VERSION 文件读取版本号。
|
||
* 唯一真实来源:agent-desktop/VERSION
|
||
*/
|
||
function readAppVersion(): string {
|
||
try {
|
||
const versionPath = path.resolve(__dirname, "../VERSION");
|
||
if (fs.existsSync(versionPath)) {
|
||
const version = fs.readFileSync(versionPath, "utf-8").trim();
|
||
if (version) return version;
|
||
}
|
||
} catch {
|
||
// 读取失败时回退
|
||
}
|
||
return "0.0.0";
|
||
}
|
||
|
||
const APP_VERSION = readAppVersion();
|
||
|
||
// Vitest-only plugin: transforms .css imports inside node_modules to empty
|
||
// stubs. This prevents errors from packages like @agentscope-ai/icons that
|
||
// import CSS.
|
||
//
|
||
// It must never run for real builds: stubbing node_modules CSS also strips
|
||
// monaco-editor's stylesheet, which makes the hidden `.monaco-editor
|
||
// .inputarea` textarea render with browser default styles (a big white box
|
||
// over the code) and breaks cursor positioning in Coding Mode.
|
||
const cssStubPlugin: Plugin = {
|
||
name: "css-stub",
|
||
transform(_code: string, id: string) {
|
||
if (id.includes("node_modules") && id.endsWith(".css")) {
|
||
return { code: "export default {}" };
|
||
}
|
||
},
|
||
};
|
||
|
||
export default defineConfig(({ command, mode }) => {
|
||
// Vitest resolves the config as a dev server (`serve`) with mode "test",
|
||
// while `vite build --mode test` is a real build that needs real CSS.
|
||
const isVitest = command === "serve" && mode === "test";
|
||
const env = loadEnv(mode, process.cwd(), "");
|
||
// Empty = same-origin; frontend and backend served together, no hardcoded host.
|
||
// Use a dedicated Vite-prefixed key so unrelated shell BASE_URL values don't leak into the build.
|
||
const apiBaseUrl = env.VITE_API_BASE_URL ?? "";
|
||
|
||
return {
|
||
define: {
|
||
VITE_API_BASE_URL: JSON.stringify(apiBaseUrl),
|
||
TOKEN: JSON.stringify(env.TOKEN || ""),
|
||
MOBILE: false,
|
||
// 注入应用版本号,前端首屏即可展示,无需等待后端接口
|
||
__APP_VERSION__: JSON.stringify(APP_VERSION),
|
||
},
|
||
plugins: [react(), ...(isVitest ? [cssStubPlugin] : [])],
|
||
css: {
|
||
modules: {
|
||
localsConvention: "camelCase",
|
||
generateScopedName: "[name]__[local]__[hash:base64:5]",
|
||
},
|
||
preprocessorOptions: {
|
||
less: {
|
||
javascriptEnabled: true,
|
||
},
|
||
},
|
||
},
|
||
resolve: {
|
||
alias: {
|
||
"@": path.resolve(__dirname, "./src"),
|
||
},
|
||
},
|
||
server: {
|
||
host: "0.0.0.0",
|
||
port: 8093,
|
||
proxy: {
|
||
// 企业管理能力端点(/admin/ent/*)属于 server-core(8090),
|
||
// 8088 pineagents 不提供这些接口,须单独代理并去掉 /api 前缀。
|
||
// 必须放在 /api 规则之前(vite proxy 按声明顺序优先匹配更具体路径)。
|
||
"/api/admin/ent": {
|
||
target: "http://localhost:8090",
|
||
changeOrigin: false,
|
||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||
},
|
||
// 算力端点(/api/compute/*)属于 server-core(8090),
|
||
// rbac_compute_pricing prefix=/compute,路径与前端一致,无需 rewrite。
|
||
// 覆盖企业算力、个人余额、用户用量、Key 管理等全部 compute 接口。
|
||
"/api/compute": {
|
||
target: "http://localhost:8090",
|
||
changeOrigin: false,
|
||
},
|
||
// 个人用量明细/趋势(/api/opc/compute/usage/*)属于 server-core(8090),
|
||
// rbac_opc prefix=/opc,须去掉 /api 前缀 → /opc/compute/usage/*。
|
||
"/api/opc/compute/usage": {
|
||
target: "http://localhost:8090",
|
||
changeOrigin: false,
|
||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||
},
|
||
"/api": {
|
||
target: "http://localhost:8088",
|
||
changeOrigin: false,
|
||
},
|
||
// park API 走 dispatcher 的 /park 前缀(park_app 子应用),
|
||
// 直接代理到 server-core 8090 端口(8088 的 pineagents app 只转发 /api 路径)
|
||
"/park": {
|
||
target: "http://localhost:8090",
|
||
changeOrigin: false,
|
||
},
|
||
},
|
||
},
|
||
test: {
|
||
globals: true,
|
||
environment: "jsdom",
|
||
setupFiles: ["./src/test/setup.ts"],
|
||
css: true,
|
||
// all @agentscope-ai/* packages excluded from inline — they are large / have CSS imports
|
||
// aliases below redirect each to a stub or compiled entry
|
||
deps: {
|
||
inline: [/@agentscope-ai\/(?!icons|chat|design)/],
|
||
},
|
||
alias: {
|
||
// Deep subpath imports of @agentscope-ai/chat (e.g. DefaultCards/Audios)
|
||
// must resolve to the real package so vi.mock('...lib/...') can intercept them.
|
||
// MUST be listed before the bare "@agentscope-ai/chat" entry (first match wins).
|
||
"@agentscope-ai/chat/lib/DefaultCards/Audios": path.resolve(
|
||
__dirname,
|
||
"node_modules/@agentscope-ai/chat/lib/DefaultCards/Audios/index.js",
|
||
),
|
||
// chat is aliased to a tiny stub to avoid OOM from the 2.3MB real package
|
||
// Tests that need specific behavior override with vi.mock('@agentscope-ai/chat', factory)
|
||
"@agentscope-ai/chat": path.resolve(__dirname, "src/test/chat-mock.ts"),
|
||
// design is aliased to a stub to avoid hanging from its 3MB lib
|
||
"@agentscope-ai/design": path.resolve(
|
||
__dirname,
|
||
"src/test/design-mock.ts",
|
||
),
|
||
"@agentscope-ai/icons": path.resolve(
|
||
__dirname,
|
||
"src/test/icons-mock.ts",
|
||
),
|
||
"@tauri-apps/api/core": path.resolve(
|
||
__dirname,
|
||
"src/test/tauri-mock.ts",
|
||
),
|
||
"@tauri-apps/plugin-dialog": path.resolve(
|
||
__dirname,
|
||
"src/test/tauri-mock.ts",
|
||
),
|
||
},
|
||
exclude: [
|
||
"**/node_modules/**",
|
||
"**/dist/**",
|
||
// 旧测试用 node:test,与 vitest 不兼容,待迁移
|
||
"**/testConnectionMessage.test.ts",
|
||
// ChatPage test causes worker crash - pre-existing issue, needs more mock setup
|
||
"**/pages/Chat/ChatPage.test.tsx",
|
||
// Tauri modules require @tauri-apps/api which only exists in desktop builds
|
||
"**/src/tauri/**",
|
||
],
|
||
coverage: {
|
||
provider: "v8",
|
||
reporter: ["text", "html", "json", "json-summary", "lcov", "cobertura"],
|
||
include: ["src/**/*.{ts,tsx}"],
|
||
exclude: [
|
||
"src/test/**",
|
||
"src/tauri/**",
|
||
"src/**/*.d.ts",
|
||
"src/main.tsx",
|
||
"src/vite-env.d.ts",
|
||
],
|
||
thresholds: {
|
||
statements: 5,
|
||
branches: 4,
|
||
functions: 3,
|
||
lines: 5,
|
||
},
|
||
},
|
||
},
|
||
optimizeDeps: {
|
||
// @agentscope-ai/design 的 FileIcon 组件用 import 导入 SVG(用作 <img src>)。
|
||
// 预构建时 esbuild 默认不识别 .svg 扩展名,会报错 "No loader is configured"。
|
||
// 用 dataurl loader:把 SVG 内联为 data URL,兼容 macOS/Windows,
|
||
// 不需要输出路径(file loader 在 Windows 下会报 "Cannot use the file loader
|
||
// without an output path")。
|
||
// 注意:不能用 optimizeDeps.exclude 排除整个库,那会导致它和所有
|
||
// CommonJS 依赖(classnames/react-is/rc-util 等)的 interop 失效,
|
||
// 出现无穷无尽的 "does not provide an export named 'default'" 错误。
|
||
esbuildOptions: {
|
||
loader: {
|
||
".svg": "dataurl",
|
||
},
|
||
},
|
||
},
|
||
build: {
|
||
// Output to QwenPaw's console directory,
|
||
// so we don't need to copy files manually after build.
|
||
// outDir: path.resolve(__dirname, "../src/pineagents/console"),
|
||
// emptyOutDir: true,
|
||
cssCodeSplit: true,
|
||
sourcemap: mode !== "production",
|
||
// Warn only for chunks above 9MB. The largest chunk is ui-vendor
|
||
// (antd + @agentscope-ai merged, ~8.4MB) which MUST stay a single chunk
|
||
// to avoid circular chunk graphs. The other large chunks are:
|
||
// • ts.worker ~7MB — Monaco TS language-service worker (loaded on demand)
|
||
// • index ~5.6MB — app shell + deps shared by lazy route chunks.
|
||
// A 1MB default would warn on these forever; 9MB still surfaces any
|
||
// genuinely out-of-control new chunk.
|
||
chunkSizeWarningLimit: 9000,
|
||
rollupOptions: {
|
||
onwarn(warning, warn) {
|
||
// Suppress the "dynamic import will not move module into another
|
||
// chunk" noise. These fire because src/pages modules are BOTH:
|
||
// • statically imported — page components legitimately share
|
||
// sub-components (MarketPanel, DetailDrawer, providerIcon, …),
|
||
// • dynamically imported — via dynamicModuleRegistry's
|
||
// import.meta.glob (plugin patch registry) and lazyWithRetry's
|
||
// PAGE_MODULES glob (route code-splitting).
|
||
// When a module is already statically reachable, Rollup simply cannot
|
||
// give it its own chunk; the dynamic import silently reuses the
|
||
// static one. That is intended behaviour here, not an error — the
|
||
// registry glob is what powers window.QwenPaw.modules patching.
|
||
if (
|
||
warning.message?.includes(
|
||
"dynamic import will not move module into another chunk",
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
warn(warning);
|
||
},
|
||
output: {
|
||
manualChunks(id) {
|
||
// React core
|
||
if (
|
||
id.includes("node_modules/react/") ||
|
||
id.includes("node_modules/react-dom/") ||
|
||
id.includes("node_modules/react-router-dom/") ||
|
||
id.includes("node_modules/scheduler/")
|
||
) {
|
||
return "react-vendor";
|
||
}
|
||
// Ant Design + AgentScope design system MUST stay in ONE chunk.
|
||
// antd and @agentscope-ai/design share react-is / rc-util and import
|
||
// each other; splitting them into separate vendor chunks produces a
|
||
// circular chunk graph (ui-vendor ↔ agentscope-vendor) that breaks
|
||
// ES-module init ordering and throws "Cannot access X before
|
||
// initialization" at runtime (white screen). Merged here to avoid
|
||
// those circular deps — the tradeoff is a larger single vendor chunk.
|
||
if (
|
||
id.includes("node_modules/antd/") ||
|
||
id.includes("node_modules/antd-style/") ||
|
||
id.includes("node_modules/@ant-design/") ||
|
||
id.includes("node_modules/@agentscope-ai/")
|
||
) {
|
||
return "ui-vendor";
|
||
}
|
||
// i18n
|
||
if (
|
||
id.includes("node_modules/i18next/") ||
|
||
id.includes("node_modules/react-i18next/")
|
||
) {
|
||
return "i18n-vendor";
|
||
}
|
||
// Markdown rendering
|
||
if (
|
||
id.includes("node_modules/react-markdown/") ||
|
||
id.includes("node_modules/remark-gfm/") ||
|
||
id.includes("node_modules/rehype") ||
|
||
id.includes("node_modules/remark") ||
|
||
id.includes("node_modules/unified/") ||
|
||
id.includes("node_modules/mdast") ||
|
||
id.includes("node_modules/hast") ||
|
||
id.includes("node_modules/micromark")
|
||
) {
|
||
return "markdown-vendor";
|
||
}
|
||
// Drag and drop
|
||
if (id.includes("node_modules/@dnd-kit/")) {
|
||
return "dnd-vendor";
|
||
}
|
||
// Utilities (dayjs, zustand, ahooks, etc.)
|
||
if (
|
||
id.includes("node_modules/dayjs/") ||
|
||
id.includes("node_modules/zustand/") ||
|
||
id.includes("node_modules/ahooks/") ||
|
||
id.includes("node_modules/@vvo/tzdb/")
|
||
) {
|
||
return "utils-vendor";
|
||
}
|
||
},
|
||
},
|
||
},
|
||
},
|
||
};
|
||
});
|