Files
agent-desktop/console/vite.config.ts
T
Pine 123ba0d0fa feat: 桌面端认证/通信/tauri 后端更新 + README
- auth/jwt/header 认证流程更新,会话与令牌处理增强
- 通信(ChatWindow/ConversationList/Network)更新
- tauri backend.rs/remote.rs、tauri.conf.json 更新
- pineagents app 路由(agent_gate/opc)与配置更新
2026-09-11 21:17:48 +08:00

300 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// <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-core8090),
// 8088 pineagents 不提供这些接口,须单独代理并去掉 /api 前缀。
// 必须放在 /api 规则之前(vite proxy 按声明顺序优先匹配更具体路径)。
"/api/admin/ent": {
target: "http://localhost:8090",
changeOrigin: false,
rewrite: (path) => path.replace(/^\/api/, ""),
},
// 算力端点(/api/compute/*)属于 server-core8090),
// rbac_compute_pricing prefix=/compute,路径与前端一致,无需 rewrite。
// 覆盖企业算力、个人余额、用户用量、Key 管理等全部 compute 接口。
"/api/compute": {
target: "http://localhost:8090",
changeOrigin: false,
},
// 个人用量明细/趋势(/api/opc/compute/usage/*)属于 server-core8090),
// 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";
}
},
},
},
},
};
});