feat(gateway,config,web,docs): backthink 实时帧显示打扰原因 + 图片上限可配 + api-reference 端点表补齐
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 前端:`lib/wsParse.ts` 的 `parseBackthinkItem` 现在也读原始 `PushResult` 的 route/detail/bypassed(兼容
  camelCase),与历史 DTO 路径**同名同义**(空串 → undefined、未报告的 bool 不编成 false);
  Backthink 页 Notice 改为如实描述。证据=用后端两条路径的**真实序列化**喂同一个 PushResult 做逐字段比对
- 图片上限可配:`CHAT_IMAGE_MAX_BYTES`(默认 5242880 = 与 /upload 同口径;**未配置/0/负 一律用默认**,
  不存在「禁用上限」);删掉重复的字符常量,换算只留一份 `chatImageCharsFor`;补 3 条用例
  (默认值==upload 预算、配 1 MiB 真生效且只有合法那条驱动回合、非正值回落默认)
- `Docs/api-reference.md`:§1 端点表按 `routes.go` **补齐**(auth refresh/logout、upload、files、三支 audio、
  /admin/plugins/installed、/admin/llm/catalog、observability/logs、notifications、owner-profile、reminders),
  既有编号一律未动(保交叉引用);两张 `historyMessage` 字段表补 `personaId`(+ `clientMsgId`);
  修正 §8.1「主人档案保存仍 501」与实现不符(已真实实现)
- 复核:build/vet 绿 + gateway/config 包测试绿 + 前端 tsc 0 错 + vite build 成功
- 登记:§1 各表的编号跨表重复(不重编号以保交叉引用);`admin_plugins.go:217` 注释写错鉴权档(未改);
  §6.4 的 WS 事件帧表仍未补全
This commit is contained in:
AskaEth committed 2026-10-04 16:24:36 +08:00
1 parent 9b01efd796
commit c96a7db7f1
7 files changed
+195 -38

No files matched your search

+1 -1
View File
@@ -138,7 +138,7 @@ export function BackthinkModule(): React.ReactNode {
</div>
)}
<Notice>数据源:/ws/chat 下行 backthink / thinking 事件(实时,主驱动)+ 打开时一次拉取 GET /api/v1/admin/backthink 历史,两者按 id 去重、按时间倒序,新记录增量插入(不整页重渲)。「推送结果」的 route / detail / bypassed 由这次历史拉取带回(后端 PushResult 的如实投影);实时帧里同一信息裹在原始 PushResult 内、前端解析器尚未读取,因此本次会话新到的行可能暂时只显示「已推送/未推送」。链路导图取自后台思考会话(sess-backthink)的 graph_update 快照 —— 后端完整链路图始终发布,与管理端 Flow 页的「当前会话」图相互独立(裁决 16.4.2)。干预为后端 M10+ 提供,当前为只读展示。</Notice>
<Notice>数据源:/ws/chat 下行 backthink / thinking 事件(实时,主驱动)+ 打开时一次拉取 GET /api/v1/admin/backthink 历史,两者按 id 去重、按时间倒序,新记录增量插入(不整页重渲)。「推送结果」的 route / detail / bypassed 两条路径同名同义:历史拉取取 AdminBackthinkItem 的同名键,实时帧取后端原始 PushResult 的 Route / Detail / Bypassed(解析器已读,见 lib/wsParse.ts#parseBackthinkItem,兼容 camelCase 变体),因此本次会话新到的行同样显示推送路由与推送说明,不再只显示「已推送/未推送」。链路导图取自后台思考会话(sess-backthink)的 graph_update 快照 —— 后端完整链路图始终发布,与管理端 Flow 页的「当前会话」图相互独立(裁决 16.4.2)。干预为后端 M10+ 提供,当前为只读展示。</Notice>
</div>
);
}
+23 -3
View File
@@ -211,8 +211,13 @@ export function parseAdminChannelMessage(v: unknown): AdminChannelMessage | null
/** Decode a background-think `backthink` WS payload into a BackthinkItem. The bus
* publishes the raw `backthink.Record` (Go default field names ID/Trigger/At/
* Conclusion/ShouldPush/PushResult); the admin wire uses camelCase, so this reads
* both defensively and returns null when the id cannot be resolved. */
* Conclusion/ShouldPush/PushResult,后者内含 Pushed/Route/Bypassed/Detail);
* the admin wire uses camelCase, so this reads both defensively and returns
* null when the id cannot be resolved.
*
* route / detail / bypassed 与历史路径(`GET /admin/backthink` →
* AdminBackthinkItem)的同名字段**同义同源**(都来自后端已存的 PushResult),
* 因此实时行与刷新后的行渲染一致;线上没报告时保持缺席,绝不编值。 */
export function parseBackthinkItem(v: unknown): BackthinkItem | null {
const r = toRecord(v);
if (!r) return null;
@@ -230,5 +235,20 @@ export function parseBackthinkItem(v: unknown): BackthinkItem | null {
const pr = toRecord(r.PushResult) ?? {};
const shouldNotify = (r.ShouldPush ?? r.shouldNotify ?? r.should_push) === true;
const pushed = (pr.Pushed ?? pr.pushed ?? r.pushed ?? r.push_pushed) === true;
return { id: String(idRaw), trigger, conclusion, shouldNotify, pushed, at };
// 与历史路径同源字段(后端已存的 PushResult 的如实投影):Route/Detail 为空时
// 留 undefined(等同 AdminBackthinkItem 的 omitempty),Bypassed 只在线上给出
// 布尔时才带值,绝不把「没报告」改写成 false。
const route = firstPushText(pr.Route, pr.route, r.route, r.push_route);
const detail = firstPushText(pr.Detail, pr.detail, r.detail, r.push_detail);
const bypassedRaw = pr.Bypassed ?? pr.bypassed ?? r.bypassed ?? r.push_bypassed;
const bypassed = typeof bypassedRaw === "boolean" ? bypassedRaw : undefined;
return { id: String(idRaw), trigger, conclusion, shouldNotify, pushed, route, detail, bypassed, at };
}
/** 取 PushResult 文本字段的第一个非空值(先 Go 字段名,再 camelCase)。空串按
* 「没有值」处理,与历史 DTO 的 omitempty 一致 —— 实时行与历史行对「没有值」
* 的判定因此相同。 */
function firstPushText(...vals: unknown[]): string | undefined {
for (const v of vals) if (typeof v === "string" && v) return v;
return undefined;
}