vite.config.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { defineConfig } from "vite";
  2. // import { darkTheme } from "./src/theme.js";
  3. import vue from "@vitejs/plugin-vue";
  4. import Components from "unplugin-vue-components/vite";
  5. import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
  6. import svgLoader from "vite-svg-loader";
  7. import path from "path";
  8. import basicSsl from "@vitejs/plugin-basic-ssl";
  9. // DMS token 的读取口径收在 harness/tools/dms-token.mjs(与本仓库的 DMS 脚本共用一份):
  10. // **显式参数 → 环境变量 → 本机文件 `.dms-token`**。
  11. // 关键是「每个请求现读」而不是启动时读一次 —— token 约 24 小时过期,以前换一次就要重启 dev server。
  12. import { DMS_TOKEN_FILE, readDmsToken, parseJwtPayload, describeDmsToken } from "./harness/tools/dms-token.mjs";
  13. // [已移除] dev 期自己签 NLS token 的中间件(`/nls-token/`)。
  14. // 2026-09-24 起 token 改由**后端网关**提供:`GET http://121.43.55.7:10011/proxy/verify/getALYToken`
  15. // → `{code:200, content:"<token>"}`,前端走同源 `/api/` 代理拿(2026-09-29 前叫 `/aly-api/`)。
  16. // 于是前端侧**不再需要 AccessKeySecret**,vite 也不必持凭据。
  17. // AK/SK 自签的能力仍留在 harness/tools/nls-token.mjs(工具侧退路,仅在没有网关时用)。
  18. /** 同一个状态只提醒一次,避免每个请求都刷屏 */
  19. let dmsTokenWarnedFor = "";
  20. /**
  21. * token 有问题时给出**明确**提示 —— 以前 token 过期是**静默失败**(DMS 写入不生效、
  22. * 只有一条埋在别处控制台里的 warn),排查「为什么数据没存上」要花很久。
  23. */
  24. const warnDmsTokenIfNeeded = (token: string): void => {
  25. if (!token) {
  26. if (dmsTokenWarnedFor === "missing") return;
  27. dmsTokenWarnedFor = "missing";
  28. console.warn(
  29. `\n[dms] ⚠️ 没有 DMS token,DMS 读写(会话/问答记录/反馈)会失败,聊天本身不受影响。\n` +
  30. `[dms] 最省事:在浏览器里复制 token,然后跑 \`npm run dms:token\`(从剪贴板读)。\n` +
  31. `[dms] 文件位置:${DMS_TOKEN_FILE}\n`
  32. );
  33. return;
  34. }
  35. const exp = Number(parseJwtPayload(token)?.exp);
  36. if (!Number.isFinite(exp)) return;
  37. const leftMs = exp * 1000 - Date.now();
  38. const at = new Date(exp * 1000).toLocaleString("zh-CN");
  39. if (leftMs <= 0) {
  40. if (dmsTokenWarnedFor === `expired:${exp}`) return;
  41. dmsTokenWarnedFor = `expired:${exp}`;
  42. console.warn(
  43. `\n[dms] ⚠️ DMS token 已于 ${at} 过期(DMS 读写会失败)。复制新 token 后跑 \`npm run dms:token\`。\n`
  44. );
  45. return;
  46. }
  47. if (leftMs < 2 * 3600 * 1000) {
  48. const key = `soon:${exp}`;
  49. if (dmsTokenWarnedFor === key) return;
  50. dmsTokenWarnedFor = key;
  51. console.warn(`\n[dms] ⏳ DMS token 将于 ${at} 过期(还剩 ${(leftMs / 3600000).toFixed(1)} 小时)。\n`);
  52. }
  53. };
  54. // https://vitejs.dev/config/
  55. export default defineConfig(() => {
  56. return {
  57. plugins: [
  58. vue(),
  59. Components({
  60. resolvers: [AntDesignVueResolver()],
  61. }),
  62. svgLoader(),
  63. basicSsl(),
  64. ],
  65. server: {
  66. proxy: {
  67. // ── 代理总原则(2026-09-29 用户指定)────────────────────────────────
  68. // 前缀**尽量与服务真实路径一致**,只有服务端本身不带前缀时才剥:
  69. // /oauth/** → 121.43.55.7:10086,**直通**(真实路径就带 /oauth)
  70. // /dms/** → 121.43.55.7:10081,**直通**(真实路径就带 /dms)
  71. // /api/** → 121.43.55.7:10011,**剥掉 /api**(网关真实路径是 /proxy/verify/…,
  72. // 实测直连 /api/proxy/… 是 404)
  73. // /chat-api/** → 聊天后端,剥前缀(后端真实路径是 /api/chat,与前端前缀不同名)
  74. // 三台后端都只有 HTTP,而 dev server 是 HTTPS ⇒ 直连会被浏览器按混合内容拦掉,
  75. // 必须由服务端这一跳(dev 是 vite 代理,生产是 nginx)代劳。
  76. // 2026-09-29 之前是 /dms-api(补 /dms) + /ip-api + /aly-api(两条打同一台) —— 现在合并。
  77. //
  78. // ⚠️ **target 直接写在这里,不从环境变量读**(用户 2026-09-29 明确要求
  79. // 「vite.config.ts 里面不要加环境变量」)—— 所以本文件不再 `loadEnv`。
  80. // 换地址就改这四处;`.env.*` 只管**产物里**用到的值(VITE_DMS_API 等同源前缀)。
  81. // 后端网关 121.43.55.7:10011:语音识别 token(/proxy/verify/getALYToken)
  82. // 与取本机 IP(/proxy/verify/getIPByBrowser)**在同一台**,所以共用这一条。
  83. "/api/": {
  84. target: "http://121.43.55.7:10011",
  85. changeOrigin: true,
  86. rewrite: (path) => path.replace(/^\/api/, ""),
  87. },
  88. // DMS 登录服务 121.43.55.7:10086(真实路径带 /oauth,所以直通、不改写)。
  89. // 前端目前不直接调它(token 是 node 侧换的);留着是为了本地能自测登录接口。
  90. "/oauth/": {
  91. target: "http://121.43.55.7:10086",
  92. changeOrigin: true,
  93. },
  94. // DMS 数据管理服务(会话/问答记录/反馈)——真实路由都在 /dms/ 下,前缀同名,直通。
  95. // token(DMS 的鉴权头名就叫 token,不是 Authorization):
  96. // ① **前端自己带**:未登录访客用服务账号现换一个(`src/network/api/dms/service-token.ts`,
  97. // 2026-09-29 用户要求改成这套)—— 这种请求**代理不覆盖**它的 token;
  98. // ② 请求没带时才由代理兜底注入:读仓库根 `.dms-token`(gitignored)或进程环境变量
  99. // DMS_TOKEN(`readDmsToken()` 自己会读,见 harness/tools/dms-token.mjs)
  100. // —— 命令行走代理排查(curl / verify-dms-*.mjs)靠的就是这条。
  101. // 现读而非静态 headers:换 token 不用重启 dev server。
  102. "/dms/": {
  103. target: "http://121.43.55.7:10081",
  104. changeOrigin: true,
  105. configure: (proxy) => {
  106. proxy.on("proxyReq", (proxyReq) => {
  107. // 前端已经带了 token 就别覆盖(否则前端的刷新逻辑白做)
  108. const fromClient =
  109. typeof proxyReq.getHeader === "function" ? proxyReq.getHeader("token") : undefined;
  110. if (fromClient) return;
  111. const token = readDmsToken();
  112. if (token) {
  113. proxyReq.setHeader("token", token);
  114. }
  115. warnDmsTokenIfNeeded(token);
  116. });
  117. },
  118. },
  119. // 新版聊天后端(POST /api/chat,SSE):后端真实路径是 /api/chat,与前端前缀不同名 ⇒ 剥前缀。
  120. // 对应 .env.development 中 VITE_CHAT_API="/chat-api"。
  121. "/chat-api/": {
  122. target: "http://192.168.2.23:8000",
  123. changeOrigin: true,
  124. rewrite: (path) => path.replace(/^\/chat-api/, ""),
  125. },
  126. // [已移除] "/stream/" 代理(数字人视频流):src/index.html 里没有任何代码往它发请求,
  127. // 相关 env(VITE_STREAM_SERVER / VITE_STREAM_TOKEN / VITE_HUMAN_IDLE_MP4_URL)也已删除。
  128. // [已移除] "/asr/" 代理(https://human-screen-v3.metamaker.cn):旧厂商 ASR 的退路,
  129. // 2026-09-24 按用户要求把旧实现连同 env(VITE_ASR / VITE_XF_ASR)一起删除;
  130. // 现在的语音识别走阿里云 NLS(见 harness/docs/reference/asr-nls.md)。
  131. },
  132. },
  133. build: {
  134. rollupOptions: {
  135. input: {
  136. main: path.resolve(__dirname, "index.html"),
  137. redirect: path.resolve(__dirname, "redirect.html"),
  138. },
  139. output: {
  140. manualChunks: (id) => {
  141. console.log(id);
  142. if (id.includes("node_modules/three")) {
  143. return "three";
  144. } else if (id.includes("node_modules")) {
  145. return "vendor";
  146. }
  147. },
  148. },
  149. },
  150. assetsInlineLimit: 20480,
  151. },
  152. resolve: {
  153. alias: {
  154. "@": path.resolve("./src/"),
  155. three$: path.resolve(__dirname, "./node_modules/three/build/three.cjs"),
  156. },
  157. },
  158. base: "./",
  159. css: {
  160. preprocessorOptions: {
  161. scss: {
  162. api: "modern-compiler",
  163. },
  164. },
  165. },
  166. };
  167. });