Comp AI CRM 中 AI Elements 附件组件实战:Attachments 组合式文件展示方案
2026/9/24 17:06:16 网站建设 项目流程
  • 后端
  • 前端
  • CRM
  • 人工智能
  • AI Agent

【免费下载链接】crm

Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.

项目地址:https://gitcode.com/gh_mirrors/crm48/crm
点击查看免费下载

Attachments 是 AI Elements 组件库中负责"附件与来源文档展示"的组件族,为 AI 聊天界面中的文件、图片、视频、音频与引用来源提供统一、可组合的渲染方案。本文以仓库内 .agents/skills/ai-elements/references/attachments.md 为核心,结合同目录可运行示例脚本与仓库中的附件数据实现,完整讲解安装、AI SDK 集成、三种布局变体、全部 Props 与工具函数,读完即可在 Next.js + AI SDK 的聊天应用中落地一个支持缩略图、徽章、文件列表与悬停预览的附件区。

组件定位与能力概览

Attachment组件族解决的是 AI 对话场景中一个高频问题:用户上传的文件、模型引用的来源文档,如何以一致且美观的方式展示?它提供的是一套统一的附件展示入口,同时覆盖FileUIPart(用户上传文件)与SourceDocumentUIPart(AI 引用的来源文档)两类 AI SDK 数据。

组件核心能力包括:

  • 三种展示变体:grid(缩略图网格)、inline(紧凑徽章,配合悬停预览)、list(带完整元数据的文件行);
  • 同时支持 AI SDK 的FileUIPartSourceDocumentUIPart数据;
  • 自动媒体类型检测(image、video、audio、document、source),由getMediaCategory工具函数驱动;
  • inline 预览的悬停卡片支持(AttachmentHoverCard系列);
  • 带可自定义回调的移除按钮,悬停时出现;
  • 组合式(composable)架构:AttachmentsAttachmentAttachmentPreviewAttachmentInfoAttachmentRemove等子组件自由拼装;
  • 无障碍支持(移除按钮带 ARIA 屏幕阅读器标签);
  • TypeScript 支持,并导出getMediaCategorygetAttachmentLabel等工具函数。

从仓库结构看,该组件文档与示例统一存放在 .agents/skills/ai-elements 目录下,配套三个可直接运行的示例脚本:scripts/attachments.tsx(grid)、scripts/attachments-inline.tsx(inline)、scripts/attachments-list.tsx(list),可作为独立调试与效果预览的参照。

安装与前置条件

附件组件属于 AI Elements 组件库,通过其 CLI 安装,安装命令会将组件源码直接下载并集成到项目components目录下(默认路径为@/components/ai-elements/):

npx ai-elements@latest add attachments

根据项目包管理器不同,也可以使用等价命令:pnpm dlx ai-elements@latestbunx --bun ai-elements@latest

安装前需要确认环境满足以下前提:

  • Node.js 18 或更高版本;
  • Next.js 项目,且已安装 AI SDK;
  • 已配置 shadcn/ui(若未安装,运行安装命令时会自动安装)。

组件安装后即为项目代码的一部分(而非隐藏在库中的黑盒),可以直接阅读源码、按需修改样式(Tailwind 类)与行为,与项目自身组件没有区别。

注意:仓库中的 AI Elements 技能文档说明(.agents/skills/ai-elements/SKILL.md)建议运行所有 CLI 命令时使用与项目packageManager匹配的包运行器,上述npx示例应替换为项目实际使用的命令。

与 AI SDK 集成:展示用户上传文件

附件组件最常见的用法是在聊天消息或输入区中展示用户上传的文件。AI SDK 的消息parts中,上传文件对应FileUIPart类型,因此只需要把FileUIPart & { id: string }数组交给Attachments,并为每个文件渲染一个Attachment即可:

"use client"; import { Attachments, Attachment, AttachmentPreview, AttachmentInfo, AttachmentRemove, } from "@/components/ai-elements/attachments"; import type { FileUIPart } from "ai"; interface MessageProps { attachments: (FileUIPart & { id: string })[]; onRemove?: (id: string) => void; } const MessageAttachments = ({ attachments, onRemove }: MessageProps) => ( <Attachments variant="grid"> {attachments.map((file) => ( <Attachment key={file.id} data={file} onRemove={onRemove ? () => onRemove(file.id) : undefined} > <AttachmentPreview /> <AttachmentRemove /> </Attachment> ))} </Attachments> ); export default MessageAttachments;

要点说明:

  • data接收附件数据对象,需包含id;组件内部通过mediaTypetypeurlfilename等字段进行媒体类型判断与预览渲染;
  • onRemove为可选回调,仅在需要删除能力时传入;内部通过闭包把当前附件的id绑定到对应按钮;
  • AttachmentPreview负责渲染图片/视频预览或类型图标,AttachmentRemove是悬停时出现的移除按钮;
  • 由于整个组件支持组合,你可以自行决定子组件的排列与嵌套层级,例如只渲染AttachmentPreview而不显示移除按钮。

三种布局变体

Attachments通过variant属性切换整体布局,三种变体分别对应不同的展示场景,仓库内三个示例脚本即为各自变体的完整实现。

Grid 变体:消息内的缩略图网格

适用于消息中带视觉缩略图的附件展示。示例 scripts/attachments.tsx 中,附件数据包含filenameidmediaTypetype: "file"url字段;图片类附件(如image/jpeg)渲染缩略图,无url的 PDF、视频则回退为类型图标。

<Attachments variant="grid"> {attachments.map((attachment) => ( <AttachmentItem key={attachment.id} attachment={attachment} onRemove={handleRemove} /> ))} </Attachments>

示例用memo包裹AttachmentItem,并用useCallback稳定handleRemove,避免列表重渲染时不必要的子组件更新——这是在聊天消息列表中渲染大量附件时值得借鉴的性能实践。

Inline 变体:输入区的紧凑徽章

适用于输入区等紧凑场景,以徽章形式展示附件,并在悬停时弹出预览卡片。示例 scripts/attachments-inline.tsx 展示了完整的 hover 交互组合:

<AttachmentHoverCard key={attachment.id}> <AttachmentHoverCardTrigger asChild> <Attachment data={attachment} onRemove={handleRemove}> <div className="relative size-5 shrink-0"> <div className="absolute inset-0 transition-opacity group-hover:opacity-0"> <AttachmentPreview /> </div> <AttachmentRemove className="absolute inset-0" /> </div> <AttachmentInfo /> </Attachment> </AttachmentHoverCardTrigger> <AttachmentHoverCardContent> {/* 图片类附件展示大图预览 + 文件名 + mediaType */} </AttachmentHoverCardContent> </AttachmentHoverCard>

该示例的核心逻辑在于:先用getMediaCategory(attachment)得到媒体分类,再通过mediaCategory === "image" && attachment.type === "file" && attachment.url判断是否渲染图片大图预览;卡片内展示文件名(getAttachmentLabel结果)与mediaType元数据。此外,示例中同时包含type: "file"type: "source-document"两类数据,验证了组件对来源文档的统一支持。

List 变体:带完整元数据的文件列表

适用于文件列表场景,每行展示图标、文件名、媒体类型与移除按钮。示例 scripts/attachments-list.tsx 展示了关键差异——为AttachmentInfo传入showMediaType以在文件名下方显示媒体类型:

<Attachment data={attachment} key={attachment.id} onRemove={handleRemove}> <AttachmentPreview /> <AttachmentInfo showMediaType /> <AttachmentRemove /> </Attachment>

示例中容器额外传入了className="w-full max-w-md",说明Attachments会透传 HTML 属性,方便限制宽度、对齐等布局需求。

Props 参考

<Attachments />

容器组件,负责设置布局变体。

PropTypeDefaultDescription
variantunknown-展示布局变体(grid / inline / list)。
...propsReact.HTMLAttributes<HTMLDivElement>-透传到底层 div 元素。

<Attachment />

单个附件条目包装器。

PropTypeDefaultDescription
dataunknown-附件数据(带 id 的 FileUIPart 或 SourceDocumentUIPart)。
onRemove() => void-点击移除按钮时触发的回调。
...propsReact.HTMLAttributes<HTMLDivElement>-透传到底层 div 元素。

<AttachmentPreview />

渲染媒体预览(图片、视频或类型图标)。

PropTypeDefaultDescription
fallbackIconReact.ReactNode-无预览可用时展示的自定义图标。
...propsReact.HTMLAttributes<HTMLDivElement>-透传到底层 div 元素。

<AttachmentInfo />

展示文件名与可选媒体类型。

PropTypeDefaultDescription
showMediaTypebooleanfalse是否在文件名下方展示媒体类型。
...propsReact.HTMLAttributes<HTMLDivElement>-透传到底层 div 元素。

<AttachmentRemove />

悬停时出现的移除按钮。

PropTypeDefaultDescription
labelstring-按钮的屏幕阅读器标签(无障碍必需)。
...propsReact.ComponentProps<typeof Button>-透传到底层 Button 组件。

<AttachmentHoverCard />

悬停预览功能的外层包装。

PropTypeDefaultDescription
openDelaynumber0打开悬停卡片的延迟(毫秒)。
closeDelaynumber0关闭悬停卡片的延迟(毫秒)。
...propsReact.ComponentProps<typeof HoverCard>-透传到底层 HoverCard 组件。

<AttachmentHoverCardTrigger />

悬停卡片的触发元素。

PropTypeDefaultDescription
...propsReact.ComponentProps<typeof HoverCardTrigger>-透传到底层 HoverCardTrigger 组件。

<AttachmentHoverCardContent />

悬停卡片中展示的内容。

PropTypeDefaultDescription
alignunknown-悬停卡片内容的对齐方式。
...propsReact.ComponentProps<typeof HoverCardContent>-透传到底层 HoverCardContent 组件。

<AttachmentEmpty />

无附件时的空状态组件。

PropTypeDefaultDescription
...propsReact.HTMLAttributes<HTMLDivElement>-透传到底层 div 元素。

需要说明的是,原文档中variantdataalign等 Prop 的类型标注为unknown,实际取值以源码实现为准:variant对应三种布局变体名,data对应 AI SDK 的FileUIPart/SourceDocumentUIPartalign对应 HoverCard 的对齐选项。

工具函数

组件包导出两个可在业务代码中直接复用的工具函数。

getMediaCategory(data)

返回附件的媒体分类,用于自定义预览逻辑(如 inline 示例中判断是否渲染图片大图):

import { getMediaCategory } from "@/components/ai-elements/attachments"; const category = getMediaCategory(attachment); // Returns: "image" | "video" | "audio" | "document" | "source" | "unknown"

从示例脚本看,该函数依据mediaTypetype字段进行判定:image/jpeg等归于imageapplication/pdf归于documentvideo/mp4归于videoaudio/mp3归于audiotype: "source-document"归于source

getAttachmentLabel(data)

返回附件的展示标签,优先取文件名,无文件名时回退为 "Image" 或 "Attachment" 等通用文案:

import { getAttachmentLabel } from "@/components/ai-elements/attachments"; const label = getAttachmentLabel(attachment); // Returns filename or fallback like "Image" or "Attachment"

该函数适用于需要统一展示附件名称的场景,如 hover 卡片标题、无障碍标签或列表首列。

仓库中的附件数据处理对照

AI Elements 的 Attachments 组件解决的是前端展示,而附件数据从上传到可预览 URL 的链路在仓库中另有实现可对照参考。在 apps/api/src/conversations/conversation-attachments.ts 中,可以看到与服务端对话附件同构的数据形态:

  • StoredBuilderAttachment类型包含idnamemediaTypesize四个字段,与前端附件组件消费的filename/mediaType/id数据一一对应;
  • builderMessageWithAttachments将数据库中的 JSON 消息与附件列表合并,为每个附件生成idnametypesize,并通过isPreviewableImage判断是否为可预览图片(image/gifimage/jpegimage/pngimage/webp),是则生成预览 URL,否则置为null
  • attachmentUrl生成/api/conversations/attachments/{id}形式的预览地址,并支持带share分享令牌的变体。

从源码结构看,这与前端getMediaCategory的"按媒体类型决定展示形态"思路一致:服务端决定"哪些类型值得生成预览 URL",前端决定"预览以何种视觉形态呈现",两者共同构成了完整的附件展示链路。仓库中 apps/app/components/agent-builder 下的聊天相关组件(如agent-builder-chat.tsxagent-composer.tsx)也在消息交互中涉及附件处理,可作为 AI 对话界面集成附件的进一步参考。

定制与无障碍实践

由于组件代码直接进入项目目录,定制非常直接:

  • 样式定制:修改components/ai-elements/attachments.tsx中的 Tailwind 类即可,例如调整缩略图圆角、徽章配色或移除按钮样式;所有子组件均透传HTMLAttributes,也可通过className就地覆盖;
  • 行为定制onRemove回调由你完全掌控,可对接状态管理、乐观更新或后端删除接口;hover 卡片的开关延迟通过AttachmentHoverCardopenDelay/closeDelay调节,避免误触;
  • 无障碍AttachmentRemovelabel属性用于屏幕阅读器朗读;建议为Attachment容器补充对应的 aria-label(如文件名),使附件区对辅助技术可感知。

常见问题速查

  • 组件无样式:确认项目按 shadcn/ui(Tailwind 4)规范配置globals.css,包含 Tailwind 导入与 shadcn/ui 基础样式;
  • CLI 未添加任何文件:确认当前工作目录是项目根目录(存在package.json)、components.json配置正确,并使用最新版 CLI(npx ai-elements@latest);
  • @/别名导入失败(module not found):检查tsconfig.json中是否配置了@/*路径别名,需包含"baseUrl": ".""paths": { "@/*": ["./*"] }
  • 缩略图不显示:确认附件数据的url非空且mediaType属于图片类型——inline 示例中明确以attachment.type === "file" && attachment.url为前提才渲染大图。

结语

Attachments 组件族以"容器 + 子组件自由组合"的方式,把 AI 聊天中最常见的附件展示需求(网格缩略图、输入区徽章、文件列表、来源文档)收敛为一套可预测、可定制、可无障碍访问的 API。配合getMediaCategory/getAttachmentLabel工具函数与 AI SDK 原生数据类型的直接支持,它可以无缝嵌入任意基于 Next.js + AI SDK 的对话界面。仓库中该组件的中文文档、三份可运行示例脚本,以及与 apps/api/src/conversations/conversation-attachments.ts 对应的服务端附件链路,为直接上手和二次开发提供了完整的参考。

  • 后端
  • 前端
  • CRM
  • 人工智能
  • AI Agent

【免费下载链接】crm

Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.

项目地址:https://gitcode.com/gh_mirrors/crm48/crm
点击查看免费下载
上一篇:VidBee开源视频下载工具完全指南:1000+网站视频离线保存
下一篇:VoxCPM文本转语音快速上手:3条命令装好它,还能克隆你的声音

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询