import { defineConfig } from "vite"; // import { darkTheme } from "./src/theme.js"; import vue from "@vitejs/plugin-vue"; import Components from "unplugin-vue-components/vite"; import { AntDesignVueResolver } from "unplugin-vue-components/resolvers"; import svgLoader from "vite-svg-loader"; import path from "path"; import basicSsl from "@vitejs/plugin-basic-ssl"; // DMS token 读取口(与仓库 DMS 脚本共用一份实现,harness/tools/dms-token.mjs): // 显式参数 → 环境变量 → 本机文件 `.dms-token`;每个请求现读,换 token 不用重启 dev server。 import { DMS_TOKEN_FILE, readDmsToken, parseJwtPayload, describeDmsToken } from "./harness/tools/dms-token.mjs"; /** 同一个状态只提醒一次,避免每个请求都刷屏 */ let dmsTokenWarnedFor = ""; /** token 缺失/过期时给出明确提示(否则 DMS 写入会静默失败,难排查) */ const warnDmsTokenIfNeeded = (token: string): void => { if (!token) { if (dmsTokenWarnedFor === "missing") return; dmsTokenWarnedFor = "missing"; console.warn( `\n[dms] ⚠️ 没有 DMS token,DMS 读写(会话/问答记录/反馈)会失败,聊天本身不受影响。\n` + `[dms] 最省事:在浏览器里复制 token,然后跑 \`npm run dms:token\`(从剪贴板读)。\n` + `[dms] 文件位置:${DMS_TOKEN_FILE}\n` ); return; } const exp = Number(parseJwtPayload(token)?.exp); if (!Number.isFinite(exp)) return; const leftMs = exp * 1000 - Date.now(); const at = new Date(exp * 1000).toLocaleString("zh-CN"); if (leftMs <= 0) { if (dmsTokenWarnedFor === `expired:${exp}`) return; dmsTokenWarnedFor = `expired:${exp}`; console.warn( `\n[dms] ⚠️ DMS token 已于 ${at} 过期(DMS 读写会失败)。复制新 token 后跑 \`npm run dms:token\`。\n` ); return; } if (leftMs < 2 * 3600 * 1000) { const key = `soon:${exp}`; if (dmsTokenWarnedFor === key) return; dmsTokenWarnedFor = key; console.warn(`\n[dms] ⏳ DMS token 将于 ${at} 过期(还剩 ${(leftMs / 3600000).toFixed(1)} 小时)。\n`); } }; export default defineConfig(() => { return { plugins: [ vue(), Components({ resolvers: [AntDesignVueResolver()], }), svgLoader(), basicSsl(), ], server: { proxy: { // 代理总原则:前缀尽量与服务真实路径一致,只有服务端本身不带前缀时才剥。 // /oauth/** → 121.43.55.7:10086,直通(真实路径就带 /oauth) // /dms/** → 121.43.55.7:10081,直通(真实路径就带 /dms),请求没带 token 时代理注入 // /api/** → 121.43.55.7:10011,剥掉 /api(网关真实路径是 /proxy/verify/…) // /chat-api/** → 聊天后端,剥前缀(后端真实路径是 /api/chat,与前端前缀不同名) // 三台后端都只有 HTTP 而 dev server 是 HTTPS,直连会被浏览器按混合内容拦掉, // 必须由服务端这一跳(dev 是 vite 代理,生产是 nginx)代劳。 // ⚠️ target 直接写在这里、不从环境变量读(所以本文件不 loadEnv),换地址就改这四处。 // 后端网关:语音识别 token(/proxy/verify/getALYToken)与取本机 IP(/proxy/verify/getIPByBrowser) // 在同一台,共用这一条。 "/api/": { target: "http://121.43.55.7:10011", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), }, // DMS 登录服务 121.43.55.7:10086(真实路径带 /oauth,所以直通、不改写): // 未登录访客用服务账号换 DMS token 走这里。 "/oauth/": { target: "http://121.43.55.7:10086", changeOrigin: true, }, // DMS 数据管理服务(会话/问答记录/反馈)——真实路由都在 /dms/ 下,前缀同名,直通。 // 鉴权头名是 token(不是 Authorization):前端自己带(service-token.ts)时**代理不覆盖**, // 只有请求没带时才由代理兜底注入(读仓库根 `.dms-token` 或环境变量 DMS_TOKEN)。 // 每请求现读,所以换 token 不用重启 dev server。 "/dms/": { target: "http://121.43.55.7:10081", changeOrigin: true, configure: (proxy) => { proxy.on("proxyReq", (proxyReq) => { // 前端已经带了 token 就别覆盖 const fromClient = typeof proxyReq.getHeader === "function" ? proxyReq.getHeader("token") : undefined; if (fromClient) return; const token = readDmsToken(); if (token) { proxyReq.setHeader("token", token); } warnDmsTokenIfNeeded(token); }); }, }, // 聊天后端(POST /api/chat,SSE):真实路径是 /api/chat,与前端前缀不同名 ⇒ 剥前缀。 // 对应 .env.development 中 VITE_CHAT_API="/chat-api"。 "/chat-api/": { target: "http://192.168.2.23:8000", changeOrigin: true, rewrite: (path) => path.replace(/^\/chat-api/, ""), }, }, }, build: { rollupOptions: { input: { main: path.resolve(__dirname, "index.html"), }, output: { manualChunks: (id) => { console.log(id); if (id.includes("node_modules/three")) { return "three"; } else if (id.includes("node_modules")) { return "vendor"; } }, }, }, assetsInlineLimit: 20480, }, resolve: { alias: { "@": path.resolve("./src/"), three$: path.resolve(__dirname, "./node_modules/three/build/three.cjs"), }, }, base: "./", css: { preprocessorOptions: { scss: { api: "modern-compiler", }, }, }, }; });