Pārlūkot izejas kodu

chore: 删除统计埋点老接口 + 清理死代码

用户要求:①删掉统计埋点的老接口 ②清死代码 ③结束后再列接口。
先问清了冲突:用户此前定过「原代码注释掉,不要直接删除」,与「清死代码」按字面=删除
相冲突 —— 用户确认**直接删除**。

一、统计埋点(老接口)整条线删除
- 调用点:BusinessAssistant.vue(页面浏览)、BusinessRecord.vue(反馈统计)
- 整个模块 src/network/api/assistant-statistics.ts 删除
- 已无人用的 env VITE_ASSISTANT_STATISTICS_BASE_URL 从 .env.qingpu 删除
- ⚠️ 只保留访客 id 的读写(getOrCreateAssistantVisitorId)——DMS 的 `访客_<id>`
  归属在用,已搬进 src/network/api/dms/chat-sessions-dms.ts;
  **localStorage key 保持不变**(assistant_statistics_visitor_id),
  改了会让已存在的访客换新 id、同一浏览器在 DMS 里变成两个访客
- 副作用:点赞从「双写」变为只写 DMS;运营侧埋点数据从此不再上报

二、死代码清理
- src/network/api/card/index.js 整文件删除(getMediaList/giveFeedback/appendHistory
  三个函数均无调用方),连同 useBusinessAssistantChat.ts 里那个未使用的 import cardAPI
- PolicyListUpdatedMatch.vue **有意保留**:对新协议无效,但老会话历史里可能仍有
  <policy-list-updated> 块,删了那些卡片列表会渲染不出来

验证:npm run build 通过;vue-tsc 本次涉及文件无报错;
verify-dms-payload-fields 12/12、verify-dms-chat-storage(真实 DMS)43/43;
src 下 assistant-statistics / network/api/card 零引用;validate-harness 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
gongtianxiao 2 dienas atpakaļ
vecāks
revīzija
13e3b0df70

+ 0 - 1
.env.qingpu

@@ -12,7 +12,6 @@ VITE_STREAM_TOKEN="AW+11d70e17340143ac9ecbfd15c16bd4b9%3AMTczOTk1NjQ5OTp6PtY%2FS
 VITE_PARENT_ORIGIN = 'https://aixq.shqp.gov.cn/'
 VITE_MODEL_SERVER="https://qingpu-data-api.metamaker.cn/llms/qingpu"
 VITE_USE_KNOWLEDGE_API=true
-VITE_ASSISTANT_STATISTICS_BASE_URL=https://qingpu-data-api.metamaker.cn/
 VITE_CHAT_API="http://192.168.2.23:8000"
 
 VITE_REDIRECT_MAP={"demo":"https://demo.heijingai.com/FTA-c2p/%23/overview","zhaoshang":"https://demo.heijingai.com/policy-application/%23/"}

+ 12 - 0
README.md

@@ -169,6 +169,18 @@
   - 新增回归脚本:`verify-chat-task-utils.mjs`(33 项,含把「响应式容器破坏对象同一性」
     这个陷阱钉死的断言)、`verify-coordinator-multi-instance.mjs`
     (11 项,验证多实例事件隔离——并行化的地基)
+- 删除统计埋点的老接口(用户要求):
+  - 删除调用点(`BusinessAssistant.vue` 的页面浏览、`BusinessRecord.vue` 的反馈统计)
+    与整个 `src/network/api/assistant-statistics.ts`,以及已无人使用的
+    `VITE_ASSISTANT_STATISTICS_BASE_URL`
+  - **点赞由「双写」变为只写 DMS**;运营侧的埋点数据从此不再上报
+  - 只保留 `getOrCreateAssistantVisitorId`(DMS 的 `访客_<id>` 归属在用),搬进 DMS 模块,
+    **localStorage key 保持不变**(改了会让已存在的访客换新 id)
+- 清理死代码(用户确认直接删除,非注释):
+  - `src/network/api/card/index.js` 整文件删除(三个函数均无调用方)
+    及其未使用的 import
+  - `PolicyListUpdatedMatch.vue` **有意保留**——老会话历史里可能仍有
+    `<policy-list-updated>` 块,删了那些卡片列表会渲染不出来
 - DMS 写入范围收窄为**只写问答记录**:
   - **1887 助手会话不再写入**(用户指示「只传具体的会话记录,另一个接口不传了」)——
     新建会话、改名都不再同步;会话列表退化为纯本地(与接 DMS 之前一致)

+ 15 - 16
harness/docs/reference/current-api-call-sites.md

@@ -45,16 +45,16 @@
 > ⚠️ 前端**只用企业信息的 `name` 与 `credit_code` 两个字段**(其余 19 个字段零引用)——
 > 所以真要用 DMS 的企业信息(栏目 1888)替换它,成本很低。
 
-### 2. 使用统计(页面浏览 / 回答反馈)—— **在用,且与 DMS 双写**
+### ~~2. 使用统计(页面浏览 / 回答反馈)~~ —— **已删除(2026-09-18)**
 
-| 埋点 | 用户什么时候触发 | 上报内容 |
-|---|---|---|
-| 页面浏览 | 打开页面(`onMounted`) | `visitor_id`(localStorage 里的访客 id)、`business_code`(= policyMode)、`env`(= 构建 mode) |
-| 回答反馈 | **点赞 / 点踩 / 取消** | 同上 + 反馈内容 |
-
-- 接口:`POST {统计基址}/assistant_statistics/page_view`、`/assistant_statistics/feedback`
-- 基址取 `VITE_ASSISTANT_STATISTICS_BASE_URL`(**只有 `.env.qingpu` 配了**),否则回退 `VITE_API`
-- ⚠️ **点赞是双写**:既写 DMS 的 `c_feedback_*`(业务数据),又发统计接口(运营数据
+用户要求删掉统计埋点的老接口。已删除:
+- 调用点:`BusinessAssistant.vue`(页面浏览)、`BusinessRecord.vue`(反馈统计)
+- 整个模块 `src/network/api/assistant-statistics.ts`(连同 `VITE_ASSISTANT_STATISTICS_BASE_URL` 这个已无人用的 env)
+- ⚠️ **只留下了访客 id 的读写**(`getOrCreateAssistantVisitorId`),因为 **DMS 记录访客归属
+  (`访客_<id>`)在用它** —— 已搬进 `src/network/api/dms/chat-sessions-dms.ts`。
+  **localStorage 的 key 字符串保持原样**(`assistant_statistics_visitor_id`):
+  改了会让已存在的访客全部换新 id,同一浏览器在 DMS 里就变成两个访客了。
+- 现在**点赞只写 DMS**(原先双写
 
 ### 3. 语音输入 —— **在用**
 
@@ -84,15 +84,14 @@
 
 ## 三、已经没有人调用的(死代码,只标注未删)
 
-| 位置 | 原本支撑的功能 | 为什么死了 |
+| 位置 | 原本支撑的功能 | 处置 |
 |---|---|---|
-| `src/network/api/card/index.js` 的 `getMediaList` | 卡片里的媒体文件信息(图片/附件的展示) | 无调用方 |
-| 同上的 `giveFeedback` | 点赞点踩**落库**(旧接口版) | 反馈改走 DMS 后,最后一个调用点消失 |
-| 同上的 `appendHistory` | 把每轮问答追加到服务端历史 | 早就注释掉了,历史改由接口的 `enable_history` 控制 |
-| `useBusinessAssistantChat.ts:2` 的 `import cardAPI` | — | **未使用的 import**,是上面这个模块**唯一**的引用(2026-09-18 复查仍在) |
-| `PolicyListUpdatedMatch.vue` | 旧协议 `<policy-list-updated>` 卡片列表 | 仍被 `BusinessRecord` 挂载,但**新协议不再产出这种块**,只有旧历史内容可能触发 |
+| `src/network/api/card/index.js`(`getMediaList` / `giveFeedback` / `appendHistory`) | 卡片媒体信息、点赞点踩的**旧接口版落库**、追加服务端历史 | ✅ **2026-09-18 整文件删除**(连同 `useBusinessAssistantChat.ts` 里那个未使用的 `import cardAPI`) |
+| `src/network/api/assistant-statistics.ts` | 使用统计埋点 | ✅ **2026-09-18 整文件删除**(访客 id 那部分已搬进 DMS 模块,见第二节) |
+| `PolicyListUpdatedMatch.vue` | 旧协议 `<policy-list-updated>` 卡片列表 | ⚠️ **保留**:仍被 `BusinessRecord` 挂载,**老会话的历史内容里可能还有这种块**,删了那些卡片列表就渲染不出来 |
 
-> 清理这些要另开一轮,并先确认没有旧数据依赖。
+> 删除的代码都在 git 历史里(`git log --diff-filter=D --name-only` 可查)。
+> 上面「保留」那条不是遗漏 —— 它虽对新协议无效,但**对旧历史仍然有效**。
 
 ## 四、复核方法(可重跑)
 

+ 38 - 0
harness/progress.md

@@ -1874,3 +1874,41 @@
     的唯一引用)——**要清理另说**,属独立的小清理
 - **下一步最佳动作**:无(本轮为清点)
 
+## Session 045
+
+- **日期**:2026-09-18
+- **本轮目标**:用户要求 ①删掉统计埋点的老接口 ②清死代码 ③结束后再列接口
+- **先问清了冲突**:用户此前定过「原代码注释掉,不要直接删除」(Session 017),
+  与「清死代码」按字面=删除相冲突 → 用户确认**直接删除**
+- **已完成**:
+  1. **统计埋点(老接口)整条线删除**:
+     - 调用点:`BusinessAssistant.vue`(页面浏览)、`BusinessRecord.vue`(反馈统计)
+     - 整个模块 `src/network/api/assistant-statistics.ts` 删除
+     - 已无人用的 env `VITE_ASSISTANT_STATISTICS_BASE_URL` 从 `.env.qingpu` 删除
+     - ⚠️ **只留下访客 id 的读写**(`getOrCreateAssistantVisitorId`)——**DMS 的
+       `访客_<id>` 归属在用它**,已搬进 `src/network/api/dms/chat-sessions-dms.ts`;
+       **localStorage 的 key 字符串保持不变**(`assistant_statistics_visitor_id`),
+       改了会让已存在的访客换新 id、同一浏览器在 DMS 里变成两个访客
+     - 副作用:**点赞从「双写」变成只写 DMS**
+  2. **死代码清理**(用户确认直接删):
+     - `src/network/api/card/index.js` **整文件删除**(三个函数都无调用方),
+       连同 `useBusinessAssistantChat.ts` 里那个未使用的 `import cardAPI`
+     - `assistant-statistics.ts`(同上)
+     - ⚠️ **`PolicyListUpdatedMatch.vue` 有意保留**:它虽对新协议无效,
+       但**老会话的历史内容里可能还有 `<policy-list-updated>` 块**,删了那些卡片列表渲染不出来
+- **运行过的验证**:
+  - `npm run build` 通过;`vue-tsc --noEmit` 本次涉及文件无报错
+  - `verify-dms-payload-fields.mjs` **12/12**;`verify-dms-chat-storage.mjs`(真实 DMS)**43/43**
+  - 残留检查:`assistant-statistics` / `network/api/card` 在 src 下零引用;`validate-harness` 通过
+- **已记录证据**:本文件 Session 045;`docs/reference/current-api-call-sites.md`
+- **更新过的文件或工件**:删 `src/network/api/{card/index.js,assistant-statistics.ts}`;
+  改 `src/network/api/dms/chat-sessions-dms.ts`、`src/components/BusinessAssistant.vue`、
+  `src/components/Chat/BusinessRecord.vue`、
+  `src/components/business-assistant/useBusinessAssistantChat.ts`、`.env.qingpu`、
+  `docs/reference/current-api-call-sites.md`、根 `README.md`、本文件
+- **已知风险或未解决问题**:
+  - ⚠️ **统计埋点数据从此不再上报**:若运营侧依赖 `page_view` / `feedback` 看板,需要告知他们
+    (这是用户明确要求删除的)
+  - ⚠️ 删掉的代码在 git 历史里可查(`git log --diff-filter=D --name-only`)
+- **下一步最佳动作**:把当前在用的接口清单给用户(本轮第三项要求)
+

+ 1 - 4
src/components/BusinessAssistant.vue

@@ -10,7 +10,6 @@ import { ref, onMounted, onUnmounted } from 'vue';
 import { isMobile } from '@/utils';
 import BusinessAssistantPC from './BusinessAssistantPC.vue';
 import BusinessAssistantMobile from './BusinessAssistantMobile.vue';
-import { recordCurrentAssistantPageView } from '@/network/api/assistant-statistics';
 
 const isMobileView = ref(isMobile());
 
@@ -42,9 +41,7 @@ const initSequence = () => {
 
 onMounted(() => {
   initSequence();
-  void recordCurrentAssistantPageView({
-    apiBaseUrl: import.meta.env.VITE_API || "",
-  });
+  // [已移除] 页面浏览埋点:统计接口(老接口)已按用户要求删除(2026-09-18)
   window.addEventListener('resize', handleResize);
   handleResize();
 });

+ 2 - 7
src/components/Chat/BusinessRecord.vue

@@ -366,7 +366,6 @@ import {
 } from "vue";
 import { MessageState } from "../stream-message-coordinator";
 import { FeedbackType, From } from "@/types/record";
-import { reportAssistantFeedback } from "@/network/api/assistant-statistics";
 import { writeDmsFeedback } from "@/network/api/dms/chat-sessions-dms";
 import DotsAnim from "../Common/DotsAnim.vue";
 import BarAnim from "../Common/BarAnim.vue";
@@ -1872,12 +1871,8 @@ function handleError(e: any) {
 
 async function handleFeedbackChange(feedback: FeedbackType) {
   localFeedback.value = feedback;
-  if (props.enableFeedback) {
-    reportAssistantFeedback({
-      record_id: String(record.value.id),
-      status: feedback,
-    });
-  }
+  // [已移除] 反馈统计埋点:统计接口(老接口)已按用户要求删除(2026-09-18)。
+  // 反馈本身仍写入 DMS(见 submitLikeFeedback / cancelFeedback / handleDislikeConfirm)
   emit("feedback", feedback, record.value.id);
 }
 </script>

+ 0 - 1
src/components/business-assistant/useBusinessAssistantChat.ts

@@ -1,5 +1,4 @@
 import { computed, nextTick, provide, ref, shallowRef, watch } from "vue";
-import cardAPI from '@/network/api/card/index';
 import { isZhaoshangLLM } from '@/utils';
 import { getChatApiBaseUrl } from '@/utils/runtime-config';
 // [旧协议] 旧聊天流式协调器(/knowledge/chat),已由 api-chat-coordinator 取代

+ 0 - 179
src/network/api/assistant-statistics.ts

@@ -1,179 +0,0 @@
-import { isZhaoshangStagingVariant } from "@/utils/llm-model";
-
-export const ASSISTANT_STATISTICS_VISITOR_ID_KEY = "assistant_statistics_visitor_id";
-
-const ASSISTANT_STATISTICS_PAGE_VIEW_URL = "/assistant_statistics/page_view";
-
-const BUSINESS_CODE_BY_POLICY_MODE: Record<string, string> = {
-  zhaoshang: "business_assistant",
-  zhaoshang_staging: "business_assistant",
-  zhaoshang_safe: "business_assistant",
-};
-
-const STATISTICS_ENV_BY_MODE: Record<string, string> = {
-  qingpu: "release",
-  production: "test",
-};
-
-type Fetcher = typeof fetch;
-
-type RecordAssistantPageViewOptions = {
-  apiBaseUrl?: string;
-  fetcher?: Fetcher;
-  idFactory?: () => string;
-  mode?: string;
-  policyMode?: string;
-};
-
-export function resolveAssistantBusinessCode(policyMode?: string | null) {
-  if (!policyMode) {
-    return "";
-  }
-
-  // 兼容 A/B 测试用的 staging 变体:zhaoshang_staging_1, zhaoshang_staging_2, ...
-  if (isZhaoshangStagingVariant(policyMode)) {
-    return "business_assistant";
-  }
-
-  return BUSINESS_CODE_BY_POLICY_MODE[policyMode] || "";
-}
-
-export function resolveAssistantStatisticsEnv(mode?: string | null) {
-  return STATISTICS_ENV_BY_MODE[mode || ""] || "test";
-}
-
-function defaultIdFactory() {
-  const cryptoRef = globalThis.crypto;
-  if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
-    return cryptoRef.randomUUID();
-  }
-
-  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
-    const random = Math.floor(Math.random() * 16);
-    const value = char === "x" ? random : (random & 0x3) | 0x8;
-    return value.toString(16);
-  });
-}
-
-export function getOrCreateAssistantVisitorId(idFactory = defaultIdFactory) {
-  const storage = globalThis.localStorage;
-  const storedVisitorId = storage?.getItem(ASSISTANT_STATISTICS_VISITOR_ID_KEY);
-
-  if (storedVisitorId) {
-    return storedVisitorId;
-  }
-
-  const visitorId = idFactory();
-  storage?.setItem(ASSISTANT_STATISTICS_VISITOR_ID_KEY, visitorId);
-  return visitorId;
-}
-
-function resolvePageViewUrl(apiBaseUrl?: string) {
-  const baseUrl = import.meta.env.VITE_ASSISTANT_STATISTICS_BASE_URL || apiBaseUrl || "";
-  return `${baseUrl.replace(/\/$/, "")}${ASSISTANT_STATISTICS_PAGE_VIEW_URL}`;
-}
-
-export async function recordAssistantPageView(options: RecordAssistantPageViewOptions = {}) {
-  const businessCode = resolveAssistantBusinessCode(options.policyMode);
-
-  if (!businessCode) {
-    return false;
-  }
-
-  const fetcher = options.fetcher || globalThis.fetch;
-  if (typeof fetcher !== "function") {
-    return false;
-  }
-
-  const token = (globalThis as typeof globalThis & { token?: string }).token || "";
-
-  const source = (globalThis as any).source;
-    const body: Record<string, unknown> = {
-      visitor_id: getOrCreateAssistantVisitorId(options.idFactory),
-      business_code: businessCode,
-      env: resolveAssistantStatisticsEnv(options.mode),
-    };
-    if (source) {
-      body.source = source;
-    }
-
-    try {
-      const response = await fetcher(resolvePageViewUrl(options.apiBaseUrl), {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-          Authorization: token,
-        },
-        body: JSON.stringify(body),
-      });
-
-    return response.ok;
-  } catch (error) {
-    console.warn("record assistant page view failed", error);
-    return false;
-  }
-}
-
-type AssistantFeedbackPayload = {
-  record_id: string;
-  status: number;
-};
-
-export async function reportAssistantFeedback(
-  payload: AssistantFeedbackPayload,
-  options: RecordAssistantPageViewOptions = {},
-) {
-  const businessCode = resolveAssistantBusinessCode(options.policyMode);
-
-  if (!businessCode) {
-    return false;
-  }
-
-  const fetcher = options.fetcher || globalThis.fetch;
-  if (typeof fetcher !== "function") {
-    return false;
-  }
-
-  const token = (globalThis as typeof globalThis & { token?: string }).token || "";
-
-  const body: Record<string, unknown> = {
-    ...payload,
-    visitor_id: getOrCreateAssistantVisitorId(options.idFactory),
-    business_code: businessCode,
-    env: resolveAssistantStatisticsEnv(options.mode),
-  };
-
-  try {
-    const baseUrl = import.meta.env.VITE_ASSISTANT_STATISTICS_BASE_URL || options.apiBaseUrl || "";
-    const url = `${baseUrl.replace(/\/$/, "")}/assistant_statistics/feedback`;
-
-    const response = await fetcher(url, {
-      method: "POST",
-      headers: {
-        "Content-Type": "application/json",
-        Authorization: token,
-      },
-      body: JSON.stringify(body),
-    });
-
-    return response.ok;
-  } catch (error) {
-    console.warn("report assistant feedback failed", error);
-    return false;
-  }
-}
-
-export function recordCurrentAssistantPageView(
-  options: Omit<RecordAssistantPageViewOptions, "mode" | "policyMode"> = {}
-) {
-  const appGlobal = globalThis as typeof globalThis & {
-    mode?: string;
-    policyMode?: string;
-  };
-
-  return recordAssistantPageView({
-    ...options,
-    mode: appGlobal.mode,
-    policyMode: appGlobal.policyMode,
-  });
-}

+ 0 - 76
src/network/api/card/index.js

@@ -1,76 +0,0 @@
-import { isKnowledgeApi } from "@/utils/runtime-config";
-import { getQueryObj } from "@/components/utils";
-
-const cardURL = import.meta.env.VITE_API
-
-export default {
-  async getMediaList(ids) {
-    let token = globalThis.token;
-    const response = await fetch(cardURL + "/dialog/file_info" + "?ids=" + ids.join(","), {
-      method: "get",
-      headers: {
-        "Content-Type": "application/x-www-form-urlencoded",
-        Authorization: token,
-      },
-    }).catch((err) => {
-      console.warn(err);
-      globalThis.changeLoadingText(err);
-    });
-    return response.json();
-  },
-
-  async giveFeedback(payload) {
-    let { status, record_id, option, remark } = payload;
-    let token = globalThis.token;
-    const body = {
-      id: record_id,
-      status,
-      ...(option !== undefined ? { option } : {}),
-      ...(remark !== undefined ? { remark } : {}),
-    };
-    const response = await fetch(cardURL + "/chat/feedback", {
-      method: "post",
-      headers: {
-        "Content-Type": "application/json",
-        Authorization: token,
-      },
-      body: JSON.stringify(body),
-    }).catch((err) => {
-      console.warn(err);
-      globalThis.changeLoadingText(err);
-    });
-    return response.json();
-  },
-
-  async appendHistory(payload) {
-    let token = globalThis.token;
-    let card_id = parseInt(getQueryObj("card_id"));
-    let { record_id } = payload;
-    const historyURL =
-      cardURL +
-      (isKnowledgeApi() ? "/knowledge/append_history" : "/dialog/append_history");
-    const response = await fetch(historyURL, {
-      method: "post",
-      headers: {
-        "Content-Type": "application/json",
-        Authorization: token,
-      },
-      body: JSON.stringify({
-        card_id,
-        id: record_id,
-        ...payload,
-      }),
-    }).catch((err) => {
-      console.warn(err);
-      globalThis.changeLoadingText(err);
-    });
-    const result = await response.json();
-
-    if (result.err_code !== 0) {
-      console.log(result.err_msg);
-      return false;
-    }
-
-    return result.ret;
-  },
-};

+ 32 - 1
src/network/api/dms/chat-sessions-dms.ts

@@ -30,9 +30,40 @@ import {
   type DmsRow,
 } from "./client";
 import { enterpriseInfo } from "@/components/useEnterpriseAuth";
-import { getOrCreateAssistantVisitorId } from "@/network/api/assistant-statistics";
 import type { RemoteSession, RemoteSessionRecord } from "@/network/api/chat-sessions";
 
+/**
+ * 访客标识(未登录时 DMS 记录的归属:`访客_<这个 id>`)。
+ *
+ * ⚠️ **localStorage 的 key 字符串不要改**:它沿用统计埋点时代的名字
+ * (`assistant_statistics_visitor_id`)。改了会让已存在的访客全部换新 id,
+ * 同一浏览器在 DMS 里就变成两个访客了。统计埋点本身已删除(2026-09-18),
+ * 但**这个 key 保留**、由 DMS 接手使用。
+ */
+export const GUEST_VISITOR_ID_KEY = "assistant_statistics_visitor_id";
+
+const defaultVisitorIdFactory = () => {
+  const cryptoRef = globalThis.crypto;
+  if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
+    return cryptoRef.randomUUID();
+  }
+  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
+    const random = Math.floor(Math.random() * 16);
+    const value = char === "x" ? random : (random & 0x3) | 0x8;
+    return value.toString(16);
+  });
+};
+
+export const getOrCreateAssistantVisitorId = (idFactory = defaultVisitorIdFactory) => {
+  const storage = globalThis.localStorage;
+  const stored = storage?.getItem(GUEST_VISITOR_ID_KEY);
+  if (stored) return stored;
+
+  const visitorId = idFactory();
+  storage?.setItem(GUEST_VISITOR_ID_KEY, visitorId);
+  return visitorId;
+};
+
 /** 本轮的来源标识,与其它接口保持一致 */
 const resolveSource = (): string => ((globalThis as any).source as string) || "zhaoshang";