Appearance
功能规格说明:图片粘贴
创建日期:2026-03-24
用户场景与测试 (必填)
用户故事:从剪贴板粘贴图片(优先级:P1)
作为用户,我希望能够使用 Ctrl+V 将剪贴板中的图片粘贴到聊天输入框中,以便轻松与 AI 分享截图或图片。
为什么是这个优先级:这是功能的核心。它允许用户向 AI 提供视觉上下文。
独立测试:可以通过复制图片到剪贴板,在聊天输入框中按 Ctrl+V,并验证输入文本中出现类似 [Image #1] 的占位符来测试。
验收场景:
- 假设 剪贴板中有图片,当 用户在聊天输入框中按
Ctrl+V(Windows 终端下为Alt+V,见「跨平台支持」),则 创建临时图片文件,并在光标位置插入占位符[Image #1]。 - 假设 粘贴了多张图片,当 用户多次按
Ctrl+V(Windows 下为Alt+V),则 每张图片获得唯一 ID 和占位符(例如[Image #1]、[Image #2])。
用户故事:发送附带图片的消息(优先级:P1)
作为用户,我希望按 Enter 时粘贴的图片随消息一起发送,以便 AI 能够看到并处理它们。
为什么是这个优先级:确保视觉上下文实际传递给 AI。
独立测试:可以通过粘贴图片、输入消息、按 Enter,并验证发送给 AI 的消息包含图片数据来测试。
验收场景:
- 假设 消息中有图片占位符
[Image #1],当 用户提交消息,则 图片文件路径和 MIME 类型包含在发送给 AI 服务的消息载荷中。 - 假设 消息中有多个图片占位符,当 用户提交消息,则 所有引用的图片都包含在消息载荷中。
用户故事:已发送图片的标签点开就能看(优先级:P1)
作为用户,我希望消息列表里已发送的图片标签点开就能看到这张图,不管这个会话跑在本机还是 SSH 远端主机上。
为什么是这个优先级:粘贴图在发送那一刻就被落成会话所在那台机器上的临时文件;这一版起,消息块里同时保留内联字节(内存与转录都留,对齐 Claude Code)与那个路径(给模型的 [Image source: <path>] 元数据用)。于是标签点开有两条路:块里有字节就地弹层,块里只剩路径(本版之前的会话,或下发远控页时剥过字节)才交宿主去读。判据是「字节在不在这一份消息里」;「宿主能不能打开本地文件」只在没有字节时兜底——此前把后者当成唯一判据,导致 SSH 会话里拿本机路径去加载远端图片(2026-10-30 用户报的裂图)与三端预览面各不相同。
独立测试:在 SSH 远端会话里粘贴一张图并发送,点开消息里的图片标签,验证就地弹层显示且全程没有向宿主发打开请求;用一份只有路径的老消息点开,验证桌面端走右侧文件面板、插件端走各自的图片预览;远控 Web 页上点开同一类消息,验证标签仍在原位、顺序不变但不可点。
验收场景:
- 假设 消息块里带内联字节,当 用户点开标签,则 就地弹层预览——桌面端、VS Code、JetBrains 表现一致,不请求宿主、不在 IDE 里新开编辑器标签。
- 假设 消息块里只有路径,当 用户点开标签,则 交给宿主打开:桌面端在右侧文件面板里内联显示(宿主按该分屏所在主机去读),插件端用它自己的图片预览命令打开(VS Code 打开内置图片预览,JetBrains 用内置图片编辑器——随 IDE 捆绑的 Images 插件认领 png/jpg/gif 等格式;只有内置编辑器认领不了该文件时才交给系统默认看图程序);渲染层不得把该路径直接塞进
<img src>自行加载。 - 假设 会话跑在 SSH 远端主机上,当 用户点开本版之后发送的图片,则 照场景 1 就地弹层(字节就在消息里,与主机无关);只剩路径的老消息按场景 2 由宿主经该主机读回,文件已不在时按文件面板既有的「路径已不在」呈现。
- 假设 宿主是远控 Web 页(下发的消息里图片字节已被剥离,见
remote/remote-web.md场景 21),当 用户看到带图片的消息,则 标签照旧显示、顺序与正文里的[imageN]一一对应,但不渲染成可点入口(与remote/remote-web.md场景 20 的路径同一条判据:不留「看着能点、点了没反应」)。
用户故事:草稿里的图片(优先级:P1,对齐 claude.ai / Claude Code 桌面端)
作为用户,我希望已经粘进输入框、还没发送的图片在我切换视图或页面重载后仍然在,以便我不必重新截图粘贴。
为什么是这个优先级:粘贴的图片目前只活在输入框的那几个标签节点上。设置页、插件市场、创作集、会话状态看板都会整页顶替对话区,把输入框连同标签一起卸载;回来时只能靠会话侧保存的草稿字符串重建输入框,而图片数据不在那份字符串里,用户看到的就只剩一段占位文本。桌面端的做法是把未发送的图片按会话存在本地存储里,草稿文本本身仍由会话侧保存——文本与图片分开存,重挂载时再拼回去。
独立测试:粘贴一张图、打几个字,打开设置页再返回,验证图片标签仍在原位且能点开预览;切到别的会话再切回,验证图片跟着会话走;发送后验证模型侧收到的是图片,而不是一个文件路径。
验收场景:
- 假设 输入框里有一张未发送的粘贴图片,当 用户打开设置页或会话状态看板(都整页顶替对话区、导致输入框卸载)再返回对话窗口,则 图片标签仍在原来的位置、仍可点开预览,与文字的先后顺序不变。
- 假设 某个会话的输入框里有一张未发送的粘贴图片,当 用户切到另一个会话,则 新会话的输入框不显示这张图;当 用户切回原会话,则 图片恢复——草稿图片按会话隔离,新对话按 pane 隔离。
- 假设 webview 重载(窗口重开、面板重建)后宿主回放同一份草稿,当 输入框挂载,则 图片从本地存储回填;本地存储里已找不到这份图片数据时,只保留占位文本,不报错、不阻塞发送。
- 假设 草稿里有图片,当 用户发送消息,则 图片仍作为图片块随用户消息发出、模型直接可见,不得替换成「文件引用 + 让模型读文件」。
- 假设 本地存储不可用(隐私模式、配额已满),当 用户粘贴图片,则 图片照常进入输入框并能正常发送,只是这次不进入持久化草稿——静默降级,不弹错、不拦输入。
用户故事:跨平台支持(优先级:P2)
作为用户,我希望图片粘贴在 macOS、Windows 和 Linux 上都能工作,以便无论操作系统如何都有一致的体验。
为什么是这个优先级:确保 CLI 工具的所有用户都能使用此功能。
验收场景:
- 假设 用户在 macOS 上,当 他们粘贴图片,则 使用
osascript读取剪贴板。 - 假设 用户在 Windows 上,当 他们按
Alt+V粘贴图片,则 使用 PowerShell 读取剪贴板。Windows 终端将Ctrl+V保留为系统文本粘贴,按键不会到达 CLI,因此 CLI 侧使用Alt+V(与 Claude Code 一致);终端若透传Ctrl+V亦可用。 - 假设 用户在 Linux 上,当 他们粘贴图片,则 使用
xclip读取剪贴板。
用户故事:图片源路径元数据(优先级:P1,对齐 Claude Code)
作为用户,我希望代理在收到图片时能看到图片的本地文件路径(包括从 webview 粘贴的图片),这样我可以写一个指定视觉模型的 subagent 来读取并描述图片内容。
为什么是这个优先级:Claude Code 在图片后附加 [Image source: <path>] 元数据文本,使模型能引用本地文件进一步处理。当前 Wave 仅发送 base64,模型不知道图片来自哪个文件,无法用工具(如 Read/Bash)操作图片文件。webview 粘贴的图片以 dataURL 形式到达 CLI,同样没有真实文件路径,需要 CLI 先将其落盘为临时文件。落盘只补路径、不替换字节:消息块里同时留内联字节(内存与转录都留,见「已发送图片的标签点开就能看」)和这个路径,路径只用于给模型的元数据与工具引用。
独立测试:可以构造带本地文件路径的图片消息,验证转换后的 API 消息在图片块后包含 [Image source: <path>] 文本;构造 dataURL 图片消息,验证落盘后消息块里仍带内联字节、且在图片块后附加 [Image source: <落盘路径>] 文本,落盘文件可读;无法落盘时不附加该文本、字节照发。
验收场景:
- 假设 图片有对应的本地文件路径(如 CLI 粘贴创建的临时文件),当 消息转换为 API 格式时,则 在图片块后附加文本
[Image source: <路径>] - 假设 图片以 dataURL 到达(如 webview 通过 stdio 传入),当 CLI 收到图片,则 先落盘为临时文件、把路径记在消息块上供上述元数据使用,同时保留块里的内联字节
- 假设 图片既没有本地文件路径也无法落盘,当 消息转换为 API 格式时,则 不附加元数据文本,仅发送图片
- 假设 会话从转录恢复,当 用户点开这些图片,则 字节从转录里的消息块读回即可就地预览,
[Image source: <路径>]仍用块里保存的路径,不重新落盘
用户故事:发送前校验图片有效性(优先级:P1)
作为用户,我希望粘贴一张无效图片时能立刻在本地被拦下并告诉我原因,而不是等消息发出去后收到一条来自上游的 HTTP 400 ... unsupported image 报错,以便我知道该怎么办。
为什么是这个优先级:剪贴板里的图片字节可以坏掉,而链路上没有任何一层会检查它。已复现的两条路径:① 剪贴板存在注册格式 PNG 但字节是坏的时候,Chromium 逐字节透传,坏字节原样进入消息;② 复制一个 0 字节(或截断、改名)的图片文件时,我们的链路会发出一张空图 data:image/png;base64,。两种情况下上游都会返回 400,用户看到的是一条英文报错,且不知道该重试还是换图。
独立测试:构造 0 字节图片 / 文本改名成 .png / 头合法但内容损坏的 PNG,粘贴进输入框,验证不出现图片占位符、不产生图片消息并出现中文提示;粘贴合法 PNG/JPEG 验证照常插入占位符。
验收场景:
- 假设剪贴板中的图片数据为空(0 字节),当用户粘贴,则不插入图片占位符、不发送该图,并给出可见提示说明数据不完整、请重新截图或另存为 PNG/JPEG 后重试。
- 假设图片字节的头签名不属于 PNG / JPEG / GIF / WebP(例如 SVG、BMP、AVIF、HEIC,或一段被改名成
.png的文本),当用户粘贴,则拒绝该图并提示仅支持这四种格式。 - 假设图片头签名在白名单内但内容已损坏或被截断(例如 PNG 缺少尾部
IEND、JPEG 缺少尾部FF D9,或浏览器原生解码失败),当用户粘贴,则拒绝该图并给出与场景 1 相同的"数据不完整/无效"提示。 - 假设图片是合法的 PNG / JPEG / GIF / WebP,当用户粘贴,则行为与现状一致:插入
[imageN]占位符并随消息一起发送。 - 假设输入框里已经贴进了一张不合格的图(例如从历史会话恢复),当消息转换为 API 请求时,则不再把空字节或未知格式伪装成
image/png发出去——读不到/读空的图片直接跳过,未知扩展名不再冒充 PNG。 - 假设运行环境没有浏览器原生图片解码能力(如 jsdom、老宿主),当用户粘贴,则退化为头尾字节校验(不因缺少解码 API 而拒掉所有图片)。
用户故事:粘贴时在本地把图片降到 2000 像素预算(优先级:P1,对齐 Claude Code / claude.ai)
作为用户,我希望粘贴一张很长的截图(例如整页网页或长聊天记录)时它在本地就被缩到预算内,而不是收到一条看不懂的英文 400 报错,也不是让一张尺寸合规但超出预算的图白烧 token。
为什么是这个优先级:粘贴路径手上有宿主解码器与 canvas(createImageBitmap + canvas,零依赖),所以把我们主动选的 2000 像素预算(口径与理由见「出站图片按 2000 像素 / 5MB 预算收口」)直接做在这一步,而不是只守网关那道 8192 硬边界:
- 一步到位——否则同一张图会先在浏览器缩到 8192、再在 Node 侧缩到 2000:两次重编码白做工,且中间那一跳若体积偏大还会落到 JPEG,叠成二次有损。
- 天然满足网关的 8192 硬边界(边界证据与实测见「出站图片按 2000 像素 / 5MB 预算收口」)。
- 这一层不依赖按需下载的 sharp ⇒ 即使本机图像能力装不上、SDK 侧只能降级,粘贴路径也落在预算内(降级路径见「无法在本机缩小时…」)。
竞品做的是同一件事:Claude Code 客户端按 2000 像素(sharp,Node 侧);claude.ai 前端粘贴时就用 canvas 缩到 2000×2000(另加 512KB 体积预算)。
独立测试:构造任一边 2001 的图,验证粘贴后进入消息的是"任一边 ≤ 2000 且仍是白名单格式"的重编码副本;构造 2000 或以内的图,验证粘贴这一步没有重编码(仍是原始字节)。
验收场景:
- 假设粘贴的图片任一边超过 2000 像素,当用户粘贴,则在本地等比缩放到任一边不超过 2000 后插入占位符,并以数据 URL 形式随消息发送。
- 假设粘贴的图片任一边不超过 2000 像素,当用户粘贴,则粘贴这一步不做重编码,保持原始字节(含体积超过 5MB 的图——体积由「出站图片按 2000 像素 / 5MB 预算收口」处理,粘贴这步不因体积重编码)。
- 假设本地缩放在该宿主上不可行(没有 canvas、取不到 2d 上下文或转码失败),当用户粘贴,则按「发送前校验图片有效性」场景 3 的"数据不完整或无效"提示拒发该图,不新增尺寸类文案。
- 假设源图是 JPEG,当降采样,则输出 JPEG;源图是 PNG / GIF / WebP(可能带透明通道),则输出 PNG。
用户故事:工具产图与 Read 图片在入口就降到预算内,列表里只留降过的那份(优先级:P1,对齐 Claude Code)
作为长期在一条会话里干活的用户,我希望模型用 Read 读到的长截图、MCP / Exec 工具产出的图片在进入消息列表的那一刻就已经降到预算内,而不是等每一轮发请求时才临时缩小,以便这些图片的字节只以「降过的那一份」存在——agent 内存、宿主侧消息缓存、主进程到渲染进程的传输、渲染进程里都是同一份小图,会话攒下几十张截图也不会把内存拖垮。
为什么是这个优先级:目前预算只在出站那道闸上生效(见下一条故事):convertMessagesForAPI 缩小的结果只进当次请求体,消息列表里的图片字节始终是原件。于是一张 4K 截图在进程里至少存在四份拷贝——agent 的消息列表、宿主侧消息缓存(桌面端每个 pane 一个 StdioAgent 各一份)、跨进程传输、渲染进程——而桌面端渲染进程崩溃的现场就是这份常驻(实测 workingSet=2981 MB、messages=1842)。Claude Code 的做法是把预算放在入口:图片进列表前就重编码到位,之后 API 与界面共用同一份小图。这里照此收口,出站那道闸保留为兜底(历史消息、本机缩不动的图、直接给路径的图)。
独立测试:构造一张 4000×3000 的工具结果图片,验证写入消息块的是缩小后的字节(解出的尺寸 ≤2000、长度远小于原件)且原件被落盘、消息里带路径;构造一张 1200×800 的图,验证消息块里的字节与工具返回的逐字相同、没有任何落盘。
验收场景:
- 假设工具结果带一张预算内的图(任一边 ≤2000 且 base64 ≤5MB),当它被写进消息块,则字节与
mediaType与工具返回的逐字相同,不做重编码、不落盘。 - 假设工具结果的图任一边超过 2000 像素或 base64 超过 5MB,当它被写进消息块,则先按「出站图片按 2000 像素 / 5MB 预算收口」同一套阶梯缩放/压缩,回写进消息块的是降过的那份,原件不以 base64 形式继续留在内存里。
- 假设一张图被降过,当原件有可留的落点(Read 读到的本地文件、以路径贴进来的图本来就在盘上;MCP / Exec 凭空产出的图则落盘为临时文件),则消息块里带上指向原件的路径,模型与用户可以按路径裁掉不需要的部分后重新读取;落盘失败时保留原始字节,不因写盘失败而丢图。
- 假设消息里的图在入口已经降过,当它出站,则原样发送(不再重编码),但仍带「原始尺寸 → 缩小后尺寸」那行说明,让模型能把看到的坐标换算回原图。
- 假设本机图像处理能力不可用,当工具结果的图超预算,则入口这一步保持原始字节(不丢图、不阻断),出站沿用「无法在本机缩小时…」的降级路径,行为与改动前一致。
- 假设同一张图已经在入口降过,当它在后续多轮请求里反复出站,则不重复编码,输出字节保持稳定。
- 假设恢复一条旧会话,当它的消息里存着改造前的超预算原件,则照常可读、出站仍由那道闸兜住——入口收口只作用于之后新产生的工具结果,不回头改写历史消息。
- 假设当前模型不支持视觉,当工具结果带图,则维持既有行为(落盘 + 给路径、不内联发送),只是内存里那份同样降到预算内。
- 假设某张图在入口无法降下来(codec 抛错、格式不在白名单),当它被写进消息块,则保留原始字节并继续这一轮,不把整轮工具结果作废。
用户故事:出站图片按 2000 像素 / 5MB 预算收口(优先级:P1,对齐 Claude Code)
作为用户,我希望不管图片是从哪儿来的(粘贴、模型自己用 Read 工具读的本地图、工具产出的图、CLI 上以文件路径贴进来的图),只要它超出视觉模型能舒适接受的范围,都在发送前被缩小到安全范围,以便这一轮对话既不会因一张太大的图整轮失败,也不会白烧 token。
这道闸现在是兜底:工具产图与 Read 图片在新会话里已于入列表时降到预算内(见上一条故事),粘贴图在浏览器里就已收口,所以这里实际拦下的是历史消息、本机缩不动时留下来的图、以及直接以文件路径给出的图。它按「每一次出站」生效,因此不承担内存职责——内存由入口那一层管。
为什么是这个优先级:上限口径对齐 Claude Code——单边 2000 像素与 base64 5MB。其中 8192 像素是网关的硬边界:上游对任一边超过 8192 像素的图片直接返回 HTTP 400 ... You have uploaded an unsupported image(通用兜底文案,不是格式问题),并且整轮请求失败。实测(2026-09-20,stream: true,与客户端实际请求一致):8192x1500 通过而 8193x1500 被拒、2250x8192 通过而 2250x9500 被拒;该边界只与单边像素有关——像素总数(28 MP 的 7000x4000 通过、16.5 MP 的 11000x1500 被拒)、字节数(6.8 MB 的 1500x1500 通过、1.3 MB 的 WebP 被拒)和容器格式都不影响判定。而 2000 像素 / 5MB 是我们主动选的预算:图更小则 token 更省、延迟更低,行为也与 Claude Code 一致;要特别注意 5MB 不是网关的硬限(实测 9.0 MB base64 的 1500x1500 噪声图仍返回 200),它是我们主动设的体量预算。粘贴路径已经在浏览器里按 2000 收口(见「粘贴时在本地把图片降到 2000 像素预算」),所以这道 SDK 侧收口负责的是它覆盖不到的部分:模型自己 Read 的本地图、工具产出的图、CLI 上以文件路径贴进来的图(都没有浏览器能力),以及粘贴图中"尺寸已合规但体积超 5MB"的那一类。
独立测试:构造单边 3000 像素的图(尺寸超预算)与 base64 超过 5MB 的图(体积超预算)各一张,验证转换后的 API 请求里图片尺寸 ≤2000 像素、base64 ≤5MB 且容器格式仍在白名单内;构造 1200x800 的小图,验证送出的仍是原始字节。
验收场景:
- 假设出站图片任一边超过 2000 像素,当消息转换为 API 请求,则等比缩小到任一边不超过 2000 像素后再发送(长宽比不变)。
- 假设出站图片的 base64 超过 5MB,当消息转换为 API 请求,则先在不改变尺寸的前提下压缩,仍超则继续缩小尺寸,直到 base64 不超过 5MB。
- 假设出站图片的尺寸与体积都在预算内,当消息转换为 API 请求,则原样发送原始字节(不因"顺手压一下"而重编码)。
- 假设图片被缩小过,当消息转换为 API 请求,则附在图片后的尺寸说明同时给出原始尺寸与缩小后的尺寸,便于模型把看到的坐标换算回原图。
- 假设本机图像处理能力不可用(未安装、下载失败、加载报错),当消息转换为 API 请求,则不阻断这一轮:单边超过 8192 的图按「无法在本机缩小时…」那条处理,其余图片照常发送。
- 假设缩小与压缩之后仍超出预算(例如极端长宽比),当消息转换为 API 请求,则按同一条说明文本处理,不把超出硬边界的图发出去。
- 假设源图是 PNG / GIF / WebP 且带透明通道,当它被压缩或缩小,则不因处理而把透明区域变成黑块(优先选能保住透明的输出格式)。
用户故事:无法在本机缩小时,超出硬边界的图不进请求,改为一行说明(优先级:P1)
作为用户,我希望当本机没有可用的图像处理能力、缩不动那张超限图时,这一轮对话仍然能继续,并且模型/我能从消息里看出发生了什么以及该怎么做。
为什么是这个优先级:网关的 400 会让整轮请求失败。这条故事是「出站图片按 2000 像素 / 5MB 预算收口」在本机缩不动时的降级路径(正常路径见上一条)。已复现的触发条件与阈值:单边 > 8192 像素,与像素总数、字节数、格式无关。参照 opencode 的做法:跳过该图并在消息里留一行可操作的说明,比让整轮炸掉更有用。
独立测试:构造带超限尺寸图片块(数据 URL 与本地文件路径各一)的消息,验证转换后的 API 请求里没有该 image_url、而是出现包含实际尺寸与上限的说明文本,且同一条消息里的文本内容照常保留;构造预算内的图,验证仍原样发出。
验收场景:
- 假设本机图像处理能力不可用,且消息中的图片任一边超过 8192 像素,当消息转换为 API 请求,则不发送该图,改为插入一行文本说明(写明实际尺寸、8192 上限,以及"裁剪 / 分段 / 缩小后再试"的处理办法),消息中的其它内容照常发送。
- 假设图片任一边不超过 8192 像素,当消息转换为 API 请求,则原样发送(含体积超过 5MB 的图——此时没有压缩能力,宁可照发也不丢上下文)。
- 假设图片容器头解析不出尺寸(未知格式或头部被截断),当消息转换为 API 请求,则不因"判不出尺寸"而丢弃该图。
- 假设该图来自本地文件路径,当它因超限被跳过,则说明文本里带上该路径,便于模型裁剪后重新读取。
边界情况
- 如果剪贴板不包含图片会怎样? 系统应忽略粘贴操作,或者如果同时存在文本则作为普通文本粘贴处理。
- 如果无法创建临时文件会怎样? 系统应记录警告并不插入占位符。
- 如果用户从输入文本中删除了占位符会怎样? 图片仍应在
attachedImages列表中,但如果文本中未引用则不会发送(或者如果实现发送所有附加图片则可能会发送)。注意:当前实现仅发送被引用的图片。 - 如果用户粘贴非常大的图片会怎样? 三层处理:粘贴路径先在本地按 2000 像素预算降采样(浏览器 canvas,零依赖、不依赖本机是否装上了图像库);模型自己 Read 本地长截图、工具产出图这些没有浏览器能力的入口,在进入消息列表那一刻按同一套预算降到预算内(原件落盘、消息里带路径),这样 API 与展示流共用同一份小图;出站前再由 SDK 侧按 2000 像素 / 5MB 兜底一遍,拦住历史消息、本机缩不动的图与以文件路径贴进来的图。
- 上游硬边界(8192)和我们自己的预算(2000 / 5MB)是什么关系? 两个不同的东西。8192 是网关的拒收边界,越过就整轮 400;2000 像素与 5MB 是我们主动选的预算,为的是省 token、降延迟并与 Claude Code 行为一致(5MB 甚至不是网关的硬限——实测 9.0 MB base64 的 1500x1500 噪声图仍返回 200)。粘贴路径现在也按 2000 收口:以前能原样通过的 2000~8192 像素粘贴截图,现在会在粘贴时就被缩小,长图上的小字会比以前更小;这是刻意取舍,而且预算调高调低都不涉及网关边界,可随时调整。
- 粘贴图还会带"原始尺寸 / 显示尺寸"那行说明吗? 不会。那行说明只在图片真的被缩过尺寸时发出,而粘贴图在浏览器里已经缩到 ≤2000,SDK 侧看到的是预算内的图、原样放行。工具产图与 Read 图片的说明在入口降尺寸时就挂到那张图上了,之后每轮出站都带着它(出站不再重编码,但说明不丢);直接以文件路径贴进来的图仍由出站那道闸在缩小后发出。
- 界面里点开看到的是原图还是降过的那份? 降过的那份。消息列表里存的就是预算内的字节,所以聊天里的图片预览与超大图点击放大看到的都是缩放后的版本;需要原始分辨率时按消息里带出的路径打开原件。
- 本机图像处理能力拿不到时会怎样? 不阻断对话:入口这一步保持原始字节,单边超过 8192 的图按「无法在本机缩小时…」跳过并留一行说明,其余图片照常发送(含体积超过 5MB 的图,宁可照发也不丢上下文)。这层的代价是:拿不到能力的环境里,超限图表现为"被跳过 + 说明"而不是"被缩小",用户可按说明里的路径自行裁剪后重试。
- 本机图像处理能力是从哪来的、什么时候就绪? 宿主自带的那份 CLI(桌面端 / VS Code / JetBrains 各自把 bundle 落到
~/.wave/cli/<端>/,身上没有node_modules)在启动时按需从镜像站下载 sharp 及其运行期闭包(约 20MB,落到三端共享的~/.wave/cli/node_modules,跨 CLI 升级保留),准备好之前不对外提供会话服务——所以不存在"刚启动就贴图、撞上还没装好"的窗口(与宿主先在启动期备好 ripgrep 再拉起 CLI 的做法一致)。用 npm 安装的wave-code自带该依赖,这一步是空转。装不上时只降级(见上一条),不影响聊天;同一进程内不重试(避免每贴一张图就打一次网络),下次启动会再试。 - 降采样/压缩后的输出体积也要有上限吗? 要:base64 不超过 5MB;能保住透明就优先保住(PNG 类源图先试无损/调色板压缩),保不住才转 JPEG。
- 本地校验是结构体检而非完整解码:头尾字节 + 宿主原生解码只能覆盖"空/明显不是图片/被截断/解码器解不开"这几类;一张结构完整但上游仍解不开的图仍可能通过,此时仍可能出现上游 400。
- 格式白名单是刻意收窄的:svg / bmp / avif / heic 等即使宿主能显示也不放行(上游模型只接受 png/jpeg/gif/webp),被拒时按场景 2 提示用户转换格式。
- 拖拽/上传文件的图片走另一条链路:桌面端拖入图片文件走"上传为附件 + 路径引用",不会生成图片数据块,因此不在本校验范围内(本校验只作用于产生图片块的粘贴路径);这类图片若尺寸超限,由「出站图片按 2000 像素 / 5MB 预算收口」与其降级路径兜底。
- 草稿里的图片存在哪里、存什么? 只存在 webview 侧的本地存储(IndexedDB),按会话分键(新对话按 pane);只存已经能发送的图片——粘贴中途失败、被格式/尺寸校验拒掉的图不入草稿;单会话保留张数有上限(当前定 20 张),超出时按最旧淘汰。图片字节不进宿主保存的那份草稿字符串:那段文本每次按键都会上报一次,把 base64 写进去等于每次输入都传 MB 级数据。
- 从草稿里删掉一张图会怎样? 删除标签即从草稿里去掉该图——下次保存只登记还留在输入框里的图片,存储里那份随之下次保存清掉;整条草稿发出去之后,该会话的草稿图片整体清空。
- 草稿图片和「发送前校验图片有效性」是什么关系? 同一道校验:图片先过校验(确认能发送)才登记进草稿,所以回填出来的图片不会等到发送时才被拒。