2026-09-24-ZACP-聊天图片不显示-根因与修复.md

ZACP 聊天里的图片不显示(断裂图):根因与修复


0. 结论速览

图片不是"加载失败",而是压根没请求到图片:agent(grok)在回复里写的是 工作区相对路径 ![飞天 1](comfyuir任务/feitian_1_00001_.png),而 ZACP 的聊天渲染 没有把这类相对路径转换成文件直链——浏览器于是按同源相对 URL 去请求:

请求 http://127.0.0.1:8680/sessions/comfyuir%E4%BB%BB%E5%8A%A1/feitian_1_00001_.png
返回 200 text/html(SPA fallback = index.html,4360 字节)

浏览器拿到一坨 HTML 当图片解 → 经典断裂图。

ZACP 自己是有文件直链能力的(GET /api/v1/workspaces/:id/files/raw?path=…&token=…), 而且文件浏览器的图片预览就在用它——只是聊天渲染这条路径没接上。 本次补上这一环:把消息里 markdown 图片的工作区相对路径改写成直链。

说明:这不是上一次「unknown session id」修复引入的。官方 v0.9.0(同一 commit 9360fc6) 的前端源码里同样没有任何聊天图片路径改写逻辑,属于上游既有缺口。


1. 证据(修复前,真实 Chromium 实测)

$ node check-chat-images.cjs http://127.0.0.1:8680/sessions/3
{ "total": 20, "brokenCount": 9..., "items": [
   { "src": "comfyuir任务/dunhuang_apsara_v2_00001_.png", "naturalWidth": 0 },   ← 断裂
   { "src": "comfyuir任务/feitian_1_00001_.png",          "naturalWidth": 0 },
   ...

网络面板里对每一张图的请求都是:

请求 URL状态Content-Type
/sessions/comfyuir%E4%BB%BB%E5%8A%A1/feitian_1_00001_.png200text/html ← 关键

同时确认后端与文件都没问题(20 个路径逐个实测):

comfyuir任务/feitian_1_00001_.png   raw=200:image/png   onDisk:2223645
...(20/20 全部 200 image/png,文件都在 /home/zyw/Pictures/Wallpapers/comfyuir任务/)

2. 根因定位(源码)


3. 修复内容(前端,2 个文件 / +139 行)

3.1 新增 frontend/src/composables/useWorkspaceImageUrls.ts

3.2 改 frontend/src/components/chat/MessageItem.vue

副作用说明:图片元素插入的瞬间浏览器会先用旧相对地址发一次请求(必然是 HTML), 随后被改写为直链再发一次。多一次无用小请求,但换来"无需预处理 markdown"的简单实现。


4. 验证(修复后,真实 Chromium + Playwright)

$ node check-chat-images.cjs http://127.0.0.1:8680/sessions/3
{
 "total": 20,
 "rewritten": 20,          ← 20 张全部被改写为 /files/raw 直链
 "rewrittenLoaded": 20,    ← 20 张 naturalWidth > 0(真的解码成功)
 "rewrittenFailed": [],
 "stillRelative": [],      ← 没有漏网的相对路径
 "errors": []              ← 无 JS 报错
}

网络面板对应变成:

/api/v1/workspaces/1/files/raw?path=comfyuir%E4%BB%BB%E5%8A%A1%2Ffeitian_1_00001_.png&token=... → 200 image/png

测试注意:第一次测量只显示 11/20,是因为消息列表按需分页 + 图片 loading=lazy, 未被请求的图自然 naturalWidth=0,看起来像失败。验证脚本已改成 「上下滚动加载全部历史 + 强制 loading=eager + 等所有图 settle」再统计, 避免这种假阴性(脚本见附件)。


5. 维护:升级 ZACP 之后

现在一共两个补丁,都已放在本目录,重建脚本会按文件名顺序自动全部应用:

补丁修的问题
grok-unknown-session-fix.patch「离开较久再发消息」报 unknown session id(后端 Go,6 行)
chat-image-workspace-url.patch聊天里 markdown 图片不显示(前端 Vue,2 文件 / +139 行)
bash rebuild-zacp-grokfix.sh                  # 默认 v0.9.0
VERSION=0.9.1 bash rebuild-zacp-grokfix.sh    # 升级后改版本号重跑

脚本本次已完整验证:全新克隆 → 两个补丁依次应用 → 前端 bun + 后端 go 构建 → 回归测试 → 原子替换二进制 → pm2 重启 → 健康探测(冷启动约 8s,脚本最多等 40s)。

回滚:

ln -sfn $HOME/.zacp/bin/zacp-0.9.0 $HOME/.local/bin/zacp && pm2 restart zacp

6. 附件

文件说明
chat-image-workspace-url.patch前端补丁(新增 composable + MessageItem 接线)
check-chat-images.cjs真实浏览器验证脚本(Playwright,统计改写数/加载数/漏网相对路径)
rebuild-zacp-grokfix.sh一键重建(自动应用本目录所有 *.patch)
grok-unknown-session-fix.patch上一次的会话失效补丁

验证脚本依赖本机已缓存的 Playwright(~/Downloads/.tools/node_modules/playwright-core

  • ~/.cache/ms-playwright),无需联网安装。

7. 追加(2026-09-24 23:2x):新生成的图又断了——这次是 grok 引用了它自己的私有目录

7.1 现象与判定

用户反馈「grok 生成的图片再次无法显示」。实际只有 1 张(会话 #3 的消息 #45), 且不是渲染层的问题:

检查结果
markdown 引用![Grok 飞天](images/1.jpg)
images/1.jpg 在哪~/.grok/sessions/%2F…Wallpapers/01a0d2df-…/images/1.jpg ← grok 的会话私有目录,webui 访问不到
工作区里的真实文件comfyuir任务/feitian_grok_00001.jpg(447848 B,与私有目录里那份字节数相同,是 cp 出来的副本)
raw 端点images/1.jpg → 404;comfyuir任务/feitian_grok_00001.jpg → 200 image/jpeg

根因:grok 的图片工具把成品写到自己的会话目录 $GROK_HOME/sessions/<cwd>/<session-id>/images/1.jpg (同名文件、每张覆盖上一张),然后 grok 在 markdown 里直接引用了这个内部路径。 它虽然已经 cp 了一份到工作区,但引用的是内部路径。grok 自己的思考里写得很直白:

{"type":"agent_thought","text":"Present the image using images/1.jpg as the markdown path.
 Also mention the copy in comfyuir任务. Don't describe appearance."}

渲染层按「工作区根」解析 images/1.jpg → 文件不存在 → 404 → 断裂图。 这一层无法在渲染端补救:私有目录里的文件同名覆盖,把它映射出来只会显示"最新那张", 与消息当时的图对不上。

7.2 处置

  1. 持久化规则(治本):新增 ~/.grok/rules/image-paths.md(grok 的 global 规则目录, 不依赖目录信任),要求内嵌图片必须引用工作目录下的真实路径,禁止引用 $GROK_HOME/sessions/.../images/。 验证:cd /home/zyw/Pictures/Wallpapers && grok inspect → Project Instructions (1) └ /home/zyw/.grok/rules/image-paths.md (global, ~209 tokens)。
  2. 修复已存在的这条消息(治标):
    • 关键点:历史消息的正文渲染走的是 events 里的 agent_message 文本(见 composables/useMessageBlocks.ts 的 deriveBlocks),不是 messages.content 列 —— 所以只改 content 页面依旧断图,必须同时改 events。
    • 改动:两处 ![Grok 飞天](images/1.jpg) → ![Grok 飞天](comfyuir任务/feitian_grok_00001.jpg); 只替换 agent_message 文本,cp "…/images/1.jpg" … 的命令原文保持不动。 出现次数校验为 1 才执行(防误改);原文备份 message45-backup.json。
  3. 让运行中的 agent 生效:pm2 restart zacp(规则在 agent 启动时加载;会话经 session/load 自动恢复,用户无感)。

7.3 验证

真实 Chromium 复查会话 #3:21/21 图片改写且加载成功,0 断裂,0 JS 报错。

7.4 经验

下载此文件