Răsfoiți Sursa

chore: 删除接口清点里的全部死代码(用户:「B 全部删掉」)

按 harness/docs/reference/current-api-call-sites.md §0 B 的清单逐项删除:
- #14 厂商 OSS 上传:useBusinessAssistantUpload.ts 里两个 [已停用] 注释块 +
  四份 env 的 VITE_UPLOAD_SIGN_URL
- #15 旧厂商 ASR:src/three-libs/asr/index.ts、VITE_ASR / VITE_XF_ASR(四份 env)、
  vite 的 /asr/ 代理、index.html 的 globalThis.isXF
- #16 stream-message-coordinator.ts 整个类(~1100 行)+ 随之孤儿的 utils/scope-stream-boundary.ts;
  MessageState 枚举搬到 src/types/record.ts(它是活的,取值未动)
- #17 updateSessionInfo;#18 getModelServerUrl / isKnowledgeApi;#19 四个空目录;
  #20 syncSessionTitleToServer 注释块 + 注释掉的旧协调器 import
- 顺带:因此变成孤儿的 npm 依赖 qs / @microsoft/fetch-event-source 已 npm uninstall

⚠️ 过程中踩坑并已修正:我把 src/components/utils.ts 也误删了 —— 判断方法错了(只 grep 了
import 路径,没 grep 导出符号名),实际 ScrollList.vue / BusinessRecord.vue 直接用它的
sleep / getQueryObj / computedOriginContentPosition;是 npm run build 报
Could not resolve "../utils" 才暴露 → git restore 恢复。教训:删文件要按导出符号名反查,
删完立刻构建。

验证:npm run build 通过;verify-nls-realtime 43/43(「旧 SDK 仍在」的断言反转为「已删除」+
断言旧 env 也清了);附件 57/57、toast 27/27、DMS 字段契约 19/19、DMS 端到端 89/89;
validate-harness 通过。vue-tsc 只剩一条既有错误(tsconfig 列了 webpack-env 但没装
@types/webpack-env,自初始提交起如此,非本次引入);npx jest 0 个用例(仓库无 jest 测试)。

⚠️ 另一处"只 grep src/ 会误判"的更正:VITE_USE_KNOWLEDGE_API 不是死配置 ——
index.html 第 321 行用它切「政策解读」卡片,故保留;清单里已更正。

Co-Authored-By: Claude Code <noreply@anthropic.com>
gongtianxiao 1 lună în urmă
părinte
comite
6783c6957b

+ 0 - 4
.env.development

@@ -2,10 +2,7 @@ VITE_VERSION=${npm_package_version}
 # VITE_API="//192.168.14.25:4006"
 # VITE_API="//human-card-mini.metamaker.cn"
 # VITE_API="//fifth-hospital.test.metamaker.cn"
-# VITE_ASR="wss://fifth-hospital.test.metamaker.cn"
 VITE_API="//qingpu-data-api.metamaker.cn"
-VITE_ASR="https://qingpu-data-api.metamaker.cn"
-VITE_XF_ASR="/asr"
 VITE_APP_KEY="e61a4f61170a4b2f8f88ee946e2e635b"
 VITE_MODEL_SERVER="https://qingpu-data-api.metamaker.cn/llms/qingpu"
 
@@ -35,7 +32,6 @@ VITE_DMS_TARGET="http://121.43.55.7:10081"
 
 # 取 OSS 上传签名的接口(当前为厂商 metamaker 提供,实测无需鉴权)。
 # 换自建签名服务时只改这里(见 harness/docs/reference/current-api-call-sites.md)
-VITE_UPLOAD_SIGN_URL="//qingpu-data-api.metamaker.cn/common/qp_signed_url"
 
 # 取本机 IP 的接口(DMS 访客归属:访客_<IP>,见 src/utils/client-ip.ts)
 # 它是 **HTTP**,而页面是 HTTPS —— 直连会被混合内容拦掉,所以走同源代理

+ 0 - 2
.env.production

@@ -1,6 +1,5 @@
 VITE_VERSION=${npm_package_version}
 VITE_API="//qingpu-data-api.metamaker.cn"
-VITE_ASR="wss://qingpu-data-api.metamaker.cn"
 VITE_DOWNLOAD_URL="https://qingpu-data-api.metamaker.cn/llm/download/"
 # 政策库来源顺序:false = 远端优先、本地兜底(见 index.html)
 VITE_POLICY_LOCAL_FIRST="false"
@@ -14,7 +13,6 @@ VITE_CHAT_API="http://192.168.2.23:8000"
 
 # 取 OSS 上传签名的接口(当前为厂商 metamaker 提供,实测无需鉴权)。
 # 换自建签名服务时只改这里(见 harness/docs/reference/current-api-call-sites.md)
-VITE_UPLOAD_SIGN_URL="//qingpu-data-api.metamaker.cn/common/qp_signed_url"
 
 # 取本机 IP 的接口(DMS 访客归属:访客_<IP>,见 src/utils/client-ip.ts)
 # ⚠️ 它只有 HTTP,页面是 HTTPS —— 生产**必须由 nginx 把 /ip-api/ 转发到

+ 0 - 4
.env.qingpu

@@ -1,8 +1,5 @@
 VITE_VERSION=${npm_package_version}
 VITE_API="//qingpu-data-api.metamaker.cn"
-# VITE_ASR="/asr"
-VITE_ASR="wss://qingpu-data-api.metamaker.cn"
-VITE_XF_ASR="/asr"
 VITE_DOWNLOAD_URL="/api/download/"
 # 政策库来源顺序:false = 远端优先、本地兜底(见 index.html)
 VITE_POLICY_LOCAL_FIRST="false"
@@ -16,7 +13,6 @@ VITE_REDIRECT_MAP={"demo":"https://demo.heijingai.com/FTA-c2p/%23/overview","zha
 
 # 取 OSS 上传签名的接口(当前为厂商 metamaker 提供,实测无需鉴权)。
 # 换自建签名服务时只改这里(见 harness/docs/reference/current-api-call-sites.md)
-VITE_UPLOAD_SIGN_URL="//qingpu-data-api.metamaker.cn/common/qp_signed_url"
 
 # 取本机 IP 的接口(DMS 访客归属:访客_<IP>,见 src/utils/client-ip.ts)
 # ⚠️ 它只有 HTTP,页面是 HTTPS —— 生产**必须由 nginx 把 /ip-api/ 转发到

+ 0 - 3
.env.test

@@ -1,7 +1,5 @@
 VITE_VERSION=${npm_package_version}
 VITE_API="//human-card-mini.metamaker.cn"
-# VITE_ASR="wss://human-screen-v3.test.metamaker.cn"
-VITE_ASR="https://qingpu-data-api.metamaker.cn"
 VITE_APP_KEY="e61a4f61170a4b2f8f88ee946e2e635b"
 VITE_USE_KNOWLEDGE_API=false
 # 政策库来源顺序:false = 远端优先、本地兜底(见 index.html)
@@ -11,7 +9,6 @@ VITE_CHAT_API="http://192.168.2.23:8000"
 
 # 取 OSS 上传签名的接口(当前为厂商 metamaker 提供,实测无需鉴权)。
 # 换自建签名服务时只改这里(见 harness/docs/reference/current-api-call-sites.md)
-VITE_UPLOAD_SIGN_URL="//qingpu-data-api.metamaker.cn/common/qp_signed_url"
 
 # 取本机 IP 的接口(DMS 访客归属:访客_<IP>,见 src/utils/client-ip.ts)
 # ⚠️ 它只有 HTTP,页面是 HTTPS —— 生产**必须由 nginx 把 /ip-api/ 转发到

+ 11 - 0
README.md

@@ -422,6 +422,17 @@
     `harness/tools/get-nls-token.mjs` 与 dev 端点共用;带 5 分钟提前刷新 + 并发去重
   - 验证:端点 200;**拿端点发的 token 真调通一次识别**(`status:20000000 SUCCESS`);
     `npm run build` 后**产物里搜三个凭据值 0 命中**
+- **死代码清理**(用户 2026-09-24「B 全部删掉」,清单见
+  [`harness/docs/reference/current-api-call-sites.md`](harness/docs/reference/current-api-call-sites.md)):
+  - 删掉:厂商 OSS 上传的停用实现(含 `VITE_UPLOAD_SIGN_URL`)、旧厂商 ASR 全套
+    (`three-libs/asr/index.ts` + `VITE_ASR` / `VITE_XF_ASR` + vite `/asr/` 代理 +
+    `index.html` 的 `globalThis.isXF`)、`stream-message-coordinator.ts` 整个类(~1100 行)
+    + 随之孤儿的 `utils/scope-stream-boundary.ts`、`updateSessionInfo`、
+    `getModelServerUrl` / `isKnowledgeApi`、三个空目录、两处停用注释块,
+    以及因此变成孤儿的 npm 依赖 `qs` / `@microsoft/fetch-event-source`
+  - `MessageState` 枚举**是活的**,已从被删的协调器搬到 `src/types/record.ts`(取值未动)
+  - 教训记档:删文件要按**导出的符号名**反查引用(只查 import 路径会漏 ——
+    `components/utils.ts` 因此被误删,构建报错才发现,已恢复)
 - **实时语音识别已切到阿里云 NLS**(2026-09-23,接管旧厂商 SDK):
   - 协议层 `src/three-libs/asr/nls-protocol.ts`(纯函数:帧构建 / 事件折叠 / 16k PCM 编码)
     + 类 `src/three-libs/asr/nls.ts`,**对外契约与旧 SDK 一致** → 两个壳组件只改了 import

+ 1 - 1
harness/docs/architecture.md

@@ -16,7 +16,7 @@
   `src/components/api-chat-coordinator.ts` 翻译成既有渲染组件可识别的内容标记。
   详见 [`reference/api-chat.md`](reference/api-chat.md)。
 - **没有 3D 虚拟人渲染**。`three` 仅用于粒子背景(`business-assistant-particle.ts`);
-  **`src/three-libs/` 下有且只有一个模块:`asr/`,是语音识别,且在用**
+  **`src/three-libs/` 下有且只有一个模块:`asr/`(2026-09-24 起只剩 NLS 实现 `nls.ts` / `nls-protocol.ts`)**
   (PC 与移动端都 `import ASR from '@/three-libs/asr/index'`,点麦克风就走它)。
   现行实现(**阿里云 NLS 直连**,2026-09-23 起)见 [`reference/asr-nls.md`](reference/asr-nls.md);
   换之前那套(厂商 `asr_hub` 中转)的记录见 [`reference/asr-realtime.md`](reference/asr-realtime.md)。

+ 30 - 16
harness/docs/exec-plans/tech-debt-tracker.md

@@ -42,22 +42,6 @@
 - **为什么先欠着**:只影响观感,不影响功能;改模板会扩大改动面。
 - **该还的条件**:需要展示这类提示时。
 
-### 6. 语音识别仍跑在厂商(metamaker)的服务上
-
-- **是什么**:语音输入走 `wss://qingpu-data-api.metamaker.cn/common/asr_hub`
-  (引擎 `aliyun_dashscope`),且**鉴权 token 与 appKey 都硬编码在源码里**
-  (`src/three-libs/asr/index.ts` 的 token:`iss: human-large-screen`,**exp 2034-12-29**,入库可见)。
-- **为什么先欠着**:**用户 2026-09-18 明确「先不动,先用原来的语音识别功能」**——
-  现在能用、无成本、前端零维护;独立的成本主要在「要有人长期维护一个 ASR 网关 + 引擎账号」。
-- **欠着会怎样**:⚠️ **厂商若失效那条 token、或停掉那个网关,语音功能会突然不可用,且没有任何预警**;
-  另外用户语音音频流经厂商服务器(政务场景下涉及数据合规评估)。
-- **该还的条件**:出现以下任一情况就该动 —— ①厂商停服/改价/要求换凭据;②合规要求音频不出自己的服务器;
-  ③要换引擎。**退路已经准备好**:协议契约已存档在
-  [`../reference/current-api-call-sites.md`](../reference/current-api-call-sites.md) 的「ASR 协议契约」,
-  实现方照着就能写自建网关,**前端一行都不用改**(只把 `VITE_ASR` 指向新地址)。
-- **一个低成本的前置动作(尚未做)**:把 `index.html` 里写死的 `globalThis.isXF = false`
-  **改成环境变量开关** —— 这样万一厂商出问题,改配置就能切到讯飞分支,不必改代码重新发版。
-
 ### 7. 移动端真机样式问题:没有兼容基线,且布局依赖 JS 测量
 
 - **是什么**:用户反馈**真机**上(F12 模拟正常)①首页展示不全 ②思考过程文案超出屏幕
@@ -164,6 +148,36 @@
 
 ## 已还
 
+### 语音识别已换阿里云 NLS(2026-09-24 已还)
+
+- **换了什么**:语音识别从厂商 metamaker 的 `asr_hub` 中转,换成**阿里云 NLS 直连**
+  (协议与实现见 [`../reference/asr-nls.md`](../reference/asr-nls.md))。
+- **当初欠的两点都解决了**:① 厂商 token 硬编码(`exp 2034-12-29`)已随旧实现删除;
+  ② token 改由**后端网关** `/proxy/verify/getALYToken` 签发,前端不持有任何云凭据。
+- **旧实现已按用户要求删除**(2026-09-24),要看它原来怎么工作查 git 历史。
+
+<details><summary>当初的欠账记录</summary>
+
+### 6. 语音识别仍跑在厂商(metamaker)的服务上
+
+- **是什么**:语音输入走 `wss://qingpu-data-api.metamaker.cn/common/asr_hub`
+  (引擎 `aliyun_dashscope`),且**鉴权 token 与 appKey 都硬编码在源码里**
+  (`src/three-libs/asr/index.ts` 的 token:`iss: human-large-screen`,**exp 2034-12-29**,入库可见)。
+- **为什么先欠着**:**用户 2026-09-18 明确「先不动,先用原来的语音识别功能」**——
+  现在能用、无成本、前端零维护;独立的成本主要在「要有人长期维护一个 ASR 网关 + 引擎账号」。
+- **欠着会怎样**:⚠️ **厂商若失效那条 token、或停掉那个网关,语音功能会突然不可用,且没有任何预警**;
+  另外用户语音音频流经厂商服务器(政务场景下涉及数据合规评估)。
+- **该还的条件**:出现以下任一情况就该动 —— ①厂商停服/改价/要求换凭据;②合规要求音频不出自己的服务器;
+  ③要换引擎。**退路已经准备好**:协议契约已存档在
+  [`../reference/current-api-call-sites.md`](../reference/current-api-call-sites.md) 的「ASR 协议契约」,
+  实现方照着就能写自建网关,**前端一行都不用改**(只把 `VITE_ASR` 指向新地址)。
+- **一个低成本的前置动作(尚未做)**:把 `index.html` 里写死的 `globalThis.isXF = false`
+  **改成环境变量开关** —— 这样万一厂商出问题,改配置就能切到讯飞分支,不必改代码重新发版。
+
+
+</details>
+
+
 ### 访客行会被企业看到(2026-09-22 已还)
 
 - **当时的问题**:1887 口径改为「`c_credit_code` 只放真实信用代码」后,访客行也带着

+ 3 - 1
harness/docs/reference/asr-realtime.md

@@ -2,7 +2,9 @@
 
 > ⚠️ **2026-09-23 起:语音识别正在换成阿里云 NLS 直连** —— 现行实现看 [`asr-nls.md`](asr-nls.md)。
 > 本文档记录的是**换之前那套**(厂商 metamaker 的 `/common/asr_hub` 中转)。
-> 旧实现代码仍在仓库里(`src/three-libs/asr/index.ts`,是切回去的退路),所以这份记录仍然有效。
+> ⚠️ **旧实现代码已于 2026-09-24 按用户要求删除**(`src/three-libs/asr/index.ts` +
+> `VITE_ASR` / `VITE_XF_ASR` + vite `/asr/` 代理)—— 本文档现在只是**历史记录**,
+> 不再对应仓库里的任何代码;要翻实现请查 git 历史。
 >
 > **整理时间**:2026-09-23 | **依据**:仓库内的实现源码(不是厂商文档,也不是记忆)
 > **性质**:**现状记录 + 接口契约**。厂商 SDK 是本地源码(`src/three-libs/asr/index.ts`,578 行),

+ 18 - 3
harness/docs/reference/current-api-call-sites.md

@@ -29,7 +29,19 @@
 > 📌 **更正一条旧说法**:`VITE_APP_KEY` **不是死配置** —— `index.html` 的 `openid_to_token` 在用。
 > (`asr-realtime.md` §8 曾写它是死配置,因为只 grep 了 `src/`,没看 `index.html`。)
 
-### B. 已停用 / 死代码(**可删**,等你拍板)
+### B. 已停用 / 死代码 —— **✅ 2026-09-24 已按用户要求全部删除**
+
+删除清单与验证见 `progress.md` 续 37。删掉的东西(想看旧实现翻 git 历史):
+厂商 OSS 上传(含 `VITE_UPLOAD_SIGN_URL`)· 旧厂商 ASR(`three-libs/asr/index.ts` + `VITE_ASR`
++ `VITE_XF_ASR` + vite `/asr/` 代理 + `index.html` 的 `globalThis.isXF`)·
+`stream-message-coordinator.ts` 整个类(`MessageState` 枚举已搬到 `src/types/record.ts`)·
+`utils/scope-stream-boundary.ts` · `updateSessionInfo` · `getModelServerUrl` / `isKnowledgeApi` ·
+三个空目录 · `syncSessionTitleToServer` 注释块 · 变成孤儿的 npm 依赖 `qs` /
+`@microsoft/fetch-event-source`(`components/utils.ts` 一度被误删,因为 `ScrollList.vue` /
+`BusinessRecord.vue` 直接用它导出的 `sleep` / `getQueryObj` / `computedOriginContentPosition`,已恢复)。
+
+<details><summary>原清单(2026-09-24 早些时候列的,留作对照)</summary>
+
 
 | # | 东西 | 现状 | 删了会怎样 |
 |---|---|---|---|
@@ -41,6 +53,8 @@
 | 19 | 空目录:`src/three-libs/metamaker`(0 文件)、`global`、`cache`、`assets` | 空壳 | 无 |
 | 20 | `syncSessionTitleToServer()` | 已停用(注释保留) | 无 |
 
+</details>
+
 ### C. 已经删干净的(只作提示,不用动)
 
 - 使用统计埋点(`assistant-statistics.ts` + `VITE_ASSISTANT_STATISTICS_BASE_URL`,2026-09-18 删)
@@ -53,8 +67,9 @@
 `VITE_API`(#8 + index.html 的 modelServer)· `VITE_CHAT_API` · `VITE_DMS_API` · `VITE_IP_API` ·
 `VITE_NLS_TOKEN_API` · `VITE_NLS_APPKEY` · `VITE_DOWNLOAD_URL` · `VITE_POLICY_LOCAL_FIRST` ·
 `VITE_MODEL_SERVER` · `VITE_PARENT_ORIGIN` · `VITE_REDIRECT_MAP` · `VITE_APP_KEY` · `VITE_VERSION` ·
-`VITE_ASR` / `VITE_XF_ASR`(只服务 #15 的退路)· 四个 `*_TARGET`(vite 代理用)· `VITE_USE_KNOWLEDGE_API`
-(经 `globalThis.isKnowledgeApi`,目前只有死代码在用 —— 见 #18)
+四个 `*_TARGET`(vite 代理用)· `VITE_USE_KNOWLEDGE_API`(经 `globalThis.isKnowledgeApi`,
+**`index.html` 在用它切「政策解读」卡片** —— 2026-09-24 更正:只 grep `src/` 会把它误判成死配置)。
+(`VITE_ASR` / `VITE_XF_ASR` 已随旧 ASR 删除。)
 
 ## 一、已经迁到新接口的(不用管)
 

+ 40 - 0
harness/progress.md

@@ -4168,6 +4168,46 @@ payload 里含模型里**不存在**的列时,`addContent` 会忽略该列照
 - dev server 把 `VITE_NLS_APPKEY` 注入到前端模块 ✓(`curl /src/utils/runtime-config.ts` 可见)
 - `npm run build` 通过;`vue-tsc` 无新错误
 
+### 续 37:接口清点后的死代码清理(用户:「B 全部删掉」)
+
+**用户指示**:把我列出的 B 类(7 项死代码 / 已停用接口)**全部删掉**。
+
+**删了什么**(都在 §0 清单里,删完把那节改成「已删」):
+
+| # | 删掉的东西 |
+|---|---|
+| 14 | 厂商 OSS 上传:`useBusinessAssistantUpload.ts` 里两个大的 `[已停用]` 注释块 + 四份 env 的 `VITE_UPLOAD_SIGN_URL` |
+| 15 | 旧厂商 ASR:`src/three-libs/asr/index.ts`、`VITE_ASR` / `VITE_XF_ASR`(四份 env)、vite 的 `/asr/` 代理、`index.html` 的 `globalThis.isXF` |
+| 16 | `stream-message-coordinator.ts` 整个类(~1100 行)+ 随之成孤儿的 `src/utils/scope-stream-boundary.ts`;**`MessageState` 枚举搬到 `src/types/record.ts`**(它是活的,取值一个没动) |
+| 17 | `updateSessionInfo()`(chat-sessions.ts) |
+| 18 | `getModelServerUrl()` / `isKnowledgeApi()`(runtime-config.ts) |
+| 19 | 三个空目录:`three-libs/metamaker`、`global`、`cache`、`assets` |
+| 20 | `syncSessionTitleToServer` 的整段注释 + `useBusinessAssistantChat` 里那条注释掉的旧协调器 import |
+| — | **顺带**:`qs` 与 `@microsoft/fetch-event-source` 两个 npm 依赖因此变成孤儿 → `npm uninstall` |
+
+**踩了一次坑(记录)**:我把 `src/components/utils.ts` 也当成"只有死协调器用"删了 ——
+**判断方法错了**:我只 grep 了 import **路径**(`components/utils'`),没 grep 它导出的**符号名**;
+实际上 `ScrollList.vue` / `BusinessRecord.vue` 直接 `from '../utils'` 用它的
+`sleep` / `getQueryObj` / `computedOriginContentPosition`。是 **`npm run build` 报
+`Could not resolve "../utils"` 才暴露**(删完就该立刻构建,这次正是这么抓到的)→
+`git restore` 恢复,重新构建通过。
+**教训:删文件前要按"导出的符号名"反查引用,不能只查路径;删完立刻构建。**
+
+**运行过的验证**:
+- `npm run build` 通过(失败过一次,见上)
+- `verify-nls-realtime.mjs` **43/43**(把「旧厂商 SDK 仍在」那条断言**反过来**成
+  「已删除」+ 断言旧 env 也清了 —— 免得哪天被误加回来)
+- 附件链路 **57/57**、toast 通道 **27/27**、DMS 字段契约 **19/19**、
+  DMS 端到端(打真实库)**89/89**、`validate-harness` 通过
+- `npx vue-tsc --noEmit`:只剩一条**既有**错误 `Cannot find type definition file for 'webpack-env'`
+  (`tsconfig.json` 里列了 `webpack-env` 但没装 `@types/webpack-env`,**自初始提交起就这样**,
+  不是这次改动引入的;它会让全量类型检查跑不起来,属独立的小清理项)
+- `npx jest`:**0 个用例**(仓库本来就没有 jest 测试文件,`npm run test` 是空跑)
+
+**⚠️ 又一次"只 grep src/ 会误判"**:`VITE_USE_KNOWLEDGE_API` 我先前写成"只有死代码在用",
+其实 **`index.html` 第 321 行在用它**切「政策解读」卡片 → **没删**,并在清单里更正。
+(同上次 `VITE_APP_KEY` 的教训 —— **`index.html` 里的内联脚本必须一起 grep**。)
+
 **🔴 部署必配(已写进技术债 #10 与 asr-nls.md)**:
 ① nginx 要把 `/aly-api/` 转发到 `http://121.43.55.7:10011/`(去掉前缀);
 ② **`.env.production` / `.env.qingpu` / `.env.test` 都没有 `VITE_NLS_APPKEY`** ——

+ 6 - 2
harness/tools/verify-nls-realtime.mjs

@@ -274,8 +274,12 @@ console.log('\n【4】两个壳组件已切到 NLS 实现(源码级断言)')
         /openSocket\(\)/.test(source) && /\.transform\(\)/.test(source) &&
         /\.close\(\)/.test(source) && /\.clear\(\)/.test(source));
   }
-  // 旧实现仍在仓库里(切回去的退路),别被顺手删掉
-  check('旧的厂商 SDK 仍在(切回去只改 import)', existsSync('src/three-libs/asr/index.ts'));
+  // 2026-09-24 用户要求「B 全部删掉」:旧厂商 SDK 连同 VITE_ASR / VITE_XF_ASR / /asr 代理一起删了
+  // —— 所以这条断言从「旧实现仍在」反过来:**确认它已经不在**(免得哪天有人误加回来)
+  check('旧的厂商 SDK 已删除(2026-09-24 用户要求)', !existsSync('src/three-libs/asr/index.ts'));
+  check('旧 ASR 的 env 也删干净了(VITE_ASR / VITE_XF_ASR)',
+    !readFileSync('.env.development', 'utf8').includes('VITE_ASR') &&
+      !readFileSync('.env.development', 'utf8').includes('VITE_XF_ASR'));
 }
 
 console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);

+ 0 - 1
index.html

@@ -240,7 +240,6 @@
 
       (function initSystem() {
         let isLanMode = globalThis.isQingpuEnv;
-        globalThis.isXF = false;
         var requestedPolicyMode = getQueryObj("mode");
         var isZhaoshangMode =
           requestedPolicyMode === "zhaoshang" ||

+ 12 - 124
package-lock.json

@@ -8,7 +8,6 @@
       "name": "vite-vue-typescript-starter",
       "version": "1.6.1",
       "dependencies": {
-        "@microsoft/fetch-event-source": "^2.0.1",
         "@types/three": "0.143.0",
         "@vueuse/core": "^10.4.1",
         "alloyfinger": "^0.1.16",
@@ -17,7 +16,6 @@
         "less": "^4.1.3",
         "marked": "^13.0.3",
         "mitt": "^3.0.1",
-        "qs": "^6.11.2",
         "sass": "^1.86.3",
         "three": "0.143.0",
         "vue": "^3.2.45",
@@ -1359,12 +1357,6 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
-    "node_modules/@microsoft/fetch-event-source": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
-      "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==",
-      "license": "MIT"
-    },
     "node_modules/@napi-rs/lzma-linux-x64-gnu": {
       "version": "1.5.1",
       "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
@@ -3060,6 +3052,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
       "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0",
@@ -3069,22 +3062,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/call-bound": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
-      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "get-intrinsic": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
@@ -3703,6 +3680,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
       "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.1",
@@ -3780,6 +3758,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
       "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -3789,6 +3768,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
       "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -3798,6 +3778,7 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
       "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "es-errors": "^1.3.0"
@@ -4107,6 +4088,7 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -4136,6 +4118,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
       "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "call-bind-apply-helpers": "^1.0.2",
@@ -4170,6 +4153,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
       "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "dunder-proto": "^1.0.1",
@@ -4231,6 +4215,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
       "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -4282,6 +4267,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
       "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -4310,6 +4296,7 @@
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz",
       "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
@@ -5647,6 +5634,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
       "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
@@ -5901,18 +5889,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/object-inspect": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
-      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
@@ -6319,22 +6295,6 @@
       ],
       "license": "MIT"
     },
-    "node_modules/qs": {
-      "version": "6.16.0",
-      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.16.0.tgz",
-      "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "es-define-property": "^1.0.1",
-        "side-channel": "^1.1.1"
-      },
-      "engines": {
-        "node": ">=0.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/querystringify": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz",
@@ -6694,78 +6654,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/side-channel": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz",
-      "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.4",
-        "side-channel-list": "^1.0.1",
-        "side-channel-map": "^1.0.1",
-        "side-channel-weakmap": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-list": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz",
-      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.4"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-map": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
-      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-weakmap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
-      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3",
-        "side-channel-map": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/signal-exit": {
       "version": "3.0.7",
       "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",

+ 0 - 2
package.json

@@ -16,7 +16,6 @@
     "predev": "node harness/tools/dms-login.mjs --if-needed"
   },
   "dependencies": {
-    "@microsoft/fetch-event-source": "^2.0.1",
     "@types/three": "0.143.0",
     "@vueuse/core": "^10.4.1",
     "alloyfinger": "^0.1.16",
@@ -25,7 +24,6 @@
     "less": "^4.1.3",
     "marked": "^13.0.3",
     "mitt": "^3.0.1",
-    "qs": "^6.11.2",
     "sass": "^1.86.3",
     "three": "0.143.0",
     "vue": "^3.2.45",

+ 1 - 1
src/components/Chat/BusinessRecord.vue

@@ -368,7 +368,7 @@ import {
   watch,
   getCurrentInstance,
 } from "vue";
-import { MessageState } from "../stream-message-coordinator";
+import { MessageState } from "@/types/record";
 import { FeedbackType, From } from "@/types/record";
 import { writeDmsFeedback } from "@/network/api/dms/chat-sessions-dms";
 import DotsAnim from "../Common/DotsAnim.vue";

+ 2 - 10
src/components/business-assistant/useBusinessAssistantChat.ts

@@ -2,8 +2,6 @@ import { computed, nextTick, provide, ref, shallowRef, watch } from "vue";
 import { showAppMessage } from "@/utils/app-message";
 import { isZhaoshangLLM } from '@/utils';
 import { getChatApiBaseUrl } from '@/utils/runtime-config';
-// [旧协议] 旧聊天流式协调器(/knowledge/chat),已由 api-chat-coordinator 取代
-// import { StreamMessageCoordinator } from '../stream-message-coordinator';
 import { ApiChatCoordinator, toChatQuestionAnswer, type ChatInterruptPayload } from '../api-chat-coordinator';
 import {
   ChatGenerationTask,
@@ -34,7 +32,6 @@ import { buildTestDocumentLineStreamChunks } from './test-document-line-stream';
 
 import { getChatHistoryStorageKey, enterpriseInfo } from '@/components/useEnterpriseAuth';
 import { fetchRemoteSessionRecords, deleteSessionInfo, type RemoteSession } from '@/network/api/chat-sessions';
-// [已停用] updateSessionInfo 随 1887 会话写入一并停用
 import { upsertDmsRecord, upsertDmsSession } from '@/network/api/dms/chat-sessions-dms';
 import { syncCompanyInfoFromChat } from '@/network/api/dms/company-info-sync';
 import { hasVisibleMessageContent } from '@/utils/interrupted-message';
@@ -123,13 +120,8 @@ export function useBusinessAssistantChat(options: UseBusinessAssistantChatOption
     void upsertDmsRecord(params);
   };
 
-  // [已停用] 不再往 DMS 的 1887「助手会话」栏目写数据(用户 2026-09-18 指示:
-  // 「只传具体的会话记录,另一个接口不传了」)。原来这里做会话标题同步,
-  // 现在改名只改本地;1887 那套写入(含标题同步)整条链路停用。
-  // 原实现:
-  // const syncSessionTitleToServer = (sessionId: string, title: string) => {
-  //   void updateSessionInfo(sessionId, getCreditCode(), title);
-  // };
+  // 会话标题不再同步到服务端(2026-09-18 用户指示,2026-09-24 连注释一起清掉):
+  // 改名只改本地;1887 的写入只保留「回答完成时 upsert 一次」,没有标题同步这条链路。
 
   const syncSessionDeleteToServer = (sessionId: string) => {
     void deleteSessionInfo(sessionId, getCreditCode());

+ 1 - 91
src/components/business-assistant/useBusinessAssistantUpload.ts

@@ -12,39 +12,8 @@ import {
   SUPPORTED_ATTACHMENT_LABEL,
 } from './shared';
 
-/* [已停用] OSS 签名响应类型 —— 2026-09-23 起改用 DMS 文件服务(src/network/api/dms/file-upload.ts)
-interface OssSignaturePayload {
-  file_url?: string;
-  host?: string;
-  key?: string;
-  policy?: string;
-  security_token?: string;
-  x_oss_credential?: string;
-  x_oss_date?: string;
-  x_oss_signature?: string;
-  x_oss_signature_version?: string;
-  'x-oss-credential'?: string;
-  'x-oss-date'?: string;
-  'x-oss-signature'?: string;
-  'x-oss-signature-version'?: string;
-  [key: string]: any;
-}
-*/
-
-/* [已停用] OSS 签名接口地址 —— 2026-09-23 起改用 DMS 文件服务(src/network/api/dms/file-upload.ts)
-// 取 OSS 上传签名的接口。**当前是厂商(metamaker)提供的**,且实测无需鉴权。
-// 已做成 env 可配:将来若换成自建签名服务,改配置即可,不用改代码。
-// 详见 harness/docs/reference/current-api-call-sites.md 与 progress.md Session 049。
-const OSS_SIGNATURE_URL =
-  import.meta.env.VITE_UPLOAD_SIGN_URL || '//qingpu-data-api.metamaker.cn/common/qp_signed_url';
-*/
 const DRAFT_HOME_KEY = '__business_assistant_home_draft__';
 
-/* [已停用] OSS 字段取值辅助 —— 2026-09-23 起改用 DMS 文件服务(src/network/api/dms/file-upload.ts)
-const getOssField = (payload: OssSignaturePayload, kebabName: string, snakeName: string) =>
-  payload[kebabName] || payload[snakeName];
-*/
-
 export function useBusinessAssistantUpload(options?: {
   imageInputRef?: Ref<HTMLInputElement | null>;
   fileInputRef?: Ref<HTMLInputElement | null>;
@@ -151,66 +120,7 @@ export function useBusinessAssistantUpload(options?: {
     }
   };
 
-/* [已停用] 取 OSS 签名 —— 2026-09-23 起改用 DMS 文件服务(src/network/api/dms/file-upload.ts)
-  const requestOssSignature = async (extension: string, signal: AbortSignal) => {
-    const response = await fetch(OSS_SIGNATURE_URL, {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify({ ext: extension }),
-      signal,
-    });
-
-    if (!response.ok) {
-      throw new Error('获取上传凭证失败');
-    }
-
-    const data = await response.json();
-    const payload: OssSignaturePayload = data?.ret || data?.data || data;
-    if (
-      !payload?.host ||
-      !payload?.key ||
-      !payload?.policy ||
-      !getOssField(payload, 'x-oss-signature', 'x_oss_signature') ||
-      !payload?.file_url
-    ) {
-      throw new Error('上传凭证缺少必要字段');
-    }
-
-    return payload;
-  };
-*/
-
-/* [已停用] 直传 OSS —— 2026-09-23 起改用 DMS 文件服务(src/network/api/dms/file-upload.ts)
-  const uploadToOss = async (file: File, signature: OssSignaturePayload, signal: AbortSignal) => {
-    const formData = new FormData();
-    formData.append('success_action_status', '200');
-    formData.append('policy', signature.policy || '');
-    formData.append('x-oss-signature', getOssField(signature, 'x-oss-signature', 'x_oss_signature') || '');
-    formData.append(
-      'x-oss-signature-version',
-      getOssField(signature, 'x-oss-signature-version', 'x_oss_signature_version') || 'OSS4-HMAC-SHA256',
-    );
-    formData.append('x-oss-credential', getOssField(signature, 'x-oss-credential', 'x_oss_credential') || '');
-    formData.append('x-oss-date', getOssField(signature, 'x-oss-date', 'x_oss_date') || '');
-    formData.append('key', signature.key || '');
-    if (signature.security_token) {
-      formData.append('x-oss-security-token', signature.security_token);
-    }
-    formData.append('file', file);
-
-    const response = await fetch(signature.host || '', {
-      method: 'POST',
-      body: formData,
-      signal,
-    });
 
-    if (!response.ok) {
-      throw new Error('上传失败');
-    }
-  };
-*/
 
   const uploadFile = async (file: File, uploadItem: BusinessAssistantUploadedFile) => {
     const abortController = new AbortController();
@@ -220,7 +130,7 @@ export function useBusinessAssistantUpload(options?: {
     try {
       // 2026-09-23 起改用 **DMS 的文件服务**(用户指定):返回的是**相对路径**,
       // 它随聊天请求体的 `pictures` / `files` 发给后端(见 file-upload.ts 的实测契约)。
-      // 旧的厂商 OSS 上传整段停用(见下方 [已停用])。
+      // (更早那版是直传厂商 OSS,实现已在 2026-09-24 按用户要求删除,见 git 历史。)
       const relativePath = await uploadDmsAttachment(file, {
         isImage: Boolean(uploadItem.isImage),
         signal: abortController.signal,

+ 0 - 1377
src/components/stream-message-coordinator.ts

@@ -1,1377 +0,0 @@
-import { fetchEventSource } from "@microsoft/fetch-event-source";
-import qs from "qs";
-import mitt, { Emitter, EventType } from "mitt";
-import { removeMarkdownLabels, resolveAllMediaResource } from "./utils";
-import { RecordRowTypes, StreamXMLFilter, getStreamPlainText } from "@/utils/stream-xml-filter";
-import { jsonrepair } from "jsonrepair";
-import { isKnowledgeApi } from "@/utils/runtime-config";
-import { getScopeCloseLineBoundaryEnd } from "@/utils/scope-stream-boundary";
-
-const isTypewriterDebugEnabled = () =>
-  import.meta.env.MODE !== "production" &&
-  typeof localStorage !== "undefined" &&
-  localStorage.getItem("enable_typewriter_debug") === "true";
-
-export enum MessageState {
-  Update,
-  Sending,
-  Success,
-  Finish,
-  Fail,
-  Stop,
-  End,
-}
-
-declare enum TTSAudioType {
-  Wav = "wav",
-}
-
-export type TTSParams = {
-  text: string;
-  tts_args: string;
-  audio_type: TTSAudioType;
-  storage_type: "cloud";
-  anim_agent: string;
-  silence_type?: string;
-  silence_value?: string;
-};
-
-type StreamMessageArgs = {
-  SSEURL: string;
-  session: string;
-  token: string;
-  card_id: number;
-  isStrict?: boolean;
-  sign?: () => string;
-};
-
-type textItem = {
-  text: string;
-  isFinished: boolean;
-  itemCount: number;
-  from?: string;
-};
-
-type OptionsType = {
-  is_strict: boolean;
-  disable_censor?: boolean;
-};
-
-type TTSItem = {
-  text: string;
-  audio: string;
-  expression_anim: string;
-  teeth_anim: string;
-  isFormChat: boolean;
-};
-
-type FollowUpSuggestion = {
-  text: string;
-  intent?: string;
-};
-
-export class StreamMessageCoordinator {
-  SSEURL: string;
-  SSEFormURL: string;
-  TTSURL: string;
-  session: string;
-  token: string;
-  card_id: number;
-  department_id: string;
-  ctrl: AbortController;
-  storedTexts: textItem[];
-  storedTTS: TTSItem[];
-  question: string;
-  totalResponse: string;
-  record_id: string;
-  loading: boolean;
-  generating: boolean;
-  isGettingTTS: boolean;
-  isSpeaking: boolean;
-  AIResponseQueueTimer: number;
-  tts: TTSParams;
-  TTSTimer: number;
-  isMuted: boolean;
-  formChatting: boolean;
-  formTaskId: string;
-  isStrict: boolean;
-  isFinishEventEmitted: boolean;
-  knowledge_ids: string[];
-  sign?: () => string;
-  protected _eventbus: Emitter<Record<EventType, unknown>>;
-  isThinkMode: boolean;
-  scopeBufferArray: string[];
-  silenceBuffer: string;
-  silenceMode: boolean;
-  followupFilter: StreamXMLFilter;
-  constructor(args: StreamMessageArgs) {
-    this._eventbus = mitt();
-    // this.link_id = args.link_id || 'a2fd3b17-c9fe-4b4f-87af-af902a2f1440'
-    this.SSEURL = args.SSEURL || "http://192.168.14.45:4006/knowledge/chat";
-    this.SSEFormURL = args.SSEFormURL || "http://192.168.14.45:4006/knowledge/form_chat";
-    this.TTSURL = args.TTSURL || "//llm-api.test.metamaker.cn/knowledge/tts";
-    this.session = args.session || "example";
-    this.card_id = args.card_id;
-    this.department_id = args.department_id;
-    this.token = args.token;
-    this.tts = {
-      ...args.tts,
-      anim_agent: "mms",
-      silence_type: "Tailing",
-      silence_value: "100ms",
-    };
-    this.isStrict = args.isStrict || false;
-    this.ctrl = new AbortController();
-    this.TTSTimer = 0;
-    this.storedTexts = [];
-    this.knowledge_ids = [];
-    this.storedTTS = [];
-    this.totalResponse = "";
-    this.isMuted = false;
-    this.formChatting = false;
-    this.formTaskId = "";
-    this.sign = args.sign;
-    // this.storedTexts = new Proxy([], {
-    //   set: (target, prop, value) => {
-    //     target[prop] = value;
-
-    //     target['length'] = target.filter(each => each !== null).length;
-    //     this.checkAvailableTexts();
-    //     this.checkAvailableTTS();
-
-    //     console.log('texts', this.storedTexts, 'tts', this.storedTTS)
-    //     return true;
-    //   }
-    // })
-
-    // this.storedTTS = new Proxy([], {
-    //   set: (target, prop, value) => {
-    //     target[prop] = value;
-    //     target['length'] = target.filter(each => each !== null).length;
-    //     this.checkAvailableTTS();
-
-    //     console.log('texts', this.storedTexts, 'tts', this.storedTTS)
-    //     return true;
-    //   }
-    // })
-
-    this.isThinkMode = false;
-    this.isSpeaking = false;
-    this.scopeBufferArray = [];
-    this.silenceBuffer = "";
-    this.silenceMode = false;
-    this.followupFilter = new StreamXMLFilter({
-      customProcessor: new Map([
-        [
-          RecordRowTypes.FollowUp,
-          (payload: { schema: string }) => {
-            const suggestions = this.parseFollowupSuggestions(payload?.schema);
-            if (suggestions.length) {
-              this._eventbus.emit("followupSuggestions", suggestions);
-            }
-          },
-        ],
-        [
-          RecordRowTypes.PolicyListUpdated,
-          async (payload: { schema: string }) => {
-            try {
-              const jsonObj = JSON.parse(jsonrepair(payload.schema));
-              let link = jsonObj.link;
-              const dataPayload = jsonObj.payload;
-              if (!link && dataPayload && dataPayload.link) {
-                link = dataPayload.link;
-              }
-              if (link) {
-                let cleanLink = link.replace(/`/g, '').trim();
-                try {
-                  const urlObj = new URL(cleanLink, window.location.origin);
-                  if (dataPayload) {
-                    Object.keys(dataPayload).forEach(key => {
-                      if (key !== 'link' && dataPayload[key] !== undefined && dataPayload[key] !== null) {
-                        urlObj.searchParams.set(key, String(dataPayload[key]));
-                      }
-                    });
-                  }
-                  cleanLink = urlObj.toString();
-                } catch (e) {
-                  console.warn('Invalid URL:', cleanLink, e);
-                }
-
-                const res = await fetch(cleanLink);
-                if (res.ok) {
-                  const data = await res.json();
-                  if (data && Array.isArray(data.data)) {
-                    globalThis.policiesPublic = data.data;
-                  }
-                }
-              }
-            } catch (e) {
-              console.error('Fetch policy-list-updated error in stream coordinator:', e);
-            }
-          },
-        ],
-        [
-          RecordRowTypes.EnterpriseNotification,
-          (payload: { schema: string }) => {
-            // 解析关注企业的统一社会信用代码,保存到 globalThis 并广播事件
-            const creditCode = String(payload?.schema || "").trim();
-            if (!creditCode) return;
-            (globalThis as any).focusedEnterpriseCreditCode = creditCode;
-            try {
-              document.dispatchEvent(
-                new CustomEvent("qingpu:enterprise-notification", {
-                  detail: { creditCode },
-                })
-              );
-            } catch (e) {
-              console.warn("Dispatch enterprise-notification failed", e);
-            }
-          },
-        ],
-      ]),
-    });
-  }
-  setURL(url) {
-    this.SSEURL = url;
-  }
-  addEventListener(eventName: string, callback) {
-    this._eventbus.on(eventName, callback);
-    return this;
-  }
-  removeEventListener(eventName: string, callback?) {
-    this._eventbus.off(eventName, callback);
-    return this;
-  }
-  allClear() {
-    this._eventbus.all.clear();
-  }
-  async generateAnswer(text: string, isFormChat: boolean = undefined, taskId: string = undefined) {
-    this.loading = true;
-    this.generating = true;
-    this.isGettingTTS = false;
-    this._eventbus.emit("loadingState", { type: "loading", state: true });
-    this.record_id = "";
-    this.question = text;
-    this.totalResponse = "";
-    this.storedTexts = [];
-    this.storedTTS = [];
-    this.isThinkMode = false;
-    this.knowledge_ids = [];
-    this.silenceBuffer = "";
-    this.silenceMode = false;
-    this.followupFilter.reset();
-
-    if (isFormChat !== undefined) {
-      this.formChatting = isFormChat;
-
-      if (taskId !== undefined) {
-        this.formTaskId = taskId;
-      }
-    }
-
-    // let data = {
-    //   link_id: this.link_id,
-    //   text: text,
-    //   session: this.session
-    // }
-
-    let data = {
-      card_id: this.card_id,
-      department_id: this.department_id,
-      text: text,
-      session_id: this.session,
-      format: "object",
-      model: globalThis.llmModel,
-      transmission: {
-        session_id: this.session,
-        model: globalThis.llmModel,
-      },
-    };
-
-    if (this.formChatting) {
-      data.task_id = this.formTaskId;
-      data.text = text;
-    } else {
-      this.formTaskId = "";
-    }
-
-    if (globalThis.transmission) {
-      data.transmission.credit_code = globalThis.transmission.credit_code;
-      if (globalThis.transmission.files !== undefined) {
-        data.transmission.files = globalThis.transmission.files;
-      }
-      if (globalThis.transmission.file_pos !== undefined) {
-        data.transmission.file_pos = globalThis.transmission.file_pos;
-      }
-    }
-
-    if ((globalThis as any).source) {
-      data.source = (globalThis as any).source;
-    }
-
-    // 与 source 同一级:补充企业级上下文与历史控制参数
-    data.credit_code = globalThis.transmission?.credit_code;
-    data.knowledge_ids = this.knowledge_ids;
-    data.enable_history = true;
-
-    if (globalThis.enable_thinking !== undefined) {
-      data.transmission.enable_thinking = globalThis.enable_thinking;
-    }
-
-    this.checkAvailableResponse();
-
-    let headers: any = {
-      "Content-Type": "application/json",
-    };
-    let token = this.token || globalThis.token;
-    if (token && token !== 'undefined') {
-      headers.Authorization = token;
-    }
-
-    let begin = Date.now();
-    let trace = window.__bl && window.__bl.getUberTraceId();
-    let traceId = trace && trace.traceId;
-    let isFirstByteReached = false;
-    if (trace) {
-      window.__bl.setConfig({
-        disableHook: true,
-      });
-      console.log(trace);
-      headers["Uber-Trace-Id"] = trace["uber-trace-id"];
-    }
-
-    let eventSourceUrl = this.formChatting ? this.SSEFormURL : this.SSEURL;
-
-    fetchEventSource(eventSourceUrl, {
-      method: "POST",
-      headers,
-      body: JSON.stringify(data),
-      signal: this.ctrl.signal,
-      openWhenHidden: true,
-      onopen: async (response) => {
-        if (trace) {
-          window.__bl &&
-            __bl.setConfig({
-              disableHook: false,
-            });
-        }
-
-        let serverPreprocessInfo = await this.handleServerPreprocessInfo(response);
-        if (serverPreprocessInfo) {
-          throw serverPreprocessInfo;
-        }
-
-        this.isThinkMode = globalThis.enable_thinking;
-        this.isFinishEventEmitted = false;
-        this._eventbus.emit("open", response);
-      },
-      onmessage: (msg) => {
-        if (!isFirstByteReached && traceId) {
-          let time = Date.now() - begin;
-          // 上报接口调用成功
-          if (trace) {
-            window.__bl &&
-              __bl.api(this.SSEURL.slice(2), true, time, 200, msg.data, begin, traceId);
-          }
-          isFirstByteReached = true;
-        }
-
-        if (msg.event === "fail") {
-          // this._eventbus.emit('info', '知识库未正确设置,请联系管理员')
-          if (this.storedTexts[this.storedTexts.length - 1]) {
-            Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-              ...this.storedTexts[this.storedTexts.length - 1],
-              isFinished: true,
-            });
-          }
-
-          this.loading = false;
-          // this.AIResponse.text = '系统繁忙,请稍后再试'
-          // this.AIResponse.state = MessageState.Finish
-          // this._eventbus.emit('subtitle', this.AIResponse)
-
-          let message = "系统繁忙,请稍后再试";
-          try {
-            let data = JSON.parse(msg.data);
-            if (data) {
-              message = data.message;
-            }
-          } catch (e) {
-            console.log(e);
-          }
-
-          this._eventbus.emit("info", message);
-          return;
-        }
-
-        try {
-          let data = JSON.parse(msg.data);
-          if (data) {
-            if (!this.record_id) {
-              this.record_id = data.record_id;
-              if (this.record_id) {
-                this._eventbus.emit("recordId", this.record_id);
-              }
-            }
-
-            if (data.knowledge_ids) {
-              this.knowledge_ids = data.knowledge_ids;
-            }
-
-            let message = data.message;
-            if (message) {
-              this.followupFilter.processChunk(message);
-            }
-
-            if (message === "<think>") {
-              this.isThinkMode = true;
-              return;
-            }
-
-            if (message == "</think>") {
-              this.isThinkMode = false;
-              return;
-            }
-
-            if (
-              message &&
-              !data.reasoning_content &&
-              this.isThinkMode &&
-              !isKnowledgeApi()
-            ) {
-              this.isThinkMode = false;
-            }
-
-            if (data.reasoning_content || this.isThinkMode) {
-              this._eventbus.emit("reasoning_content", data.reasoning_content || message);
-              return;
-            }
-
-            if (message) {
-              this.isThinkMode = false;
-              let { taskId, relate, reference } = resolveAllMediaResource(message);
-              if ((relate && relate.length) || (reference && reference.length)) {
-                const ques_relate = relate
-                  .map((each) => ({
-                    question: each["key_question"] || each["问题"] || each["事项名称"] || each,
-                  }))
-                  .filter((each) => each.question);
-
-                this._eventbus.emit("relate", ques_relate);
-
-                const ques_reference = reference
-                  .map((each) => ({
-                    question: each["key_question"] || each["问题"] || each["事项名称"] || each,
-                  }))
-                  .filter((each) => each.question);
-
-                this._eventbus.emit("reference", ques_reference);
-                return;
-              } else if (taskId) {
-                this.turnFormRequest({ taskId, question: this.question });
-                return;
-              } else if (message.includes("<!-- POLICY_TABLE")) {
-                this.putInText({ message, from: data.from });
-                this.textEnqueue();
-              } else {
-                message.split("").forEach((char) => {
-                  this.putInText({ message: char, from: data.from });
-                });
-              }
-            }
-
-            if (data.status) {
-              if (data.status === "done" || data.status === "interrupt") {
-                this.turnChatRequest();
-              }
-            }
-
-            if (message) {
-              this._eventbus.emit("message", message);
-            }
-
-            if (data.disease_id) {
-              this._eventbus.emit("diagnosed", data.disease_id);
-            }
-          }
-        } catch (e) {
-          this._eventbus.emit("info", "知识库未正确设置,请联系管理员");
-          if (this.storedTexts[this.storedTexts.length - 1]) {
-            Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-              ...this.storedTexts[this.storedTexts.length - 1],
-              isFinished: true,
-            });
-          }
-          console.log(e);
-        }
-      },
-      onerror: (err) => {
-        if (traceId) {
-          let time = Date.now() - begin;
-          // 上报接口调用失败
-          window.__bl &&
-            __bl.api(
-              this.SSEURL.slice(2),
-              false,
-              time,
-              "ERROR",
-              JSON.stringify(err),
-              begin,
-              traceId
-            );
-        }
-
-        console.warn(err);
-        this._eventbus.emit("info", "与服务器的连接异常,请稍后再试");
-        this.stopGenerate();
-        this.turnChatRequest();
-        this._eventbus.emit("error", err);
-        throw new Error(err);
-        if (this.storedTexts[this.storedTexts.length - 1]) {
-          Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-            ...this.storedTexts[this.storedTexts.length - 1],
-            isFinished: true,
-          });
-        }
-      },
-      onclose: () => {
-        console.warn("onclose");
-        this.generating = false;
-
-        this.storeTotalResponse();
-        this._eventbus.emit("close");
-
-        if (this.storedTexts[this.storedTexts.length - 1]) {
-          Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-            ...this.storedTexts[this.storedTexts.length - 1],
-            isFinished: true,
-          });
-        }
-      },
-    });
-  }
-  async generateFormSubmit(text: string) {
-    this.loading = true;
-    this.generating = true;
-    this.isGettingTTS = false;
-    this._eventbus.emit("loadingState", { type: "loading", state: true });
-    this.record_id = "";
-    this.question = text;
-    this.totalResponse = "";
-    this.storedTexts = [];
-    this.storedTTS = [];
-    this.isThinkMode = false;
-    this.knowledge_ids = [];
-    this.followupFilter.reset();
-
-    const currentModel = "form_submit";
-    const messages = [
-      {
-        role: "user",
-        content: text,
-      },
-    ];
-
-    // let data = {
-    //   stream: true,
-    //   model: currentModel,
-    //   messages,
-    //   session_id: this.session,
-    // };
-
-    // if (globalThis.transmission) {
-    //   data.credit_code = globalThis.transmission.credit_code;
-    // }
-
-    let data = {
-      card_id: this.card_id,
-      department_id: this.department_id,
-      text: text,
-      session_id: this.session,
-      format: "object",
-      model: currentModel,
-      transmission: {
-        session_id: this.session,
-        model: currentModel,
-      },
-    };
-
-    this.checkAvailableResponse();
-
-    let headers: any = {
-      "Content-Type": "application/json",
-    };
-    let token = this.token || globalThis.token;
-    if (token && token !== 'undefined') {
-      headers.Authorization = token;
-    }
-
-    let begin = Date.now();
-    let trace = window.__bl && window.__bl.getUberTraceId();
-    let traceId = trace && trace.traceId;
-    let isFirstByteReached = false;
-    if (trace) {
-      window.__bl.setConfig({
-        disableHook: true,
-      });
-      console.log(trace);
-      headers["Uber-Trace-Id"] = trace["uber-trace-id"];
-    }
-
-    let eventSourceUrl = `${import.meta.env.VITE_MODEL_SERVER}/v1/chat/completions`;
-    // let eventSourceUrl = this.SSEURL;
-
-    fetchEventSource(eventSourceUrl, {
-      method: "POST",
-      headers,
-      body: JSON.stringify(data),
-      signal: this.ctrl.signal,
-      openWhenHidden: true,
-      onopen: async (response) => {
-        if (trace) {
-          window.__bl &&
-            __bl.setConfig({
-              disableHook: false,
-            });
-        }
-
-        let serverPreprocessInfo = await this.handleServerPreprocessInfo(response);
-        if (serverPreprocessInfo) {
-          throw serverPreprocessInfo;
-        }
-
-        this.isThinkMode = globalThis.enable_thinking;
-        this.isFinishEventEmitted = false;
-        this._eventbus.emit("open", response);
-      },
-      onmessage: (msg) => {
-        if (!isFirstByteReached && traceId) {
-          let time = Date.now() - begin;
-          // 上报接口调用成功
-          if (trace) {
-            window.__bl &&
-              __bl.api(this.SSEURL.slice(2), true, time, 200, msg.data, begin, traceId);
-          }
-          isFirstByteReached = true;
-        }
-
-        if (msg.event === "fail") {
-          // this._eventbus.emit('info', '知识库未正确设置,请联系管理员')
-          if (this.storedTexts[this.storedTexts.length - 1]) {
-            Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-              ...this.storedTexts[this.storedTexts.length - 1],
-              isFinished: true,
-            });
-          }
-
-          this.loading = false;
-          // this.AIResponse.text = '系统繁忙,请稍后再试'
-          // this.AIResponse.state = MessageState.Finish
-          // this._eventbus.emit('subtitle', this.AIResponse)
-
-          let message = "系统繁忙,请稍后再试";
-          try {
-            let data = JSON.parse(msg.data);
-            if (data) {
-              // message = data.choice[0].delta.content;
-              message = data.message;
-            }
-          } catch (e) {
-            console.log(e);
-          }
-
-          this._eventbus.emit("info", message);
-          return;
-        }
-
-        try {
-          let data = JSON.parse(msg.data);
-          if (data) {
-            if (!this.record_id) {
-              this.record_id = data.record_id;
-              if (this.record_id) {
-                this._eventbus.emit("recordId", this.record_id);
-              }
-            }
-
-            if (data.knowledge_ids) {
-              this.knowledge_ids = data.knowledge_ids;
-            }
-
-            // const message = data.choices[0].delta.content;
-            const message = data.message;
-            if (message) {
-              this.followupFilter.processChunk(message);
-            }
-
-            if (message === "<think>") {
-              this.isThinkMode = true;
-              return;
-            }
-
-            if (message == "</think>") {
-              this.isThinkMode = false;
-              return;
-            }
-
-            if (
-              message &&
-              !data.reasoning_content &&
-              this.isThinkMode &&
-              !isKnowledgeApi()
-            ) {
-              this.isThinkMode = false;
-            }
-
-            if (data.reasoning_content || this.isThinkMode) {
-              this._eventbus.emit("reasoning_content", data.reasoning_content || message);
-              return;
-            }
-
-            if (message) {
-              this.isThinkMode = false;
-              let { taskId, relate, reference } = resolveAllMediaResource(message);
-              if ((relate && relate.length) || (reference && reference.length)) {
-                const ques_relate = relate
-                  .map((each) => ({
-                    question: each["key_question"] || each["问题"] || each["事项名称"] || each,
-                  }))
-                  .filter((each) => each.question);
-
-                this._eventbus.emit("relate", ques_relate);
-
-                const ques_reference = reference
-                  .map((each) => ({
-                    question: each["key_question"] || each["问题"] || each["事项名称"] || each,
-                  }))
-                  .filter((each) => each.question);
-
-                this._eventbus.emit("reference", ques_reference);
-                return;
-              } else if (taskId) {
-                this.turnFormRequest({ taskId, question: this.question });
-                return;
-              } else if (message.includes("<!-- POLICY_TABLE")) {
-                this.putInText({ message, from: data.from });
-                this.textEnqueue();
-              } else {
-                message.split("").forEach((char) => {
-                  this.putInText({ message: char, from: data.from });
-                });
-              }
-            }
-
-            if (data.status) {
-              if (data.status === "done" || data.status === "interrupt") {
-                this.turnChatRequest();
-              }
-            }
-
-            if (message) {
-              this._eventbus.emit("message", message);
-            }
-
-            if (data.disease_id) {
-              this._eventbus.emit("diagnosed", data.disease_id);
-            }
-          }
-        } catch (e) {
-          this._eventbus.emit("info", "知识库未正确设置,请联系管理员");
-          if (this.storedTexts[this.storedTexts.length - 1]) {
-            Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-              ...this.storedTexts[this.storedTexts.length - 1],
-              isFinished: true,
-            });
-          }
-          console.log(e);
-        }
-      },
-      onerror: (err) => {
-        if (traceId) {
-          let time = Date.now() - begin;
-          // 上报接口调用失败
-          window.__bl &&
-            __bl.api(
-              this.SSEURL.slice(2),
-              false,
-              time,
-              "ERROR",
-              JSON.stringify(err),
-              begin,
-              traceId
-            );
-        }
-
-        console.warn(err);
-        this._eventbus.emit("info", "与服务器的连接异常,请稍后再试");
-        this.stopGenerate();
-        this.turnChatRequest();
-        this._eventbus.emit("error", err);
-        throw new Error(err);
-        if (this.storedTexts[this.storedTexts.length - 1]) {
-          Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-            ...this.storedTexts[this.storedTexts.length - 1],
-            isFinished: true,
-          });
-        }
-      },
-      onclose: () => {
-        console.warn("onclose");
-        this.generating = false;
-
-        this._eventbus.emit("close");
-
-        if (this.storedTexts[this.storedTexts.length - 1]) {
-          Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-            ...this.storedTexts[this.storedTexts.length - 1],
-            isFinished: true,
-          });
-        }
-      },
-    });
-  }
-  stopGenerate(payload: { answer?: string } = {}) {
-    let { answer } = payload || {};
-
-    this.loading = false;
-    this.generating = false;
-    this.isSpeaking = false;
-    this.storedTexts = [];
-    this.storedTTS = [];
-    this.isThinkMode = false;
-    this.silenceBuffer = "";
-    this.silenceMode = false;
-    this.followupFilter.reset();
-
-    if (this.totalResponse || answer) {
-      this.storeTotalResponse({ answer });
-    }
-
-    this._eventbus.emit("loadingState", { type: "loading", state: false });
-    if (this.AIResponseQueueTimer) {
-      clearInterval(this.AIResponseQueueTimer);
-      this.AIResponseQueueTimer = 0;
-    }
-    this.abortConnection();
-  }
-  abortConnection() {
-    this.ctrl.abort();
-    this.ctrl = new AbortController();
-  }
-  private getCurrentTextItem(from: string) {
-    let lastIdx = this.storedTexts.length - 1;
-    let last = this.storedTexts[lastIdx];
-
-    if (this.storedTexts.length === 0 || (last && last.isFinished)) {
-      this.storedTexts.push({
-        text: "",
-        isFinished: false,
-        itemCount: 0,
-        from,
-      });
-      lastIdx = this.storedTexts.length - 1;
-      last = this.storedTexts[lastIdx];
-    }
-
-    return { lastIdx, last };
-  }
-
-  private appendTextToCurrentItem(message: string, from: string, autoEnqueue = true) {
-    const { lastIdx, last } = this.getCurrentTextItem(from);
-
-    // 追加文本优化:targetObject.message 为空时跳过
-    if (!message) return;
-
-    const updatedText = (last?.text || "") + message;
-    Reflect.set(this.storedTexts, lastIdx, {
-      ...last,
-      text: updatedText,
-      itemCount: updatedText.length,
-      isFinished: false,
-      from,
-    });
-
-    // 优化分段触发:批量符号 includes 和换行
-    const gapChars = [",", "。", ",", "!", "?", "\n"];
-    const isGap =
-      gapChars.some((char) => message.includes(char)) ||
-      message.includes("\n\n");
-    if (autoEnqueue && isGap) {
-      this.textEnqueue();
-    }
-  }
-
-  putInText(targetObject: { message: string; from: string }) {
-    // 追加文本优化:targetObject.message 为空时跳过
-    if (!targetObject.message) return;
-
-    const startedAt = performance.now();
-    let splitCount = 0;
-
-    // HTML 注释(如 <!-- POLICY_TABLE)应当跳过 silence 检查,
-    // 因为 checkSilenceMode 逐字符处理会破坏 <!- 前缀导致整个注释块被丢弃
-    let message = targetObject.message;
-    if (message.includes("<!--")) {
-      this.silenceBuffer = "";
-      this.silenceMode = false;
-    } else {
-      message = this.checkSilenceMode(message);
-    }
-
-    while (message) {
-      const last = this.storedTexts[this.storedTexts.length - 1];
-      const lastText = last && !last.isFinished ? last.text || "" : "";
-      const boundaryEnd = getScopeCloseLineBoundaryEnd(lastText + message);
-
-      if (boundaryEnd > lastText.length) {
-        const boundaryMessageLength = boundaryEnd - lastText.length;
-        this.appendTextToCurrentItem(message.slice(0, boundaryMessageLength), targetObject.from, false);
-        this.textEnqueue(true);
-        message = message.slice(boundaryMessageLength);
-        splitCount += 1;
-        continue;
-      }
-
-      this.appendTextToCurrentItem(message, targetObject.from);
-      break;
-    }
-
-    const cost = performance.now() - startedAt;
-    if (isTypewriterDebugEnabled() && (cost > 8 || splitCount > 1)) {
-      console.debug("[StreamMessageCoordinator] putInText", {
-        cost: Number(cost.toFixed(2)),
-        splitCount,
-        messageLength: targetObject.message.length,
-        from: targetObject.from,
-      });
-    }
-  }
-  textEnqueue(force = false) {
-    if (this.storedTexts.length === 0) {
-      return;
-    }
-
-    const startedAt = performance.now();
-    if (!force && this.storedTexts[this.storedTexts.length - 1].itemCount <= 12) {
-      if (isTypewriterDebugEnabled()) {
-        console.debug("[StreamMessageCoordinator] textEnqueue", {
-          force,
-          action: "keep-pending",
-          itemCount: this.storedTexts[this.storedTexts.length - 1].itemCount,
-          storeLength: this.storedTexts.length,
-        });
-      }
-      Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-        ...this.storedTexts[this.storedTexts.length - 1],
-        itemCount:
-          this.storedTexts[this.storedTexts.length - 1].itemCount +
-          this.storedTexts[this.storedTexts.length - 1].text.length,
-      });
-      // 当前的 textItem 还没有完成,不需要再次 enqueue,直接返回
-      return;
-    }
-
-    Reflect.set(this.storedTexts, this.storedTexts.length - 1, {
-      ...this.storedTexts[this.storedTexts.length - 1],
-      isFinished: true,
-    });
-
-    if (isTypewriterDebugEnabled()) {
-      const cost = performance.now() - startedAt;
-      console.debug("[StreamMessageCoordinator] textEnqueue", {
-        force,
-        action: "finish-item",
-        itemCount: this.storedTexts[this.storedTexts.length - 1].itemCount,
-        storeLength: this.storedTexts.length,
-        cost: Number(cost.toFixed(2)),
-      });
-    }
-  }
-  async fetchTTS(text: string, options: OptionsType = { is_strict: this.isStrict }) {
-    let headers: any = {
-      "Content-Type": "application/x-www-form-urlencoded",
-    };
-    let token = this.token || globalThis.token;
-    if (token && token !== 'undefined') {
-      headers.Authorization = token;
-    }
-
-    if (this.sign) {
-      headers["X-Heijing-Verf"] = this.sign();
-    }
-    text = removeMarkdownLabels(text);
-    const response = await fetch(this.TTSURL, {
-      method: "post",
-      headers,
-      // body: qs.stringify({ text: textItem.text, tts_args: JSON.stringify({ ...this.human.tts, voice_name: 'boss_neuralv2' }), audio_type: 'wav', storage_type: 'cloud' }),
-      body: qs.stringify({
-        text,
-        tts_args: JSON.stringify(this.tts),
-        audio_type: "wav",
-        storage_type: "cloud",
-        card_id: this.card_id,
-        ...options,
-      }),
-    }).catch((err) => {
-      console.warn(err);
-      this._eventbus.emit("error", err);
-    });
-
-    const result = await response.json();
-    if (result.err_code != 0) {
-      this.handleSensitiveTTS(result, text);
-    }
-
-    return result;
-  }
-  async checkAvailableTexts() {
-    let _record_id = this.record_id;
-    if (!this.isGettingTTS && this.storedTexts.length && this.storedTexts[0].isFinished) {
-      this.isGettingTTS = true;
-      let textItem = this.storedTexts.length ? this.storedTexts[0] : null;
-      if (textItem && textItem.text) {
-        // 为了让 tts 的时候不念出 控制 token,需要把控制 token 去掉
-        const streamText = getStreamPlainText(textItem.text);
-        let { taskId } = resolveAllMediaResource(textItem.text);
-        let { content } = resolveAllMediaResource(streamText);
-        if (this.checkIfCanBeConvertedToTTS(content)) {
-          if (this.isMuted) {
-            let err_code = 0;
-            // if (Number(textItem.from) > 1) {
-            //   let headers = {
-            //     Authorization: this.token,
-            //     "Content-Type": "application/json",
-            //   };
-            //   if (this.sign) {
-            //     headers["X-Heijing-Verf"] = this.sign();
-            //   }
-
-            //   const response = await fetch(this.CensorURL, {
-            //     method: "post",
-            //     headers,
-            //     body: JSON.stringify({ text: content, is_strict: this.isStrict }),
-            //   }).catch((err) => {
-            //     console.warn(err);
-            //     this._eventbus.emit("error", err);
-            //   });
-            //   const result = await response.json();
-            //   err_code = result.err_code;
-            // }
-
-            if (_record_id !== this.record_id) {
-              return;
-            }
-
-            if (err_code === 0) {
-              this.storedTTS.push({
-                text: textItem.text,
-                audio: "",
-                expression_anim: "",
-                teeth_anim: "",
-                isFormChat: this.formChatting,
-              });
-              this.storedTexts.shift();
-            } else {
-              this.handleSensitiveTTS({ err_code }, textItem.text);
-              return;
-            }
-          } else {
-            let options: OptionsType = {
-              is_strict: this.isStrict,
-            };
-            if (Number(textItem.from) <= 1) {
-              // 假如大模型判断的风险等级小于 1,则需要禁用敏感词过滤
-              options.disable_censor = true;
-            }
-
-            if (content == "" && textItem.text != "") {
-              if (taskId) {
-                this.storedTTS.push({
-                  text: "",
-                  audio: "",
-                  expression_anim: "",
-                  teeth_anim: "",
-                  isFormChat: true,
-                });
-                this.turnFormRequest({ taskId });
-              } else {
-                this.storedTTS.push({
-                  text: textItem.text,
-                  audio: "",
-                  expression_anim: "",
-                  teeth_anim: "",
-                  isFormChat: false,
-                });
-              }
-              this.storedTexts.shift();
-              return;
-            }
-            // const result = await this.fetchTTS(content, options);
-            // if (result.err_code !== 0 || _record_id !== this.record_id) {
-            //   return;
-            // }
-
-            // this.handleSensitiveTTS({ err_code: 0 }, textItem.text);
-
-            // let { audio, expression_anim, teeth_anim } = result.ret;
-            this.storedTTS.push({
-              text: textItem.text,
-              audio: "",
-              expression_anim: "",
-              teeth_anim: "",
-              isFormChat: this.formChatting,
-            });
-            this.storedTexts.shift();
-            // Promise.all([
-            //   Resources.audioFetcher.fetch(audio),
-            //   Resources.ttsTeethAnimateFetcher.fetch(teeth_anim),
-            //   Resources.ttsEmoAnimateFetcher.fetch(expression_anim),
-            // ]);
-          }
-        } else {
-          this.storedTexts.shift();
-        }
-
-        this.totalResponse += textItem.text;
-        this.isGettingTTS = false;
-      } else {
-        this.storedTexts.shift();
-        this.isGettingTTS = false;
-      }
-    }
-  }
-  checkIfCanBeConvertedToTTS(content) {
-    let canBeConverted = false;
-    const regex = /[\u4e00-\u9fa5A-Za-z0-9]/;
-
-    if (regex.test(content)) {
-      canBeConverted = true;
-    }
-
-    return canBeConverted;
-  }
-  handleSensitiveTTS(result, text) {
-    let errorText = "抱歉,我不能回答该问题。你可以问其他问题。";
-    if (text === errorText) {
-      return;
-    } else {
-      text = result.err_msg || errorText;
-    }
-
-    this._eventbus.emit("info", text);
-
-    this.storeTotalResponse({ answer: text });
-
-    console.warn("text error", text);
-    this.storedTexts = [
-      {
-        text: errorText,
-        isFinished: true,
-        itemCount: errorText.length,
-      },
-    ];
-    this.storedTTS = [];
-    this.isGettingTTS = false;
-    this.abortConnection();
-  }
-  async checkAvailableTTS() {
-    if (this.storedTTS.length) {
-      // if (!this.isSpeaking) {
-      this.isSpeaking = true;
-      let ttsItem = this.storedTTS.shift();
-      if (ttsItem) {
-        this._eventbus.emit("tts", ttsItem);
-      }
-      // }
-    }
-  }
-  cancelSpeakingState() {
-    this.isSpeaking = false;
-  }
-  changeMuteStatus(status: boolean) {
-    this.isMuted = status;
-  }
-  checkAvailableResponse() {
-    if (this.AIResponseQueueTimer) {
-      clearInterval(this.AIResponseQueueTimer);
-    }
-
-    let isAllTextFinished = false;
-    let isAllTTSFinished = false;
-    this.AIResponseQueueTimer = setInterval(async () => {
-      if (this.storedTexts.length) {
-        this.checkAvailableTexts();
-      }
-
-      if (this.storedTTS.length) {
-        this.checkAvailableTTS();
-      }
-
-      isAllTextFinished = this.generating == false && this.storedTexts.length == 0;
-      isAllTTSFinished = isAllTextFinished && this.storedTTS.length == 0; //&& this.storedResponse.length == 0
-
-      // if (isAllTextFinished && this.isFinishEventEmitted === false) {
-      //   this._eventbus.emit('totalResponse', { question: this.question, answer: this.totalResponse, record_id: this.record_id })
-      //   this.totalResponse = ''
-      //   this.isFinishEventEmitted = true
-      // }
-
-      if (!this.isSpeaking && isAllTTSFinished) {
-        console.log("checking all finished", this.loading);
-        this.loading = false;
-        this._eventbus.emit("loadingState", { type: "loading", state: false });
-        if (this.AIResponseQueueTimer) {
-          clearInterval(this.AIResponseQueueTimer);
-        }
-      }
-    }, 20);
-  }
-  checkSilenceMode(char: string): string {
-    if (this.silenceMode) {
-      this.silenceBuffer += char;
-      if (this.silenceBuffer === "</silence>") {
-        this.silenceMode = false;
-        this.silenceBuffer = "";
-      } else if (!"</silence>".startsWith(this.silenceBuffer)) {
-        // Mismatch in silence mode: swallow buffer but check if last char starts new tag
-        this.silenceBuffer = char === "<" ? "<" : "";
-      }
-      return "";
-    } else {
-      if (this.silenceBuffer.length > 0) {
-        this.silenceBuffer += char;
-        if (this.silenceBuffer === "<silence>") {
-          this.silenceMode = true;
-          this.silenceBuffer = "";
-          return "";
-        } else if (!"<silence>".startsWith(this.silenceBuffer)) {
-          // Mismatch in normal mode: release buffer
-          let output = this.silenceBuffer.slice(0, -1);
-          const lastChar = this.silenceBuffer.slice(-1);
-          this.silenceBuffer = lastChar === "<" ? "<" : "";
-          if (this.silenceBuffer === "") {
-            output += lastChar;
-          }
-          return output;
-        }
-        return "";
-      } else {
-        if (char === "<") {
-          this.silenceBuffer = "<";
-          return "";
-        } else {
-          return char;
-        }
-      }
-    }
-  }
-  checkScopeRules(text: string): {
-    isScopeStart: boolean;
-    isScopeEnd: boolean;
-    isAllCheckFailed: boolean;
-  } {
-    // 需要判断 <silence></silence>
-    let isScopeStart = false;
-    let isScopeEnd = false;
-    let isScopeStartCheckFailed = false;
-    let isScopeEndCheckFailed = false;
-
-    const startTag = "<silence>";
-    const endTag = "</silence>";
-
-    const lastIndex = this.scopeBufferArray.length - 1;
-    const lastBuffer = this.scopeBufferArray[lastIndex] || "";
-    if (text.includes(startTag) || lastBuffer.includes(startTag)) {
-      isScopeStart = true;
-    }
-    if (text.includes(endTag) || lastBuffer.includes(endTag)) {
-      isScopeEnd = true;
-    }
-
-    if (!isScopeStart && lastBuffer.length >= startTag.length) {
-      isScopeStartCheckFailed = true;
-    }
-
-    return {
-      isScopeStart,
-      isScopeEnd,
-      isAllCheckFailed: isScopeStartCheckFailed || isScopeEndCheckFailed,
-    };
-  }
-  async handleServerPreprocessInfo(response: Response) {
-    let errorTips = "";
-    if (response.status === 401) {
-      this._eventbus.emit("reLogin");
-      errorTips = "need to reLogin";
-    } else if (response.headers.get("Content-Type").startsWith("application/json")) {
-      let stream = response.body;
-      const reader = stream.getReader();
-      let result = await reader.read();
-
-      let value = result.value;
-      let targetString = new TextDecoder().decode(value);
-      let errInfo = JSON.parse(targetString);
-
-      errorTips = errInfo.err_msg;
-      this._eventbus.emit("info", errorTips);
-    }
-
-    return errorTips;
-  }
-  storeTotalResponse(payload = {}) {
-    if ((this.totalResponse || payload.answer) && !this.isFinishEventEmitted) {
-      if (payload.answer) {
-        this.totalResponse = payload.answer;
-      }
-
-      this._eventbus.emit("totalResponse", {
-        question: this.question,
-        answer: this.totalResponse,
-        record_id: this.record_id,
-        session: this.session,
-        task_id: this.formTaskId,
-        knowledge_ids: this.knowledge_ids,
-        ...payload,
-      });
-      this.totalResponse = "";
-      this.knowledge_ids = [];
-      this.isFinishEventEmitted = true;
-    }
-  }
-  isTotalResponseSaved() {
-    return this.isFinishEventEmitted;
-  }
-  async turnFormRequest(payload) {
-    this.stopGenerate();
-    await new Promise((resolve) => setTimeout(resolve, 100));
-    const { taskId, question } = payload;
-    this.formChatting = true;
-    this.formTaskId = taskId;
-    this.generateAnswer("");
-    this.question = question;
-  }
-  async turnChatRequest(continueChat = false) {
-    console.log("turn chat request");
-    this.formChatting = false;
-  }
-
-  parseFollowupSuggestions(schema: string): FollowUpSuggestion[] {
-    if (!schema) {
-      return [];
-    }
-
-    try {
-      const parsed = JSON.parse(schema);
-      const data = Array.isArray(parsed?.data) ? parsed.data : [];
-      return data
-        .map((item: any) => ({
-          text: String(item?.text || "").trim(),
-          intent: item?.intent ? String(item.intent).trim() : undefined,
-        }))
-        .filter((item: FollowUpSuggestion) => item.text);
-    } catch (error) {
-      console.warn("Failed to parse followup suggestions", error);
-      return [];
-    }
-  }
-}

+ 0 - 14
src/network/api/chat-sessions.ts

@@ -73,20 +73,6 @@ export async function fetchRemoteSessionRecords(
   }
 }
 
-/** 保存会话信息(标题/归属企业)。语义是 upsert:DMS 里没有该会话时补建 */
-export async function updateSessionInfo(
-  sessionId: string,
-  creditCode: string,
-  title: string
-): Promise<boolean> {
-  try {
-    return await upsertDmsSession({ sessionId, title, creditCode });
-  } catch (error) {
-    console.warn("update session info error:", error);
-    return false;
-  }
-}
-
 /** 删除会话(连带它的问答记录) */
 export async function deleteSessionInfo(
   sessionId: string,

+ 0 - 578
src/three-libs/asr/index.ts

@@ -1,578 +0,0 @@
-import { BaseEvent } from "@/utils/base-event";
-import { isMobile } from "@/utils";
-
-/**
- * language:语种
- *    zh_cn:中文(支持简单的英文识别)
- *    en_us:英文
- * domain:应用领域
- *    iat:日常用语
- * accent:方言
- *    mandarin:中文普通话、其他语种
- * vad_eos:用于设置端点检测的静默时间,单位是毫秒。
- * ptt:(仅中文支持)是否开启标点符号添加
- *    1:开启(默认值)
- *    0:关闭
- * nunum:(中文普通话和日语支持)将返回结果的数字格式规则为阿拉伯数字格式,默认开启
- *    0:关闭
- *    1:开启
- */
-type AsrServerOptions = {
-  language: string;
-  domain: string;
-  accent: string;
-  vad_eos: number;
-  dwa: string;
-};
-
-const DEFAULT_ASR_SERVER_OPTIONS: AsrServerOptions = {
-  language: "zh-cn",
-  domain: "iat",
-  accent: "mandarin",
-  vad_eos: 10000,
-  dwa: "wpgs",
-};
-
-const isXunFei = globalThis.isXF;
-const closeCommand = isXunFei ? { data: { status: 2 } } : { command: "close" };
-const CHUNK_MULTIPLE = isXunFei ? 1 : 4;
-const CHUNK_LENGTH = 1280 * CHUNK_MULTIPLE;
-const CHECK_INTERVAL = isXunFei ? 40 : 80;
-
-/**
- *
- * @example 音频流 实时文本翻译
- *  ```
- *  let audioTextTransformer  = new AudioTextTransformer();
- * await audioTextTransformer.openMicroPhone();
- * audioTextTramsformter.addEventListener("transform",(arg)=>{
- *   console.log(`当前累计接受到的文本为:` ,arg.transformText)
- * })
- * await audioTextTramsformer.openSocket();
- *
- * // 关闭socket
- * audioTextTransformer.close()
- * ```
- *
- * @example 适用整段音频翻译,适用手机长按交互方式
- * ```
- * let audioTextTransformer = new AudioTextTransformer();
- * await audioTextTransformer.openMicroPhone();
- * await audioTextTransformer.openSocket();
- * let transformText = await audioTextTransformer.transform();
- * ```
- */
-export default class ASR extends BaseEvent {
-  private socket: WebSocket | null = null;
-  private _isTransformed: boolean = false;
-  appKey: string;
-  CONSTANTS: { [key: string]: any };
-  audioData: number[] = [];
-  audioContext: AudioContext | null = null;
-  source: MediaStreamAudioSourceNode | null = null;
-  processor: ScriptProcessorNode | null = null;
-  stream: MediaStream | null = null;
-  transformText: string = "";
-  transformTextFix: number = 0;
-  handleSendInterval: number | null = null;
-  token: string = "";
-  socketPromise: Promise<void> | null = null;
-  asrServerOptions: AsrServerOptions;
-  isPrivilegeGranted: boolean = false;
-  processing: boolean = false;
-
-  constructor(
-    appKey: string,
-    CONSTANTS = {
-      audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false },
-      video: false,
-    },
-    asrServerOptions: AsrServerOptions = DEFAULT_ASR_SERVER_OPTIONS
-  ) {
-    super();
-    this.appKey = appKey;
-    this.CONSTANTS = CONSTANTS;
-    this.asrServerOptions = { ...DEFAULT_ASR_SERVER_OPTIONS, ...asrServerOptions };
-  }
-
-  private _init() {
-    this.socket = null;
-    this.audioData = [];
-    this.transformText = "";
-    this.transformTextFix = 0;
-    this._isTransformed = false;
-  }
-
-  async fetchToken() {
-    if (isXunFei) {
-      const asr_socket = import.meta.env.VITE_XF_ASR;
-      const url = `${asr_socket}/api/human_asr/v2/asr/gen_auth`;
-      const response = await fetch(url, {
-        method: "post",
-        body: JSON.stringify({ app_key: this.appKey }),
-      });
-      const result = await response.json();
-      if (result.err_code !== 0) {
-        throw new Error(result.err_msg);
-      }
-      this.token = result.ret;
-    } else {
-      // this.token = globalThis.token;
-      this.token =
-        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOjkxLCJjb21wYW55X2lkIjo1OSwiZXhwIjoyMDUwOTkxNjU2LCJpYXQiOjE3MzU2MzE2NTYsImlzcyI6Imh1bWFuLWxhcmdlLXNjcmVlbiIsInNjb3BlIjoiIn0.PTR5i63VlosjiepcyV2_KAIQnT-ySFJjggOhqvG5l1Y";
-    }
-  }
-
-  async openMicroPhone() {
-    try {
-      this.stream = await navigator.mediaDevices.getUserMedia(this.CONSTANTS);
-
-      if (navigator.permissions) {
-        navigator.permissions.query({ name: "microphone" }).then((permissionStatus) => {
-          console.log("Microphone permission state:", permissionStatus.state);
-
-          if (permissionStatus.state === "denied") {
-            throw new Error("麦克风权限被拒绝,请在系统设置或浏览器设置中启用!");
-          }
-        });
-      }
-    } catch (e) {
-      console.warn(e);
-      throw new Error("获取麦克风流失败,请检查麦克风是否正常工作");
-      return false;
-    }
-
-    if (!this.isPrivilegeGranted) {
-      this.isPrivilegeGranted = true;
-      return true;
-    }
-
-    return true;
-  }
-
-  startRecord() {
-    this._init();
-    this.audioContext = new window.AudioContext();
-
-    if (!this.stream) {
-      this._eventbus.emit("error", { message: "未获取到麦克风流" });
-      return;
-    }
-
-    this.source = this.audioContext.createMediaStreamSource(this.stream);
-    this.processor = this.audioContext.createScriptProcessor(0, 1, 1);
-    this.processor.connect(this.audioContext.destination);
-    this.source.connect(this.processor);
-    this.processor.onaudioprocess = (audioProcessEvent) => {
-      if (!this.stream || !this.stream.active) {
-        return;
-      }
-      const inputData = audioProcessEvent.inputBuffer.getChannelData(0);
-      const temp = to16kHZ(inputData);
-      const data = to16BitPCM(temp);
-      this.audioData.push(...data);
-    };
-    this.processing = true;
-    this._isTransformed = false;
-  }
-
-  async openSocket(): Promise<void> {
-    const mid = ({ text: str, command = "" }) => {
-      const is_quit = command;
-      if (isXunFei) {
-        this.transformText = this.transformText.slice(0, this.transformTextFix) + str;
-      } else {
-        this.transformText = str;
-      }
-      console.log("mid  ===========", this.transformText);
-      this._eventbus.emit("transform", {
-        type: "mid",
-        current: str,
-        transformText: this.transformText,
-        command: is_quit ? "quit" : "",
-      });
-    };
-    const fin = ({ text: str, command = "" }) => {
-      this.transformTextFix = this.transformText.length;
-      const is_quit = command;
-      if (isXunFei) {
-        this.transformText = this.transformText + str;
-      } else {
-        this.transformText = str;
-      }
-      console.log("fin  ===========", this.transformText);
-      this._eventbus.emit("transform", {
-        type: "fin",
-        current: str,
-        transformText: this.transformText,
-        command: is_quit ? "quit" : "",
-      });
-    };
-    const breath = ({ text: str, command = "" }) => {
-      const is_quit = command;
-      if (isXunFei) {
-        this.transformText = this.transformText.slice(0, this.transformTextFix) + str;
-      } else {
-        this.transformText = str;
-      }
-      this._eventbus.emit("transform", {
-        type: "breath",
-        current: str,
-        transformText: this.transformText,
-        command: is_quit ? "quit" : "",
-      });
-    };
-
-    if (this.handleSendInterval) {
-      clearInterval(this.handleSendInterval);
-    }
-
-    await this.fetchToken();
-
-    this.socketPromise = new Promise((resolve, reject) => {
-      let ws;
-      const protocol = location.href.includes("https") ? "wss://" : "ws://";
-      const domain = location.host;
-      const asrSocket = isXunFei ? import.meta.env.VITE_XF_ASR : import.meta.env.VITE_ASR;
-      let socketHost =
-        asrSocket.startsWith("https") || asrSocket.startsWith("ws")
-          ? new URL(asrSocket).host
-          : domain + (asrSocket.startsWith("/") ? asrSocket : "");
-      let url = "";
-
-      if (isXunFei) {
-        url = `${protocol}${socketHost}/api/human_asr/v2/asr?Authorization=${this.token}`;
-      } else {
-        const project = "qingpu";
-        const engine = "aliyun_dashscope";
-        url = `${protocol}${socketHost}/common/asr_hub?project=${project}&engine=${engine}`;
-
-        url += `&is_long_connection=true`
-        url += `&heartbeat=true`
-
-        if (isMobile()) {
-          const max_sentence_silence = 5000;
-          url += `&max_sentence_silence=${max_sentence_silence}`;
-        }
-      }
-
-      ws = new WebSocket(url);
-      this.socket = ws;
-      ws.onopen = () => {
-        const params = {
-          business: {
-            language: this.asrServerOptions.language,
-            domain: this.asrServerOptions.domain,
-            accent: this.asrServerOptions.accent,
-            vad_eos: this.asrServerOptions.vad_eos,
-            dwa: this.asrServerOptions.dwa,
-          },
-          data: {
-            status: 0,
-            format: "audio/L16;rate=16000",
-            encoding: "raw",
-            audio: "",
-          },
-        };
-        if (isXunFei) {
-          ws.send(JSON.stringify(params));
-        } else {
-          const firstCommand = { card_id: this.asrServerOptions.card_id };
-          ws.send(JSON.stringify(firstCommand));
-        }
-
-        if (this.processing) {
-          this._handleSendAudio();
-        } else {
-          this.close();
-        }
-        resolve();
-      };
-
-      ws.onmessage = async (e: MessageEvent<string>) => {
-        if (!e.data) {
-          return;
-        }
-
-        const response = JSON.parse(e.data);
-        if (isXunFei) {
-          if (response.code == "10165") {
-            await this.openSocket();
-            return;
-          } else if (response.code != "0") {
-            console.warn(response);
-            return;
-          }
-
-          const data = response.data.result;
-          let str = "";
-          const ws = data.ws;
-          for (let i = 0; i < ws.length; i++) {
-            str += ws[i].cw[0].w;
-          }
-          if (data.pgs) {
-            if (data.pgs === "apd") {
-              fin({ text: str });
-            } else {
-              mid({ text: str });
-            }
-          } else {
-            fin({ text: str });
-          }
-          if (data.ls && this.audioData.length && !this._isTransformed) {
-            await this.openSocket();
-          }
-        } else {
-          const data = response;
-          if (data.command === "start") {
-            mid({ text: data.text, command: data.is_quit });
-          } else if (data.command === "stop") {
-            fin({ text: data.text, command: data.is_quit });
-          }
-
-          const commas = [
-            "。",
-            ",",
-            "!",
-            ",",
-            "?",
-            ";",
-            ":",
-            "、",
-            "”",
-            "’",
-            ")",
-            "》",
-            "】",
-          ];
-          if (data.text && commas.includes(data.text[data.text.length - 1]) && data.is_sentence) {
-            breath({ text: data.text, command: data.is_quit });
-          }
-        }
-      };
-
-      ws.onerror = (e) => {
-        this._eventbus.emit("error", e);
-        reject();
-      };
-    });
-
-    return this.socketPromise;
-  }
-
-  private _handleSendAudio() {
-    this._isTransformed = false;
-    this.handleSendInterval = setInterval(() => {
-      if (!isXunFei && this.audioData.length < CHUNK_LENGTH) {
-        return;
-      }
-
-      const data = this.audioData.splice(0, CHUNK_LENGTH);
-      if (
-        !this.socket ||
-        this.socket.readyState !== WebSocket.OPEN ||
-        data.length === 0 ||
-        this._isTransformed
-      ) {
-        return;
-      }
-
-      const buffer = new Int8Array(data);
-      this.socket.send(buffer);
-    }, CHECK_INTERVAL);
-  }
-
-  private _takeOverSendAudioData(): Promise<void> {
-    return new Promise((resolve, reject) => {
-      if (this.audioData.length === 0) {
-        return resolve();
-      }
-      const handleSendInterval = setInterval(() => {
-        if (this.audioData.length === 0) {
-          clearInterval(handleSendInterval);
-          return resolve();
-        }
-        if (!this.socket) {
-          clearInterval(handleSendInterval);
-          reject();
-          return;
-        }
-        if (
-          this.socket.readyState == WebSocket.CONNECTING ||
-          this.socket.readyState == WebSocket.CLOSED ||
-          this.socket.readyState == WebSocket.CLOSING
-        ) {
-          clearInterval(handleSendInterval);
-          reject();
-          return;
-        }
-        const data = this.audioData.splice(0, CHUNK_LENGTH);
-        if (data.length === 0) {
-          return;
-        }
-        const buffer = new Int8Array(data);
-        this.socket.send(buffer);
-      }, CHECK_INTERVAL) as unknown as number;
-    });
-  }
-
-  /**
-   * 停止录音,发送 end,等待讯飞关闭连接,完成所有文字转换
-   */
-  async transform(): Promise<{ text: string; command: string }> {
-    this._isTransformed = true;
-    if (this.stream) {
-      // 断开麦克风的输入
-      this.stream.getTracks().forEach((track) => track.stop());
-    }
-    this.source?.disconnect();
-    this.processor?.disconnect();
-    // 等待所有音频数据发送完毕
-    clearInterval(this.handleSendInterval);
-    this.handleSendInterval = null;
-
-    let tryCount = 0;
-    const MaxTryCount = 5;
-    while (this.audioData.length) {
-      if (
-        this.socket &&
-        this.socket.readyState !== WebSocket.OPEN &&
-        this.socket.readyState !== WebSocket.CONNECTING
-      ) {
-        if (!isXunFei) {
-          break;
-        }
-
-        try {
-          if (this.audioData.length) {
-            await this.openSocket();
-          } else {
-            // 没有音频数据,直接跳出循环
-            break;
-          }
-        } catch (e) {
-          tryCount += 1;
-          // Socket 连接失败尝试, 超过5次无视剩余数据,直接跳出循环,并放回当前获得的文本
-          if (MaxTryCount < tryCount) {
-            break;
-          }
-          continue;
-        }
-      }
-      try {
-        await this.socketPromise;
-        await this._takeOverSendAudioData();
-        tryCount = 0;
-      } catch (e) {
-        console.warn(e);
-      }
-    }
-
-    const socket = this.socket;
-    const promise: Promise<{ text: string; command: string }> = new Promise((resolve) => {
-      if (!socket || socket.readyState !== WebSocket.OPEN) {
-        resolve({ text: this.transformText, command: "end" });
-        this.close();
-        return;
-      }
-      const onLastMessage = (e: MessageEvent<string>) => {
-        if (!e.data) {
-          return;
-        }
-
-        const response = JSON.parse(e.data);
-        if (isXunFei) {
-          if (response.code !== 0) {
-            return;
-          }
-          const data = response.data.result;
-          if (data.ls && this.audioData.length === 0 && this._isTransformed) {
-            resolve({ text: this.transformText, command: "end" });
-            this.close();
-          }
-        } else {
-          const { text, command, is_sentence } = response;
-          if (is_sentence && this.audioData.length == 0 && this._isTransformed) {
-            resolve({ text, command });
-          }
-          this.close();
-        }
-      };
-      const onClose = () => {
-        socket?.removeEventListener("close", onClose);
-        resolve({ text: this.transformText, command: "end" });
-        this.close();
-      };
-      socket.addEventListener("message", onLastMessage);
-      socket.addEventListener("close", onClose);
-    });
-
-    socket.send(JSON.stringify(closeCommand));
-    return promise;
-  }
-  /**
-   * 停止录音 并关闭Socket连接。
-   */
-  close() {
-    this.audioData = [];
-    if (this.handleSendInterval) {
-      clearInterval(this.handleSendInterval);
-      this.handleSendInterval = null;
-    }
-    this.processing = false;
-    this._isTransformed = false;
-    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
-      this.socket.send(JSON.stringify(closeCommand));
-      this.clear();
-    }
-    this.stream?.getTracks().forEach((track) => track.stop());
-  }
-  /**
-   * 清除Socket连接,事件
-   */
-  clear() {
-    this.source?.disconnect();
-    this.processor?.disconnect();
-    if (
-      isXunFei &&
-      this.socket &&
-      (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)
-    ) {
-      this.socket.close();
-    }
-    if (this.handleSendInterval) {
-      clearInterval(this.handleSendInterval);
-    }
-    this._init();
-    this.allClear();
-  }
-}
-
-function to16kHZ(buffer: Float32Array): Float32Array {
-  const data = new Float32Array(buffer);
-  const fitCount = Math.round(data.length * (16000 / 44100));
-  const newData = new Float32Array(fitCount);
-  const springFactor = (data.length - 1) / (fitCount - 1);
-  newData[0] = data[0];
-  for (let i = 1; i < fitCount - 1; i++) {
-    const tmp = i * springFactor;
-    const before = Math.floor(tmp);
-    const after = Math.ceil(tmp);
-    const atPoint = tmp - before;
-    newData[i] = data[before] + (data[after] - data[before]) * atPoint;
-  }
-  newData[fitCount - 1] = data[data.length - 1];
-  return newData;
-}
-
-function to16BitPCM(input) {
-  const dataLength = input.length * (16 / 8);
-  const dataBuffer = new ArrayBuffer(dataLength);
-  const dataView = new DataView(dataBuffer);
-  let offset = 0;
-  for (let i = 0; i < input.length; i++, offset += 2) {
-    const s = Math.max(-1, Math.min(1, input[i]));
-    dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
-  }
-  return Array.from(new Int8Array(dataView.buffer));
-}

+ 16 - 1
src/types/record.ts

@@ -1,4 +1,19 @@
-import { MessageState } from "@/components/stream-message-coordinator";
+/**
+ * 消息状态(渲染层与记录组件共用)。
+ *
+ * 2026-09-24 从 `components/stream-message-coordinator.ts` 搬过来 —— 那个协调器
+ * 整个类已是死代码(无人实例化)被删除,但这个枚举**是活的**
+ * (`BusinessRecord.vue` 与 `types/record.ts` 在用,取值不能动)。
+ */
+export enum MessageState {
+  Update,
+  Sending,
+  Success,
+  Finish,
+  Fail,
+  Stop,
+  End,
+}
 
 /***
  * Message Owner

+ 0 - 8
src/utils/runtime-config.ts

@@ -1,15 +1,7 @@
-export function getModelServerUrl() {
-  return ((globalThis as any).modelServer || "") as string;
-}
-
 export function getApiBaseUrl() {
   return ((globalThis as any).VITE_API || "") as string;
 }
 
-export function isKnowledgeApi() {
-  return !!((globalThis as any).useKnowledgeApi ?? (globalThis as any).isKnowledgeApi);
-}
-
 /**
  * 新版聊天协议后端基础地址(POST /api/chat,SSE)。
  * 优先取 index.html 注入的 globalThis.VITE_CHAT_API(部署时可改),

+ 0 - 6
src/utils/scope-stream-boundary.ts

@@ -1,6 +0,0 @@
-const scopeCloseLineBoundaryReg = /<\/scope>\r?\n/i;
-
-export const getScopeCloseLineBoundaryEnd = (text: string) => {
-  const match = text.match(scopeCloseLineBoundaryReg);
-  return match && typeof match.index === "number" ? match.index + match[0].length : -1;
-};

+ 3 - 8
vite.config.ts

@@ -136,16 +136,11 @@ export default defineConfig(({ mode }) => {
           changeOrigin: true,
           rewrite: (path) => path.replace(/^\/aly-api/, ""),
         },
-        "/asr/": {
-          //匹配的路径
-          target: "https://human-screen-v3.metamaker.cn", //目标url
-          changeOrigin: true, //跨域
-          rewrite: (path) => path.replace(/^\/asr/, ""), //重写路径
-          ws: true,
-        },
         // [已移除] "/stream/" 代理(数字人视频流):src/index.html 里没有任何代码往它发请求,
         // 相关 env(VITE_STREAM_SERVER / VITE_STREAM_TOKEN / VITE_HUMAN_IDLE_MP4_URL)也已删除。
-        // 注意 "/asr/" 代理**保留** —— 它是切到讯飞 ASR 分支(VITE_XF_ASR="/asr")的退路。
+        // [已移除] "/asr/" 代理(https://human-screen-v3.metamaker.cn):旧厂商 ASR 的退路,
+        // 2026-09-24 按用户要求把旧实现连同 env(VITE_ASR / VITE_XF_ASR)一起删除;
+        // 现在的语音识别走阿里云 NLS(见 harness/docs/reference/asr-nls.md)。
       },
     },
     build: {