feat(gateway,config,web,docs): backthink 实时帧显示打扰原因 + 图片上限可配 + api-reference 端点表补齐
- 前端:`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:
1 parent
9b01efd796
commit
c96a7db7f1
7 files changed
+195
-38
No files matched your search
@@ -103,7 +103,8 @@ type loginResponse struct {
|
||||
// 经 WebSocket 通道触发流式并立即返回。Image 是
|
||||
// 为视觉路径附带的可选 base64 data: URI 或 http(s) URL;
|
||||
// loop 把它经视觉/OCR 工具或主模型路由(design/06 §0)。
|
||||
// Image 受 maxChatImageChars 限制(与 POST /api/v1/upload 的 5 MiB 同口径),
|
||||
// Image 受配置上限(CHAT_IMAGE_MAX_BYTES,默认与
|
||||
// POST /api/v1/upload 的 5 MiB 同口径)换算出的字符上限限制,
|
||||
// 超限一律 400 image_too_large,不被静默吃下。
|
||||
// DeviceID 是客户端产出的发送浏览器/设备标识
|
||||
// (design/16 §4.5),例如 "web:chrome:a1b2c3";它可选且按原样持久化。
|
||||
@@ -131,29 +132,45 @@ type chatResponse struct {
|
||||
StopReason string `json:"stopReason"`
|
||||
}
|
||||
|
||||
// maxChatImageBytes 是 chat.image 允许的**解码后图像字节**上限,与
|
||||
// maxChatImageBytes 是 chat.image 允许的**解码后图像字节**上限的**默认值**,与
|
||||
// POST /api/v1/upload 的 maxUploadBytes(5 MiB,见 upload.go)同口径:
|
||||
// 图片有上传与内联两条入口,两边必须给出同一个「一张图能有多大」的答案,
|
||||
// 否则上传的限制可被 base64 内联绕开 —— 客户端发来多大就落库多大,
|
||||
// 每页历史(GET /sessions/{id}/messages)与每次 WS history 回放都要
|
||||
// 把整段 base64 再传一遍(协同文档 G3)。
|
||||
//
|
||||
// 它只是默认值:生效上限由配置 CHAT_IMAGE_MAX_BYTES 经 chatImageLimitBytes 解析。
|
||||
const maxChatImageBytes = 5 << 20
|
||||
|
||||
// chatImagePrefixAllowance 是 data: URI 前缀(如 "data:image/png;base64,")
|
||||
// 所占的字符余量。按常见图片 MIME 的最长前缀取整,不含 base64 载荷本身。
|
||||
const chatImagePrefixAllowance = 64
|
||||
|
||||
// maxChatImageChars 是同一口径换算到 base64 字符串长度的上限。base64 每
|
||||
// 3 字节编码为 4 字符(膨胀 4/3),因此正好 5 MiB 的整幅图编码后是
|
||||
// ceil(maxChatImageBytes/3)*4 = 6990508 字符(这就是 (n+2)/3*4 的由来:
|
||||
// 余 1、2 字节时末组仍需补满 4 字符),再加 data: 前缀余量 —— 恰好 5 MiB
|
||||
// 的合法图片绝不会被误拒。
|
||||
// chatImageCharsFor 把「解码后图像字节上限」换算为 image 字符串的字符上限:
|
||||
// base64 每 3 字节编码为 4 字符(膨胀 4/3),因此恰好 n 字节的整幅图编码后是
|
||||
// ceil(n/3)*4 = (n+2)/3*4 字符(这就是 (n+2)/3*4 的由来:余 1、2 字节时末组仍需
|
||||
// 补满 4 字符),再加 data: 前缀余量 —— 恰好 n 字节的合法图片绝不会被误拒。
|
||||
// 它是**唯一**的换算处:默认上限与配置上限都经它落地,不存在第二份膨胀系数。
|
||||
//
|
||||
// 校验按**字符串长度**做,是因为入口层只有这一步是零分配的:vision 层要先
|
||||
// base64 解码整个字符串才拿到真实字节数(vision.decodeBase64 →
|
||||
// base64.StdEncoding.DecodeString),也就是先分配再报错。在入口层按长度拦下,
|
||||
// 超大字符串根本走不到解码那一步。
|
||||
const maxChatImageChars = ((maxChatImageBytes+2)/3)*4 + chatImagePrefixAllowance
|
||||
func chatImageCharsFor(bytesLimit int) int {
|
||||
return ((bytesLimit+2)/3)*4 + chatImagePrefixAllowance
|
||||
}
|
||||
|
||||
// chatImageLimitBytes 解析本次生效的**解码后字节**上限:配置给了正数就用它,
|
||||
// 否则(未配置的零值、0、负数)回落到默认 maxChatImageBytes。语义被钉成「≤0 =
|
||||
// 用默认」而**不是**「禁用上限」——一张图能有多大只能有一个答案,配置不该把它
|
||||
// 变成无限;这同时保证「没配 CHAT_IMAGE_MAX_BYTES」的默认行为与引入该配置之前
|
||||
// 逐字节一致(同一张图此前能过、现在也能过,反之亦然)。
|
||||
func chatImageLimitBytes(configured int) int {
|
||||
if configured > 0 {
|
||||
return configured
|
||||
}
|
||||
return maxChatImageBytes
|
||||
}
|
||||
|
||||
// chatImageTooLargeCode 是 image 超限时返回给客户端的机器可读错误码,
|
||||
// 与其它 400 文案区分开:客户端只看 code 就能把「图太大,请改走上传」
|
||||
@@ -164,7 +181,7 @@ const chatImageTooLargeCode = "image_too_large"
|
||||
// 400 + image_too_large 的显式错误(带上限与本次长度,并指明改用
|
||||
// POST /api/v1/upload 取 URL 再发)。limit 作为参数传入,使该判定能被
|
||||
// 用小上限直接单测(边界与比较运算符因此可被钉住),REST 入口传的是
|
||||
// maxChatImageChars。
|
||||
// chatImageCharsFor(生效的字节上限)。
|
||||
//
|
||||
// 文案里的体积由 limit 反推(base64 膨胀 4/3 → 解码后约 limit*3/4 字节),
|
||||
// 因此换上限时文案自动跟随,不会出现「写着 5 MiB、实际是别的数」。
|
||||
@@ -230,9 +247,12 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
|
||||
// image 上限在**任何**解码/落库之前检查,且对同步与 stream 两条分支
|
||||
// 都生效(stream 只是把该轮转发给 ws.chat,转发前必须先拒),
|
||||
// 因此超限的图片既不进模型、也不落库、也不会被历史回放重传。
|
||||
// 上限来自配置对默认值的解析(CHAT_IMAGE_MAX_BYTES,≤0 = 用默认 5 MiB),
|
||||
// 换算成字符上限只有 chatImageCharsFor 一处。
|
||||
// 日志只带长度与上限,绝不带图片内容。
|
||||
if err := checkChatImageSize(req.Image, maxChatImageChars); err != nil {
|
||||
s.log.Warn("chat image rejected", "session", req.SessionID, "chars", len(req.Image), "limit", maxChatImageChars)
|
||||
limit := chatImageCharsFor(chatImageLimitBytes(s.cfg.Server.ChatImageMaxBytes))
|
||||
if err := checkChatImageSize(req.Image, limit); err != nil {
|
||||
s.log.Warn("chat image rejected", "session", req.SessionID, "chars", len(req.Image), "limit", limit)
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user