如何设计一个支持图片输入的 AI Chat

很多 AI Chat 的第一版是文本输入框。用户输入问题,前端发送 messages,后端调用模型,assistant 流式返回。

当你要支持图片输入时,最容易做成“文本框旁边加一个上传按钮”。这能跑通 demo,但很难支撑真实产品。因为图片输入涉及文件处理、预览、上传、权限、模型上下文、失败恢复和结果解释。

一个好的图片输入 Chat,不只是能把图片发给模型,而是让用户清楚地知道:我传了什么,AI 会看什么,处理到哪一步,回答基于哪里,出错后怎么修正。

这篇文章从前端工程师视角,设计一个可落地的图片输入 AI Chat。

先定义产品边界

支持图片输入并不等于支持所有图片任务。

先明确你的 Chat 要做哪类事情:

  • 看图问答:用户上传图片并提问。
  • 截图诊断:用户上传页面截图,让 AI 分析报错或 UI 问题。
  • 图片内容审核:判断图片是否符合规则。
  • 图片信息抽取:识别票据、表单、商品标签。
  • 图片对比:比较两张图的差异。
  • 图片编辑指令:用户上传图片并要求修改。

不同任务决定不同交互。

如果是看图问答,用户可能需要多轮追问。如果是票据抽取,用户需要字段校对。如果是截图诊断,用户可能需要圈出报错区域。如果是图片对比,图片顺序和标签就很重要。

本文重点讨论最常见的“图片 + 文本问题 + AI 回答”的 Chat,但会保留扩展空间。

推荐交互流程

一个稳定的图片输入流程可以拆成八步:

  1. 用户选择、拖拽或粘贴图片。
  2. 前端校验格式、大小、数量和分辨率。
  3. 前端生成本地预览。
  4. 必要时压缩或裁剪图片。
  5. 上传图片并创建 attachment。
  6. 图片进入 ready 状态后允许发送。
  7. 用户输入问题并提交。
  8. AI 基于文本和图片生成回答。

注意这里不是“选图后立即发送”。图片需要先变成稳定的 attachment,再进入消息上下文。这样失败时能定位到附件,而不是整条消息变成黑箱错误。

数据模型设计

不要把图片只塞进 message content 里。图片有自己的生命周期,应该独立建模。

type ImageAttachmentStatus =
  | 'local'
  | 'validating'
  | 'processing'
  | 'uploading'
  | 'ready'
  | 'failed';

type ImageAttachment = {
  id: string;
  fileName: string;
  mimeType: 'image/png' | 'image/jpeg' | 'image/webp';
  size: number;
  width?: number;
  height?: number;
  localPreviewUrl?: string;
  remoteUrl?: string;
  status: ImageAttachmentStatus;
  error?: string;
};

type ChatMessage = {
  id: string;
  role: 'user' | 'assistant';
  text: string;
  imageAttachmentIds?: string[];
  status: 'sending' | 'streaming' | 'completed' | 'failed';
};

这样设计有几个好处:

  • 图片可以在发送前独立上传和失败重试。
  • 同一张图片可以被多轮追问复用。
  • 消息只保存引用,不必保存完整文件状态。
  • UI 可以在输入区和消息区复用同一个附件组件。

如果业务要求更强的可追溯性,还可以记录图片分析版本:

type ImageAnalysis = {
  id: string;
  attachmentId: string;
  model: string;
  caption?: string;
  detectedText?: string;
  createdAt: string;
};

这适合图片会被预解析、检索或审计的场景。

输入区组件应该支持哪些能力

图片输入区不只是 <input type="file" />

一个可用的 Chat 输入区通常需要:

  • 点击上传。
  • 拖拽上传。
  • 粘贴截图。
  • 缩略图预览。
  • 删除图片。
  • 上传进度。
  • 失败重试。
  • 数量限制。
  • 文件大小提示。
  • 发送前禁用不可用附件。

用户会自然地把截图复制后直接粘贴进 Chat。如果你的产品面向开发者、运营、客服或设计师,粘贴截图通常比文件选择更重要。

一个简化的粘贴处理逻辑如下:

function getImagesFromClipboard(event: ClipboardEvent) {
  const files: File[] = [];

  for (const item of Array.from(event.clipboardData?.items ?? [])) {
    if (item.kind !== 'file') continue;
    const file = item.getAsFile();
    if (file && file.type.startsWith('image/')) {
      files.push(file);
    }
  }

  return files;
}

工程上要注意,粘贴图片经常没有友好的文件名,可以生成类似 pasted-image-2026-05-11.png 的名称用于展示。

本地校验与预处理

图片进入上传前,前端应该先做基础校验。

常见规则包括:

  • 类型只允许 PNG、JPEG、WebP。
  • 单张图片不超过某个大小。
  • 单次消息最多上传若干张。
  • 图片宽高不能太小。
  • 超大图片需要压缩。
  • 动图是否支持要明确说明。
function validateImage(file: File) {
  const allowedTypes = ['image/png', 'image/jpeg', 'image/webp'];
  const maxSize = 10 * 1024 * 1024;

  if (!allowedTypes.includes(file.type)) {
    return '只支持 PNG、JPEG 和 WebP 图片。';
  }

  if (file.size > maxSize) {
    return '图片不能超过 10MB。';
  }

  return null;
}

压缩策略要谨慎。压缩可以降低上传成本和模型处理成本,但过度压缩会影响 OCR、截图诊断和小字识别。

比较稳妥的策略是:

  • 普通照片可以压缩到合理分辨率。
  • 包含文字的截图尽量保留清晰度。
  • 票据、表格、合同截图不要过度降低宽度。
  • 压缩后保留原图尺寸元信息,便于定位问题。

上传策略:先传附件,再发消息

推荐把图片上传和消息发送拆开。

前端先调用后端获取上传地址,上传成功后拿到 attachmentId,发送消息时只带 attachment id。

这种方式有几个好处:

  • 上传失败不会污染聊天消息。
  • 后端可以做权限和病毒扫描。
  • 大图片不需要经过业务 API body。
  • 图片可以复用在同一会话的后续问题中。
  • 模型调用层只处理已授权、已存在的附件。

如果产品很轻量,也可以直接用 multipart 一次性提交。但当你需要重试、复用、审计和多轮上下文时,attachment 模型会更稳。

发送给模型的上下文

消息发送时,后端需要把文本和图片组装成模型能理解的输入。

前端不一定需要知道具体模型 API 格式,但需要和后端约定清楚 message payload。

type SendImageChatRequest = {
  conversationId: string;
  text: string;
  attachmentIds: string[];
};

后端再把 attachment 转成模型输入。例如:

type ModelInputPart =
  | { type: 'input_text'; text: string }
  | { type: 'input_image'; imageUrl: string };

function buildVisionMessage(text: string, imageUrls: string[]): ModelInputPart[] {
  return [
    ...imageUrls.map((imageUrl) => ({
      type: 'input_image' as const,
      imageUrl,
    })),
    { type: 'input_text', text },
  ];
}

顺序也很重要。如果用户上传多张图片,UI 应该让用户知道它们的顺序,必要时允许添加标签,例如“图 1:报错截图”“图 2:网络面板”。

多轮对话中的图片上下文

支持图片输入后,一个关键问题是:后续追问是否继续带上图片?

例如用户先发一张报错截图问“这是什么问题?”,assistant 回答后,用户追问“那我应该改哪个配置?”此时模型通常仍然需要原图上下文。

常见策略有三种:

  • 每轮都显式携带当前会话相关图片。
  • 只在用户勾选“继续引用这张图片”时携带。
  • 后端维护会话上下文,根据最近消息和附件策略选择图片。

前端应该让用户看见当前上下文里有哪些图片。否则用户会困惑:AI 这次到底还看不看得到刚才那张图?

一个实用设计是在输入框上方展示“本轮将引用”的图片缩略图,允许用户移除。

回答呈现与图片引用

图片问答不一定只返回文本。更好的体验是能把回答和图片局部关联起来。

例如:

  • “红框区域的按钮被禁用,因为表单缺少必填项。”
  • “表格第三列的金额格式不一致。”
  • “截图右上角的网络错误是 401。”

如果后端能返回区域引用,前端就可以高亮对应图片区域。

type ImageCitation = {
  attachmentId: string;
  label: string;
  region?: {
    x: number;
    y: number;
    width: number;
    height: number;
  };
};

type AssistantImageAnswer = {
  text: string;
  citations: ImageCitation[];
};

区域坐标建议使用归一化坐标,范围为 0 到 1。这样无论图片在 UI 中如何缩放,都可以稳定渲染高亮框。

function toRenderedBox(
  region: { x: number; y: number; width: number; height: number },
  renderedWidth: number,
  renderedHeight: number,
) {
  return {
    left: region.x * renderedWidth,
    top: region.y * renderedHeight,
    width: region.width * renderedWidth,
    height: region.height * renderedHeight,
  };
}

这类引用能力会让图片 Chat 从“看起来很聪明”变成“用户可以验证”。

错误恢复设计

图片输入的失败类型很多,错误提示要能指导用户下一步。

常见错误包括:

错误用户可行动作
文件格式不支持换成 PNG、JPEG 或 WebP
文件太大压缩或裁剪后重新上传
图片过小或模糊上传更清晰版本
上传失败重试上传
图片包含敏感内容确认权限或移除图片
模型无法理解补充文字说明或圈出区域
请求超时稍后重试或减少图片数量

不要只展示“处理失败”。图片 Chat 的失败通常可以被用户修正,前端要给出明确路径。

安全与隐私

图片比文本更容易包含意外敏感信息。一个截图可能包含用户头像、邮箱、客户列表、订单号、token、内部 URL 或浏览器插件信息。

工程上至少要考虑:

  • 上传前提示不要上传无权处理的图片。
  • 使用带过期时间的访问 URL。
  • 后端校验 attachment 是否属于当前用户和会话。
  • 对图片访问做审计。
  • 支持用户删除附件。
  • 在日志中不要记录原始图片 URL 或敏感 OCR 文本。
  • 根据业务需要增加内容安全扫描。

对于企业场景,附件生命周期尤其重要。用户删除会话时,是否删除原图、缩略图、解析结果和模型缓存,需要有明确策略。

常见坑

图片还没上传完成就允许发送

这会导致消息发送成功但模型拿不到图片。输入区应该根据 attachment 状态决定发送按钮是否可用。

只在当前输入框保存图片状态

用户发送后,图片应该成为消息的一部分。如果图片状态只存在输入框组件里,刷新、重试和历史消息展示都会出问题。

压缩截图导致文字不可读

截图类任务经常依赖小字。过度压缩会让模型无法识别错误信息、表格字段和按钮文案。

多张图片没有顺序和标签

用户说“第二张图的问题更明显”,模型和界面都需要知道第二张是哪张。多图输入应该展示顺序,必要时允许用户改名或备注。

不处理追问时的图片上下文

多轮对话里,后续问题可能仍然依赖图片。系统要明确哪些图片仍在上下文中,并让用户可见。

不做附件权限校验

发送消息时只传 attachment id 并不代表安全。后端必须检查该 attachment 是否属于当前用户、当前组织和当前会话。

小结

支持图片输入的 AI Chat,不是给文本框加一个上传按钮,而是新增一套附件生命周期:选择、校验、预览、预处理、上传、发送、引用、追问和删除。

前端工程师要把图片当成一等公民建模。图片有状态、有权限、有顺序、有预览、有失败原因,也可能有区域引用和解析结果。

当用户能清楚看到 AI 使用了哪些图片、处理到哪一步、回答基于哪里,并且能在失败时修正输入,图片 Chat 才会从 demo 变成稳定可用的产品能力。