20260915-mky-vent-base-agent-token.md 15 KB

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/masterf25e5319) 日期:2026-09-15 代码交付版本:0b3c1857;本说明随后追加(仓库 docs/ai-changes/),最终分支 tip 见提交清单 状态:真实浏览器验收通过(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 新增 MeInfogetMe()
.../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.tsssePost()

原来的行为

headers: { 'Content-Type': 'application/json' },   // 完全没有令牌

点选解读是基座里唯一不带令牌调 TfAgents 的路径。后果:TfAgents 无法识别是谁在用, 只能把消耗记到兜底账号 click-anonymous做不到「按登录人员记账」(决策 D5 / B1-20 的明确要求)。

现在的行为

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(渲染文案)

原来的行为

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 / spentTokensStreamError
  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 新增 MeInfogetMe()

    export const getMe = () => defHttp.get<MeInfo>(
     { url: Api.me },
     { isTransformResponse: false, errorMessageMode: 'none' }
    );
    
    • 不手写请求头:走 defHttp 时 axios 拦截器自动附 AuthorizationX-Access-Tokensrc/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.vueoptionItems 由普通数组改为 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() 放在 visiblewatch 里。但本组件存在「挂载即显示」的用法 (它在 layouts/default/index.vuehomeAI/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-modaldestroyOnClosevisible=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/masterb3ec299b..f25e5319,2 提交,仅涉及 home/configurable/*gasPumpMonitor/*,与本改动零文件重叠),落后 0
部署 ❌ 未部署(需另行下令)

merge 后已确认:本次 6 个文件与验收时逐字节一致git diff 29d9be9e HEAD -- <6 文件> 为空), 且 git diff upstream/master..HEAD 只含这 6 个文件 + 本说明 —— 第三节的验收结论对交付版本依然成立。

复现 / 后续命令

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」)。