附件

支持附件栏形式和附件、文字内容内联混排两种交互模式,发送、编辑和历史沿用同一模式。

attachments.tsx
import '@volcengine/chatkit/styles.css';

import { Chatkit, type ChatkitUploader, useChatRuntime } from '@volcengine/chatkit';
import { createMockAdapter } from '@volcengine/chatkit/adapters/mock';
import { useState } from 'react';

import { localUpload } from '../backends/local-upload';

const adapter = createMockAdapter({});

// 这里只演示附件交互:文件转为 base64 内联资源,不上传也不持久化。
const upload: ChatkitUploader = localUpload;

// 附件:给了 upload 才显示。点输入框左下角的按钮,或把文件拖进来、粘贴进来。
// 可选类型默认取后端声明支持的;Mock 还支持 ZIP、Word、Excel、PowerPoint 等常见文件。
export default function Attachments() {
  const [inline, setInline] = useState(false);
  const runtime = useChatRuntime({ sessionId: 'attachments', adapter });
  if (!runtime) return null;
  return (
    <div className="demo-column">
      <div className="demo-bar">
        <strong>附件交互(base64 内联,不持久化)</strong>
        <label>
          <input type="checkbox" checked={inline} onChange={event => setInline(event.target.checked)} />
          内联混排
        </label>
      </div>
      <div className="demo-main">
        <Chatkit
          runtime={runtime}
          settings={{ welcome: '选一张图片或一个文档,和问题一起发出' }}
          features={{
            attachments: {
              upload,
              mode: inline ? 'inline' : 'strip',
              maxTotalSize: 20 * 1024 * 1024,
            },
          }}
        />
      </div>
    </div>
  );
}
切换附件栏与内联混排,再上传、发送和编辑消息新窗口打开

这个示例只验证附件交互:小文件被转成 base64 内联资源,消息因此可以立即展示附件,但消息和文件只存在当前页面内存中,刷新后不会恢复。需要持久化、私有文件预览或模型读取时,见接入文件服务和 TOS。

用法

import type { ChatkitUploader } from '@volcengine/chatkit';

const upload: ChatkitUploader = (file, context) => uploadAttachment(file, context);

<Chatkit
  sessions={sessions}
  features={{
    attachments: {
      upload,
      mode: 'inline',
      maxTotalSize: 20 * 1024 * 1024,
    },
  }}
/>;

给了 upload 后,用户可以点输入框工具栏的附件按钮选文件,也可以把文件拖进来或粘贴进来。upload 需要返回完整的 AttachmentResource;上传到 TOS/S3、自有文件服务以及短期预览地址的实现见后端接入页。

模式输入与发送编辑消息
mode: 'strip'(默认)输入框上方单行附件栏;文件较多时横向滚动;发送后图片以紧凑方块展示,文字在最后附件只读且不可删除,只能修改文字
mode: 'inline'文件直接插入光标位置;发送后仍按正文顺序显示为附件 chip附件可拖拽移动、删除,文字与附件一起提交

草稿中已有附件时切换 mode 会同步迁移位置:切到 inline 后,附件栏内容插入当前光标处;切回 strip 后,正文附件收拢到附件栏。

从输入框之外添加附件:

const result = await useComposer().addAttachmentsAsync(items, {
  placement: 'inline', // 或 separate
});

结果包含逐项 accepted / rejected 和正文位置 ID。本地 File 仍会走 upload;已经上传的稳定资源需要开启 allowUploaded。

自定义附件菜单时,关闭默认按钮并把 ComposerAddAttachment 放进输入框工具栏插槽。items 可以包含文件选择、自定义操作和禁用项;keepMenuOpen 让自定义项点击后继续显示菜单。

import { Chatkit, ComposerAddAttachment } from '@volcengine/chatkit';

<Chatkit
  sessions={sessions}
  features={{ attachments: { upload, slot: false } }}
  slots={{
    'composer.toolbarStart': (
      <ComposerAddAttachment
        items={[
          { type: 'file' },
          { type: 'custom', key: 'from-drive', label: '从网盘选择', keepMenuOpen: true, onClick: openDrivePicker },
          { type: 'custom', key: 'locked', label: '暂不可用', disabled: true },
        ]}
      />
    ),
  }}
/>;
composer-controls.tsx
import '@volcengine/chatkit/styles.css';

import { Chatkit, ComposerAddAttachment, useChatRuntime } from '@volcengine/chatkit';
import { createMockAdapter } from '@volcengine/chatkit/adapters/mock';
import { useState } from 'react';

import { localUpload } from '../backends/local-upload';

const adapter = createMockAdapter();

export default function ComposerControls() {
  const runtime = useChatRuntime({ sessionId: 'composer-controls', adapter });
  const [customClicks, setCustomClicks] = useState(0);
  if (!runtime) return null;

  return (
    <div style={{ height: '100%', minHeight: 400, display: 'flex', flexDirection: 'column' }}>
      <p>自定义项点击次数:{customClicks}(点击后菜单保持打开)</p>
      <div style={{ flex: 1, minHeight: 0 }}>
        <Chatkit
          runtime={runtime}
          settings={{ composer: { sendTip: '发送消息(空草稿时也可查看提示)' } }}
          features={{
            attachments: {
              upload: localUpload,
              slot: false,
            },
          }}
          slots={{
            'composer.toolbarStart': (
              <ComposerAddAttachment
                items={[
                  { type: 'file' },
                  {
                    type: 'custom',
                    key: 'keep-open',
                    label: '计数并保持菜单打开',
                    keepMenuOpen: true,
                    onClick: () => setCustomClicks(count => count + 1),
                  },
                  { type: 'custom', key: 'disabled', label: '暂不可用', disabled: true },
                ]}
              />
            ),
          }}
        />
      </div>
    </div>
  );
}
自定义、禁用与保持打开的附件菜单新窗口打开

配置项

配置项类型默认说明
features.attachments.upload(file: File, context?: ChatkitAttachmentUploadContext) => Promise<UploadResult>—上传实现。
features.attachments.composerEnabledboolean—只配置历史附件展示时可关闭输入框上传。
features.attachments.mode"strip" | "inline"'strip'附件交互模式:`strip` 使用附件栏并在编辑消息时锁定附件;`inline` 在光标处插入附件, 发送后保持混排,编辑消息时可移动或删除附件。
features.attachments.allowUploadedboolean—允许程序化添加已上传资源,无需本地上传实现。
features.attachments.resolvePreviewUrlChatkitResolveAttachmentPreviewUrl—按需解析消息或已恢复 composer 附件的预览 URL。 1. `attachment.url` 可能是工作区路径或容器内路径,浏览器无法直接作为 `<img src>` 加载。 2. 接入方可在这里异步换成可访问 URL,例如经自己的文件服务读取后返回 Blob URL。 3. 返回 `revoke` 时 Chat UI 会在图片或附件卡片卸载、地址切换时调用,避免 Blob URL 泄漏。
features.attachments.unavailableReasonstring—上传不可用时向用户显示的原因。
features.attachments.maxTotalSizenumber5242880单次发送的附件总大小上限(字节)。
features.attachments.acceptUploadAccept—允许选择的文件类型,同 `<input accept>` 的写法;不给时用后端声明支持的类型。
features.attachments.onPreview(attachment: ChatkitMessageAttachment) => void—点了消息或输入框里的附件时调用,由接入方打开自己的预览。
features.attachments.slotfalse | ChatkitToolbarSlotName'composer.toolbarStart'附件按钮的位置;`false` 表示不自动放,由接入方自己摆。
features.attachments.actionsChatkitAttachmentAction[]—附件菜单里的快捷操作(标题 + 预设提示词):点了把提示词填进输入框,并触发 `onEvent` 的 `prompt_click`。
features.attachments.onEvent(event: ChatkitAttachmentEvent) => void—附件入口的交互事件:点了上传(`upload_click`)、点了快捷操作(`prompt_click`)。

说明

  • 允许的文件类型:不写 accept 时用后端声明支持的类型(适配器的 acceptedFileTypes);后端没有声明表示不限制。文档站 Mock 默认支持图片、PDF、文本、音视频、ZIP,以及 DOC/DOCX、XLS/XLSX、PPT/PPTX。
  • 大小上限:默认是一次发送合计 5 MiB(5 * 1024 * 1024 字节),不是每个文件 5 MiB;上例改为 20 MiB。类型和大小检查覆盖选择、拖拽、粘贴的本地文件,服务端仍需按自己的要求校验。
  • mode 统一默认上传位置、消息展示和编辑能力;不传时使用 strip。
  • 上传中、上传失败或正文中有缺失附件时不能发送,需等待上传完成、重试或移除。上传函数拒绝 Promise 表示失败。
  • 附件随发送输入交给适配器,怎么编进请求由适配器决定。
  • 完成资源必填 id/name/mimeType/size/source。source 是 URL、base64 内联数据或稳定资源引用;资源注入也检查类型、大小和数量。私有文件不持久化短期签名 URL。
  • upload 获得 signal、uploadId、draftId、sessionId 与进度回调;删除和释放取消请求。未配置附件功能时入口隐藏;配置附件对象但没有上传实现时,本地上传菜单禁用并提示原因。文件粘贴/拖拽显示原因,混合粘贴中的文字保留。
  • Chatkit 包装后的发送、编辑与插话,以及 onSend 改写的最终附件,均检查引用、类型、来源、数量和总大小。直接调用 Core 不读取界面额度,服务端仍需校验业务限制。
  • 私有资源通过 resolvePreviewUrl 按需换取可预览地址;适配器通过 resolveForSend 在真正请求模型时解析读取来源。完整契约见接入文件服务和 TOS。
  • 原始有序输入必须与资源一起保存,历史才能恢复附件位置。能在当前消息中看到附件,不代表刷新后仍能恢复;内联 base64、稳定资源引用与持久化的区别见为什么需要文件服务。
  • ComposerAddAttachment 必须渲染在 <Chatkit> 插槽内,才能读取该实例的上传、语言与弹层容器。聊天区外只需要文件按钮时,用同一 ChatkitProvider 下的 AttachmentButton,并保留已挂载的输入框,见外置组件接入。
  • 插槽菜单挂在所属 wrapper 内并继承实例主题;不要移动到 document.body。祖先的 transform、filter 或 contain 可能改变浮层定位。
  • 菜单打开后,方向键与 Home/End 在可用项间导航并跳过禁用项。附件菜单和图片预览按钮的焦点与强制颜色规则见主题页。
  • 文件选择器由浏览器和操作系统提供;结合目标设备检查选择、拖拽、粘贴、上传失败、预览及键盘或触摸操作。
  • 换附件按钮的位置见调整内置功能。
  • 特殊图片格式(HEIC、TIFF)不会自动转码,需要时在 upload 里自己转。