恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

AionUi ACP 图片输出展示机制全解析:从 base64 清洗到本地文件渲染的完整链路

  • 首页
  • 资讯中心
  • /
  • AionUi ACP 图片输出展示机制全解析:从 base64 清洗到本地文件渲染的完整链路

相关资讯

ruflo 安全策略全解读:从漏洞报告流程到 PathValidator / SafeExecutor 的系统边界纵深防御 2026/9/10 11:10:44
Backstage Well-known Skills:通过 `.well-known` 端点向 AI 编程助手分发官方工程技能 2026/9/10 11:10:44
FlatBuffers 贡献指南:从 CLA 签署到 flatc 构建、goldens 再生成与多语言测试的完整开发工作流 2026/9/10 11:10:44

最新资讯

学生成绩预测机器学习系统:从数据清洗到Flask部署全流程
libcurl 详解 CURLINFO_FILETIME_T:安全获取远端资源的修改时间(64 位时间戳)
Buck电路双闭环控制模型仿真研究(Simulink仿真实现)
Composio 集成 Shopify 实战:自定义 OAuth 凭据、App not found 与 read_all_orders 权限排查指南
uipro uninstall 提示 “No installed AI skill directories detected“ 怎么排查?
Matlab六自由度机械臂正逆运动学仿真与轨迹规划实践

今日推荐

AI搜索重构内容生态:企业从“流量争夺”转向“答案共建”
AI搜索的信任缺口:企业内容如何在答案时代自证可信
Spring Boot+Vue+Node.js售后服务系统开发实战

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

AionUi ACP 图片输出展示机制全解析:从 base64 清洗到本地文件渲染的完整链路

发布时间:2026/9/10 11:10:44
AionUi ACP 图片输出展示机制全解析:从 base64 清洗到本地文件渲染的完整链路 AionUi ACP 图片输出展示机制全解析从 base64 清洗到本地文件渲染的完整链路【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi导读本文围绕 AionUi 对 ACPAgent Client Protocol工具调用中图片结果的展示方案展开核心结论是AionUi 采用文件路径而非 inline base64 来承载和渲染 ACP 图片输出。后端在数据入口处清洗掉 Codex 等代理图片生成工具产生的大型 base64前端按需通过本地文件接口读取图片并交给LocalImageView渲染。读完本文你将掌握 ACP 图片输出相关的数据约定、清洗边界、渲染流程、聚合视图View Steps中的图片展示以及前端兜底清洗与测试覆盖能够直接基于仓库源码理解并扩展这套机制。为什么 ACP 图片输出需要文件路径而非 base64在 ACP 会话中工具调用tool call的raw_output.result常常携带代理生成图片的完整 base64 数据。以 Codex 的图片生成为例一张普通生成图的 base64 轻松达到MB 级。如果让这类数据原样进入聊天消息列表、WebSocket 事件流和 SQLite 消息记录会带来三个直接问题消息列表内存压力每条消息携带 MB 级字符串渲染与滚动性能明显劣化WebSocket 事件膨胀实时增量推送的tool_call_update事件体积被放大网络与序列化开销上升SQLite 记录膨胀历史消息持久化占用空间成倍增长且频繁读写大字段拖慢数据库操作。AionUi 的解决方案是把图片内容与图片引用解耦后端是主要清洗边界将大型 base64 从result字段中剥除保留小型本地文件路径字段前端收到这类消息后仅在需要展示时才通过本地文件接口按需读取图片二进制再渲染到界面上。这样聊天数据链路中流动的始终是几十字节的路径字符串而不是几 MB 的 base64。数据约定清洗后保留的字段根据 docs/guides/acp-image-output.zh-CN.md后端会把 Codex 图片生成工具的大型raw_output.resultbase64 清洗掉并保留以下小型字段字段含义rawOutput.image.path或raw_output.image.path生成图片的本地路径首选字段rawOutput.saved_path或raw_output.saved_path兼容 Codex 当前返回结构的本地路径回退字段rawOutput.result_omitted布尔值表示原始图片 base64 已被省略rawOutput.result_bytes被省略内容的原始字节长度这些字段的类型定义可以在 acpTypes.ts 中找到对应源码export interface AcpImageOutput { path: string; mime_type?: string; source?: string; } export interface AcpRawOutput { saved_path?: string; image?: AcpImageOutput; result_omitted?: boolean; result_omitted_reason?: string; result_bytes?: number; status?: string; [key: string]: unknown; }注意类型定义中同时保留了result_omitted_reason省略原因如image_base64和saved_path这为前端判断某个路径是否真的是被清洗的图片提供了依据。后端清洗边界sanitize 的实现细节清洗逻辑的核心实现在 acpToolCallOutput.ts几个关键点如下。1. 判定阈值与图片特征前缀const INLINE_IMAGE_RESULT_LIMIT 64 * 1024; const IMAGE_PATH_EXTENSION_RE /\.(?:png|jpe?g|webp|gif)$/i; const isProbablyInlineImageResult (value: string): boolean value.length INLINE_IMAGE_RESULT_LIMIT (value.startsWith(iVBORw0KGgo) || // PNG value.startsWith(/9j/) || // JPEG value.startsWith(UklGR) || // WebP value.startsWith(data:image/));阈值INLINE_IMAGE_RESULT_LIMIT为64 KB超过该长度才可能被判定为大型内联图片结果通过 base64 文件头特征识别 PNG / JPEG / WebP以及data:image/前缀的 Data URL避免误伤超长的普通文本输出。2. 清洗与重建 image 引用const sanitizeAcpRawOutput (rawOutput?: AcpRawOutput): AcpRawOutput | undefined { if (!rawOutput) return rawOutput; const result rawOutput.result; const savedPath rawOutput.saved_path; if (typeof result ! string || !isProbablyInlineImageResult(result)) { return rawOutput; } const { result: _result, ...rest } rawOutput; const sanitized: AcpRawOutput { ...rest, result_omitted: true, result_omitted_reason: rawOutput.result_omitted_reason || image_base64, result_bytes: rawOutput.result_bytes || result.length, }; if (rawOutput.image || (typeof savedPath string savedPath)) { const path rawOutput.image?.path || savedPath; sanitized.image rawOutput.image || { path, mime_type: mimeTypeFromImagePath(path), source: codex_image_generation, }; } return sanitized; };清洗逻辑要点只有result是字符串且命中疑似内联图片特征时才触发清洗非字符串 result如对象原样保留清洗时删除result字段写入result_omitted: true、result_omitted_reason: image_base64、result_bytes记录原始长度若存在image.path或非空saved_path则补全image引用对象image已存在时例如 Codex 本身返回了image字段则保留原有元数据不被覆盖mimeTypeFromImagePath根据扩展名推导 MIME.jpg/.jpeg→image/jpeg、.webp→image/webp、.gif→image/gif其余默认image/png。3. 对外暴露的清洗入口export const sanitizeAcpToolUpdate (update: ToolCallUpdate[update]): ToolCallUpdate[update] ({ ...update, rawOutput: sanitizeAcpRawOutput(update.rawOutput), raw_output: sanitizeAcpRawOutput(update.raw_output), }); export const sanitizeAcpToolCallContent (content: ToolCallUpdate): ToolCallUpdate ({ ...content, update: sanitizeAcpToolUpdate(content.update), });sanitizeAcpToolUpdate同时处理rawOutputcamelCase与raw_outputsnake_case两种线上结构sanitizeAcpToolCallContent则面向整条工具调用消息内容。图片路径提取image.path 优先saved_path 回退前端渲染前统一通过getAcpImagePath解析展示路径export const getAcpImagePath (update: ToolCallUpdate[update]): string | undefined { const rawOutput update.rawOutput || update.raw_output; const imagePath rawOutput?.image?.path; if (typeof imagePath string imagePath) return imagePath; const savedPath rawOutput?.saved_path; if ( typeof savedPath string savedPath (rawOutput?.result_omitted_reason image_base64 || isImagePath(savedPath)) ) { return savedPath; } return undefined; };两条规则值得注意优先读image.path取不到再回退到saved_pathsaved_path不是无脑采用只有当result_omitted_reason image_base64或路径本身命中图片扩展名png/jpg/jpeg/webp/gif时才视为图片路径。这样/tmp/result.txt这类非图片保存路径不会被误当作预览图——对应测试见 acpToolCallOutput.test.ts。文件名生成由getAcpImageFileName负责取路径最后一段作为文件名空路径时回退为generated-image.png见 acpToolCallOutput.test.ts。渲染流程MessageAcpToolCall → LocalImageView → /api/fs/image-base64消息卡片内的图片渲染ACP 工具调用消息由 MessageAcpToolCall.tsx 渲染其关键逻辑为const imagePath getAcpImagePath(update); const imageAlt imagePath?.split(/[/\\]/).pop() || t(acp.image.generated_alt); ... {imagePath ( div classNamegroup relative mt-3 overflow-hidden rounded border bg-1 p-2 LocalImageView src{imagePath} alt{imageAlt} classNamemax-w-full max-h-[520px] object-contain rounded / Tooltip content{t(acp.image.download)} Button ... onClick{() void handleDownloadImage(imagePath)} / /Tooltip /div )}流程为先用getAcpImagePath(update)解析出本地路径 → 交给LocalImageView按需加载 → 渲染为最多 520px 高的预览图并在右上角悬浮一个下载按钮点击后调用downloadFileFromPath(path, getAcpImageFileName(path))下载成功/失败分别通过acp.image.download_success/acp.image.download_error提示见 MessageAcpToolCall.tsx。LocalImageView本地文件如何变成可渲染图片LocalImageView.tsx 是本地图片按需读取的核心组件const root useConversationContextSafe()?.workspace ?? ; const absolutePath useMemo(() { if (!root) return src; if ( src.startsWith(http) || src.startsWith(data:) || src.startsWith(/) || src.startsWith(file:) || src.startsWith(\\) || /^[A-Za-z]:/.test(src) ) { return src; } return joinPath(root, src); }, [src, root]); useEffect(() { setLoading(true); ipcBridge.fs.getImageBase64 .invoke({ path: absolutePath, workspace: root || undefined }) .then((base64) { if (base64) setUrl(base64); setLoading(false); }) .catch((error) { /* ... */ }); }, [absolutePath]);要点相对路径解析如果src是相对路径例如./chart.png会以当前会话工作区代理 cwd为根解析成绝对路径绝对路径、http(s)、data:、file:、Windows 盘符路径则原样透传按需读取通过 IPC 桥调用/api/fs/image-base64接口定义见 ipcBridge.tsgetImageBase64: httpPoststring | null, { path: string; workspace?: string }(/api/fs/image-base64)把本地图片读成 base64 后再交给img渲染加载状态读取期间展示 loading 图标与 alt 文本读取失败则静默降级保留 alt不会让整条消息崩溃。由于图片只在消息可见、需要渲染时才被读取聊天列表、WebSocket 事件和 SQLite 记录中都不再承载 MB 级 base64这正是按需读取的价值所在。聚合视图View Steps中的图片展示对话列表会把同一轮的工具调用聚合到View Steps摘要中对应 MessageToolGroupSummary.tsx。ACP 图片工具调用在进入聚合视图前会先经过标准化层保留image.path。标准化层是normalizeAcpToolCall见 normalizeToolCall.ts其中关键一行return { key: update.tool_call_id, name: update.title, status: normalizeAcpStatus(update.status), description: keyParam || (rawInput?.command as string) || update.kind, input, output, truncated: content?._compact?.truncated true, messageId: message.id, conversationId: message.conversation_id, imagePath: getAcpImagePath(update), // ← 保留图片路径 };imagePath被写入标准化的NormalizedToolCall随后在ToolItemDetail中渲染{item.imagePath ( div classNamegroup relative m-l-20px m-t-8px overflow-hidden rounded border bg-1 p-2 max-w-280px LocalImageView src{item.imagePath} alt{getAcpImageFileName(item.imagePath)} classNamemax-w-full max-h-320px object-contain rounded / Tooltip content{t(acp.image.download)} Button ... onClick{() void handleDownloadImage(item.imagePath)} / /Tooltip /div )}因此聚合视图展开后同样能展示同一张本地图片并带有下载按钮把图片保存到本机。此外如果条目被截断truncated展开时还会通过ipcBridge.database.getConversationMessage拉取完整消息重新标准化以便拿到完整图片路径。前端兜底清洗mergeAcpToolCallContent 与快速合并路径后端是主要清洗边界但前端也保留了两处兜底清洗防止极端情况下大 base64 仍然进入渲染链路。1. 合并路径 mergeAcpToolCallContent在消息列表快速合并路径中mergeAcpToolCallContent位于 chatLib.ts把增量更新的工具调用合并进既有消息合并前同样经过sanitizeAcpToolCallContent清洗测试证据见 acpToolCallOutput.test.tsit(sanitizes incoming updates when merging an existing ACP tool call, () { const existing createAcpToolCall(undefined).content; const incoming createAcpToolCall({ saved_path: /Users/test/.codex/generated_images/session/ig_test_image.webp, result: UklGR${A.repeat(128 * 1024)}, }).content; const merged mergeAcpToolCallContent(existing, incoming); expect(merged.update.rawOutput?.result).toBeUndefined(); expect(merged.update.rawOutput?.image?.mime_type).toBe(image/webp); });2. 消息插入路径 composeMessage新收到的工具调用消息在插入消息列表composeMessage时同样会被清洗包括空列表插入、追加到非空列表等场景见 acpToolCallOutput.test.ts。兜底清洗逻辑与后端一致收到包含saved_path且超出阈值64 KB的result字符串时移除result并补齐image.path、result_omitted和result_bytes。前端兜底与后端清洗使用同一套sanitizeAcpRawOutput实现见 acpToolCallOutput.ts保证前后端清洗语义完全一致。测试覆盖相关单测位于tests/unit/chat/覆盖维度如下测试文件覆盖内容acpToolCallOutput.test.ts图片路径提取image.path优先于saved_path、文件名生成、base64 清洗PNG/JPEG/WebP/GIF、短结果保留、非图片长文本保留、非字符串 result 保留、snake_case 兼容、既有 image 元数据保留、合并与插入路径清洗messageHooks.dom.test.tsx空列表、非空列表和addtrue路径下的 ACP 图片消息清洗MessageAcpToolCall.dom.test.tsx 与 MessageToolGroupSummary.dom.test.tsx图片预览渲染、下载成功调用和下载失败提示以清洗判定的边界为例acpToolCallOutput.test.ts明确了三类场景的预期行为超长图片 base64result被移除、result_omitted true、result_omitted_reason image_base64、image引用被补齐含正确 MIME短文本输出即使saved_path存在也原样保留result不触发清洗超长非图片文本即使有saved_path也不清洗result_omitted保持 undefined非字符串 result对象原样保留绝不误伤。总结整条链路回顾从一张 Codex 生成的图片到屏幕上的预览AionUi 的完整链路是后端入口sanitizeAcpToolCallContent清洗大型内联 base64保留image.path/saved_path/result_omitted/result_bytes等小型字段数据链路清洗后的消息经 WebSocket 推送、SQLite 持久化全程只携带路径引用前端兜底mergeAcpToolCallContent与消息列表快速合并路径再次清洗确保异常数据不进入渲染消息卡片MessageAcpToolCall通过getAcpImagePath解析路径LocalImageView经/api/fs/image-base64按需读取本地图片并渲染附下载按钮聚合视图normalizeAcpToolCall标准化时保留imagePathMessageToolGroupSummary的 View Steps 展开后复用同一套图片展示与下载逻辑。这套路径引用 按需读取 双层清洗的方案让 ACP 图片输出在聊天列表、WebSocket 事件与 SQLite 记录三层数据载体中始终保持轻量是 AionUi 处理大型工具输出的代表性设计也适用于其他类型的超大工具输出如长日志、大 diff做类似改造。【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号