Procházet zdrojové kódy

docs(ai-changes): 补本分支改动说明并约定「改动说明随分支提交」

把本分支(M1 点选补令牌 / M2 结构化错误 / M3 管理入口按 is_admin 收敛)的改动说明
放进 docs/ai-changes/,随分支一起提交,使其出现在 Gogs 的改动对照页面
(compare/master...fix/base-agent-token)里,人工审核无需再翻外部文档。

内容含:提交清单、逐文件改动、每项「原来的行为/现在的行为/为什么改/与原来的差别」、
浏览器验收证据(M1 3/3、M2 5/5、M3 9/9,含验收中发现并修复的 is_admin 未拉取缺陷)、
静态检查结果(含既有问题说明)、已知取舍、交付与上游同步状态。

同时新增 docs/ai-changes/README.md 约定命名(<YYYYMMDD>-<主题>.md)与必备章节。
lizuo před 1 týdnem
rodič
revize
53aa9de96e

+ 265 - 0
docs/ai-changes/20260915-mky-vent-base-agent-token.md

@@ -0,0 +1,265 @@
+# AI 改动说明 · mky-vent-base · 点选令牌 + 结构化错误 + 管理入口收敛
+
+> **对照网址**:
+> 主仓库 <http://39.97.59.228:8013/hrx/mky-vent-base/compare/master...fix/base-agent-token>
+> fork   <http://39.97.59.228:8013/lizuo/mky-vent-base/compare/master...fix/base-agent-token>
+>
+> 依据:`/root/TFAgents/docs/基座(mky-vent-base)配合改动清单.md`(M1 / M2 / M3 三项必改)
+> 分支:`fix/base-agent-token` 基线:`b3ec299b`(交付时已 merge `upstream/master` → `f25e5319`)
+> 日期:2026-09-15 交付版本:`0b3c1857`(本说明为随后追加的提交)
+> 状态:真实浏览器验收通过(M1 3/3、M2 5/5、M3 9/9);已交付主仓库同名分支,待人工审核合并;**未部署**
+
+---
+
+## 一、这次改了什么(提交清单)
+
+```
+<本次>   docs(ai-changes): 补本分支改动说明(本文件)
+0b3c1857  Merge upstream/master(b3ec299b..f25e5319)
+29d9be9e  fix(ventAI): 验收发现并修复 is_admin 仅 watch 拉取导致管理员也看不到入口 + 注释补全
+aef76ebb  feat(ventAI): 非管理员隐藏技能/子智能体/思考级别等全局变更入口 (M3)
+2c2673eb  fix(ventAI): 点击解读路径复用 StreamError 结构化错误并转可读文案 (M2)
+21b2fc53  fix(ventAI): 点击解读 SSE 请求携带 X-Access-Token 鉴权头 (M1)
+b3ec299b  ← 基线(当时的 upstream/master)
+```
+
+**改动面:6 个文件,+166 / −13**(其中约 100 行为审核注释,功能代码约 60 行;本说明文件不计入)。
+
+| 文件 | 改了什么 |
+|---|---|
+| `src/views/ventAI/dataPicker/api.ts` | M1 请求头补令牌;M2 复用结构化错误解析 |
+| `src/views/ventAI/dataPicker/useDataPicker.ts` | M2 新增 `formatClickError()`,把 429/413/404 转中文文案 |
+| `src/views/ventAI/manageAssistent/api.ts` | M2 导出 `throwStreamHttpError`;M3 新增 `MeInfo` 与 `getMe()` |
+| `.../manageAssistent/components/AiAssistantModal.vue` | M3 拉取并持有 `isAdmin`,透传给两个子组件 |
+| `.../components/chatModal/TaskListPanel.vue` | M3 按 `isAdmin` 过滤「技能 / 子智能体」入口 |
+| `.../components/chatModal/ChatInputArea.vue` | M3 非管理员隐藏「思考级别」切换控件 |
+
+---
+
+## 二、逐项说明:改了什么、为什么这么改、与原来的差别
+
+### M1 · 点选解读补 `X-Access-Token`
+
+**位置**:`src/views/ventAI/dataPicker/api.ts`(`ssePost()`)
+
+**原来的行为**:
+```ts
+headers: { 'Content-Type': 'application/json' },   // 完全没有令牌
+```
+点选解读是基座里**唯一**不带令牌调 TfAgents 的路径。后果:TfAgents 无法识别是谁在用,
+只能把消耗记到兜底账号 `click-anonymous`,**做不到「按登录人员记账」**(决策 D5 / B1-20 的明确要求)。
+
+**现在的行为**:
+```ts
+headers: {
+  'Content-Type': 'application/json',
+  'X-Access-Token': getToken(),   // 与对话链路 manageAssistent/api.ts:215 同名同源
+},
+```
+
+**关键设计点(审核重点)**:
+- 令牌是**可选**语义:`getToken()` 为空时**仍照常发请求**,没有加任何「未登录就拦截 / 跳登录」的判断。
+  TfAgents 侧刻意保留 `click-anonymous` 兜底,若在此强制登录,会把未登录/令牌过期场景直接变成不可用。
+- 头名 `X-Access-Token` 与仓库既有写法一致;TfAgents 读小写 `x-access-token`,HTTP 头不区分大小写。
+- 该请求用 `fetch` + `ReadableStream`(非 `EventSource`),故可自定义请求头,无技术障碍。
+
+**与原来的差别**:请求头由「只有 Content-Type」变为「Content-Type + X-Access-Token」;
+请求体、返回值 `{thread_id, session_id}`、事件解析**完全没动**。
+
+---
+
+### M2 · 点选错误改用结构化解析
+
+**位置**:`manageAssistent/api.ts`(导出解析器)、`dataPicker/api.ts`(调用)、`useDataPicker.ts`(渲染文案)
+
+**原来的行为**:
+```ts
+if (!response.ok) {
+  throw new Error(`HTTP错误: ${response.status}`);   // 用户只能看到「HTTP错误: 429」
+}
+```
+
+**现在的行为**:三步,全部复用仓库既有能力,**没有新写第二套解析器**。
+1. `throwStreamHttpError` 由模块内私有改为 `export`(**函数体零改动**);
+2. 点选链路改为 `await throwStreamHttpError(response)` —— 它解析响应体的 `detail`
+   (字符串或 `{ code, message, retry_after, limit_tokens, spent_tokens }`),抛出带
+   `status / retryAfter / limitTokens / spentTokens` 的 `StreamError`;
+3. `useDataPicker.ts` 新增本地 `formatClickError()`,把结构化错误渲染成中文:
+
+| 场景 | 展示文案 |
+|---|---|
+| 429 且带 `retry_after` | 服务端 message + `(约15分钟后恢复)`(不足 1 分钟显示「不到 1 分钟」) |
+| 429 无 `retry_after` | 服务端 message + `(请稍后再试)` |
+| 413 且服务端无文案 | `附件体积超过上限,请减小文件后重试` |
+| 404 且服务端无文案 | `会话不存在或无权访问` |
+| 其它 / 普通 Error | 保持原样 `e?.message \|\| '请求失败'` |
+
+**为什么必须改**:TfAgents 之后对 429(额度/并发闸)、413(体积超限)、404(会话归属)返回结构化错误体
+(D6 / B1-15 / B1-19 / B1-08),前端必须能翻译成人话。
+
+**与原来的差别**:错误提示由「HTTP错误: 429」变为可读中文;**兜底路径不变**
+(非 `StreamError` 的异常仍原样透出 message),不会把网络中断等场景改坏。
+
+---
+
+### M3 · 管理入口按 `is_admin` 收敛(已定案「方案 B」)
+
+**背景**:TfAgents 按决策 D1 / D9 把 9 个「全局变更」端点收归管理员(白名单在其 `.env`,先留空)。
+基座原先对这些入口**没有任何角色判断**,非管理员点了才吃 403。
+
+**后端契约**(TfAgents 侧,B2-01 追加需求):
+```
+GET /api/me   (需认证:X-Access-Token)
+→ 200 { "username": "...", "is_admin": true|false, "admin_configured": true|false }
+→ 401 未登录 / 令牌无效
+```
+基座侧对应 URL 为 `/ventAI/api/me`(`/ventAI` 反代到 TfAgents)。
+
+**基座四处改动**:
+
+1. **`manageAssistent/api.ts`** 新增 `MeInfo` 与 `getMe()`:
+   ```ts
+   export const getMe = () => defHttp.get<MeInfo>(
+     { url: Api.me },
+     { isTransformResponse: false, errorMessageMode: 'none' }
+   );
+   ```
+   - **不手写请求头**:走 `defHttp` 时 axios 拦截器自动附 `Authorization` 与 `X-Access-Token`
+     (`src/utils/http/axios/index.ts:209-216`)。
+   - `errorMessageMode: 'none'` 是**刻意加的**:`/api/me` 在 TfAgents 上线前返回 404,
+     若用默认错误模式,**每次打开 AI 弹框都会弹红色报错**,会被误判为故障。
+
+2. **`AiAssistantModal.vue`** 新增 `isAdmin` / `adminConfigured` 两个 ref 与 `fetchMe()`,
+   并在**两处**触发:`onMounted` 一次 + `visible` 的 watch 里一次。
+   - 初值 `isAdmin = false`:接口返回前先按非管理员渲染,避免管理入口「闪现」给非管理员。
+   - 失败降级为**保守失败**:未登录 / 接口未上线 / 网络失败 → 一律 `isAdmin = false`(隐藏入口)。
+   - **`fetchModelMsg()` 未做任何改动**:思考级别的**读取**对所有人保持开放,只收敛「切换」。
+
+3. **`TaskListPanel.vue`**:`optionItems` 由普通数组改为 `computed` 并按 `isAdmin` 过滤。
+   - 「技能」「子智能体」→ 仅管理员可见;「新建任务」「定时任务」→ 对所有人开放
+     (定时任务属个人功能,不在收敛范围内)。
+   - 用 `computed` 而非普通数组,是为了 `isAdmin` 异步返回后能**自动重算**,无需刷新页面。
+
+4. **`ChatInputArea.vue`**:仅给 `.btn-think-wrapper`(思考级别切换控件)加 `v-if="isAdmin"`。
+   - **只包住这一个块**:左右分隔线与发送按钮保持原样,不改按钮区布局。
+   - `handleSelectThinkLevel` / `thinkLevelOptions` / `initialThinkLevel` 的 watch 均未改动。
+
+**与原来的差别**:
+
+| | 原来 | 现在 |
+|---|---|---|
+| 非管理员看到「技能」入口 | 是 | **否** |
+| 非管理员看到「子智能体」入口 | 是 | **否** |
+| 非管理员看到「思考级别」切换 | 是 | **否** |
+| 非管理员看到「定时任务」 | 是 | 是(不变) |
+| 思考级别读取(`getModelMsg`) | 开放 | 开放(不变) |
+| `/api/me` 不可用时 | — | **保守隐藏**(不是全部放开) |
+
+> ⚠️ **这是界面可见性,不是安全边界**:真正的拦截在 TfAgents 后端。前端隐藏只是让非管理员
+> 不用「点了才知道没权限」,不能替代后端鉴权。
+
+---
+
+## 三、浏览器真实验收(Playwright + 真实 Chromium)
+
+**方法**:用基座自带 **mock 登录**(`/?mock-login=1`)取得**真实登录令牌**,再驱动真实页面。
+点选链路用临时验收页挂载**真实的 `v-data-picker` 指令**(`mode: 'click'`),
+AI 弹框用临时验收页挂载**真实的 `AiAssistantModal` 组件**;`/api/me` 用请求拦截注入不同角色响应。
+**临时页与临时路由已在验收后全部删除**,工作区只剩 6 个目标文件(本说明文件除外)。
+
+### 验收结果
+
+| 项 | 断言 | 结果 |
+|---|---|---|
+| M1-1 | 点击后确实向点选解读接口发出请求(`/ventAI/api/interpret/click/tun`) | ✅ |
+| M1-2 | 请求头含 `X-Access-Token` 且非空 | ✅ |
+| M1-3 | 该令牌与登录态令牌**同源一致** | ✅ |
+| M2-1 | 429 显示服务端结构化中文文案 | ✅ `今日额度已用完,明日 00:00 恢复(约15分钟后恢复)` |
+| M2-2 | 不再出现旧的「HTTP错误: 429」 | ✅ |
+| M2-3 | 429 附加「约 X 分钟后恢复」 | ✅ |
+| M2-413 | 413 显示中文文案且不含「HTTP错误」 | ✅ `上传体积超过上限` |
+| M2-404 | 404 显示中文文案且不含「HTTP错误」 | ✅ `会话不存在或无权访问` |
+| M3-1~3 | 非管理员看不到技能 / 子智能体 / 思考级别 | ✅ 计数 0/0/0 |
+| M3-4~6 | 管理员能看到技能 / 子智能体 / 思考级别 | ✅ 计数 1/1/1 |
+| M3-7 | `/api/me` 未上线(404)时保守降级隐藏 | ✅ 计数 0/0/0 |
+
+M1+M2 合计 8/8 通过;M3 合计 9/9 通过(含前置项)。
+
+### 🔴 验收中发现并已修复的真实缺陷(`29d9be9e`)
+
+M3 初版把 `fetchMe()` **只**放在 `visible` 的 `watch` 里。但本组件存在「**挂载即显示**」的用法
+(它在 `layouts/default/index.vue` 与 `homeAI/index.vue` 中由页面持有 `visible`),此时 watch 不触发
+→ **`getMe` 从未被调用** → `isAdmin` 恒为 `false` → **管理员同样看不到管理入口**。
+
+- **实测证据**:拦截 `/api/me` 计数为 **0**;管理员可见性 `skill=0 / subAgents=0 / think=0`。
+- **修复**:增加 `onMounted(() => fetchMe())`,挂载即拉取一次;watch 内保留原有调用
+  (用于「每次打开都刷新」,管理员名单可能在服务端 `.env` 变更)。
+- **修复后**:管理员 `1/1/1`,非管理员 `0/0/0`,404 时 `0/0/0`。
+
+> 该缺陷**纯静态审查(lint / 类型检查)发现不了**——初版代码能编译、能跑、lint 也干净,
+> 只是 `is_admin` 从头到尾没被取过。这正是坚持做浏览器验收的价值。
+
+### 验收环境说明
+
+- dev server:`vite`(端口 3100,本机起、验收后已停)。
+- TfAgents 后端:`182.92.126.35:8070` 真实可达(`/api/model` 返回 200);
+  **`/api/me` 尚未实现**(全仓 grep 无实现,仅文档),故 M3 用请求拦截注入角色响应
+  ——这也顺带验证了「接口未上线即保守降级」。
+- 已知无关噪声(非本次改动引入):VentModelWeb 子应用卸载报错 `P$.a.off is not a function`
+  (既有问题)、dev 环境 `/js/libs/adapter.min.js` 等静态资源 404、Jeecg WebSocket 连不上。
+
+---
+
+## 四、静态检查
+
+| 检查 | 命令 | 结果 |
+|---|---|---|
+| ESLint | `node_modules/.bin/eslint <6 个文件>` | 8 个错误,**全在 `ChatInputArea.vue` 的既有未使用变量**(`handleSelectModel` 等);在基线 `b3ec299b` 复跑得到**同样 8 个**(仅行号偏移)→ **无新增问题** |
+| TypeScript | `node_modules/.bin/vue-tsc --noEmit -p tsconfig.json` | ⚠️ **无法完成**:默认堆 OOM(exit 134),加大堆后 `RangeError: Maximum call stack size exceeded`(无诊断输出);**基线同样崩** → 属既有环境缺陷,与本改动无关 |
+| 替代类型核验 | 受限 tsconfig 只覆盖 6 个改动文件 | 基线 1103 错误 / 分支 1103 错误,改动文件内错误行号偏移、**无一条涉及新标识符**(`getMe`/`isAdmin`/`formatClickError`/`throwStreamHttpError` 等) → **无新增类型错误** |
+| 工作区 | `git status --porcelain` | ✅ 只剩 6 个目标文件(+ 本说明);临时验收页/临时路由已删除;无 `package-lock.json` 变动 |
+
+---
+
+## 五、已知取舍(均不影响功能)
+
+1. **`adminConfigured` 已赋值但暂无模板消费**:按改动清单预留(`false` 时可提示「管理员未配置」)。
+   若不需要,可删除该 ref 与 `getMe` 里的赋值,或补一句提示文案。
+2. **隐藏思考级别后,它左右两个 `.action-divider` 会相邻**(纯视觉问题):
+   改动清单明确要求「只包住思考级别块、保留分隔线」,故未顺带调整布局。
+3. **`getMe` 会随弹框打开被调用一次,且挂载时也会调用一次**:`a-modal` 有 `destroyOnClose`,
+   `visible=false` 时内容不渲染,因此不产生额外副作用;代价是比原实现多一个轻量 GET。
+4. **M1 的「计费归属」端到端核对本次未做**:需 TfAgents 上线可选令牌 + 可按用户查用量,
+   当前只验证到「请求头确实带了与登录态一致的令牌」。**建议上线后补一次端到端记账核对。**
+5. **`/api/me` 上线前,管理员也看不到管理入口**(404 → 保守降级)。
+   这意味着**基座与 TfAgents 的 B2-01 必须同批上线**,否则管理功能无人可用。
+
+---
+
+## 六、交付状态与上游同步
+
+| 项 | 值 |
+|---|---|
+| 本地分支 | `fix/base-agent-token` |
+| 我的 fork(`origin` = `lizuo/mky-vent-base`) | ✅ 已推送 `fix/base-agent-token` |
+| 主仓库(`upstream` = `hrx/mky-vent-base`) | ✅ 已交付同名分支(**人工审核合并,未动 master**) |
+| 上游基线同步 | ✅ 已 merge `upstream/master`(`b3ec299b..f25e5319`,2 提交,仅涉及 `home/configurable/*`、`gasPumpMonitor/*`,与本改动**零文件重叠**),落后 0 |
+| 部署 | ❌ 未部署(需另行下令) |
+
+merge 后已确认:本次 6 个文件与验收时**逐字节一致**(`git diff 29d9be9e HEAD -- <6 文件>` 为空),
+且 `git diff upstream/master..HEAD` 只含这 6 个文件 + 本说明 —— 第三节的验收结论对交付版本依然成立。
+
+### 复现 / 后续命令
+
+```bash
+cd /root/VentAnaly60/mky-vent-base
+
+# 拉取该分支查看
+git fetch upstream && git checkout -b review/base-agent-token upstream/fix/base-agent-token
+
+# 若后续还需更新(改了代码后)
+git push origin fix/base-agent-token && git deliver fix/base-agent-token
+```
+
+> **前置依赖**:TfAgents 侧的 `GET /api/me`(返回 `is_admin` / `admin_configured`)需与其**同批上线**,
+> 否则管理员也会失去管理入口(见「五、5」)。

+ 43 - 0
docs/ai-changes/README.md

@@ -0,0 +1,43 @@
+# AI 改动说明(ai-changes)
+
+本目录存放**由 AI 协助完成的改动**的说明文档,目的是让人工审核在 Gogs 的「改动对照」页面上
+就能直接看到「改了什么 / 为什么这么改 / 与原来的差别 / 怎么验收的 / 有什么已知取舍」,
+不必再去翻外部文档。
+
+## 为什么要放进分支里
+
+改动说明**必须随改动一起提交到分支**,这样它才会出现在改动对照(compare)页面里:
+
+```
+http://39.97.59.228:8013/hrx/<repo>/compare/master...<分支名>
+```
+
+放在仓库外面的说明文件(例如 `/root/TFAgents/docs/...`)不会出现在对照页,审核人看不到。
+
+## 命名约定
+
+```
+docs/ai-changes/<YYYYMMDD>-<简短主题>.md
+```
+
+- 日期用**改动/交付当天**的日期(本地时区 Asia/Shanghai),便于按文件名排序找到最新一批;
+- 主题用 ASCII 短横线连接的小写单词(例:`agent-token`、`sec-cleanup`、`perf-load-v5`);
+- 同一天同一分支多次追加说明时,直接**更新同一个文件**,不要新建(避免同一批改动出现多份说明)。
+
+## 每份说明应包含
+
+1. **头部**:对照网址、依据文档、分支、基线、日期、交付版本、状态(是否已验收/已交付/已部署);
+2. **提交清单**:本次分支上有哪些提交(功能提交 + merge 提交);
+3. **改动面**:涉及哪些文件、各改了什么;
+4. **逐项说明**:每项都写清「原来的行为 / 现在的行为 / 为什么这么改 / 与原来的差别」,
+   关键取舍(例如「令牌是可选语义,不能改成强制登录」)必须写明,便于人工判断有没有改错业务形态;
+5. **验收证据**:怎么验的、结果如何、失败项的实际现象;**验收中发现并修复的缺陷要单独标出**;
+6. **静态检查**:lint / 类型检查的命令与结果,以及**哪些是既有问题**(避免被误认为新引入);
+7. **已知取舍**:明确写清未做的部分与原因,不要让审核人以为漏了;
+8. **交付状态与上游同步**:fork / 主仓库 / 是否 merge 了最新 `upstream/master` / 是否部署。
+
+## 已有说明
+
+| 日期 | 文件 | 分支 | 主题 |
+|---|---|---|---|
+| 2026-09-15 | `20260915-mky-vent-base-agent-token.md` | `fix/base-agent-token` | M1 点选补令牌 / M2 结构化错误 / M3 管理入口按 `is_admin` 收敛 |