使用场景
在 AI 聊天场景下,用户可通过 AIChatInput 实现富文本输入、上传、引用、建议、模版、功能配置、及丰富自定义展示等需求。
AIChatInput 的富文本输入是基于 tiptap 实现,tiptap 是一款现代的富文本编辑器开发框架,具备极强的可定制性和扩展性。其组件化能力优秀,性能优良,内置多种常用拓展,并支持用户自定义节点、命令、插件与菜单,使复杂 AI 场景下的富文本输入能力能够灵活适配和扩展。本库的 AIChatInput 组件对 tiptap 进行了封装,开发者可开箱即用或按需按业务扩展。
代码演示
如何引入
import { AIChatInput } from '@chenzy-design/svelte'; 基本用法
支持文本输入以及文件上传,使用时可按需配置以下参数:
uploadProps配置文件上传相关的参数,详见 UploadPropsonUploadChange获取文件上传变化- 删除上传文件时,会触发
uploadProps.onRemove,并遵循uploadProps.beforeRemove(支持 Promise) placeholder输入框的占位符defaultContent输入框的默认内容onContentChange输入框内容变化时的回调函数,参数为当前输入框的内容
消息发送
sendHotKey 设置发送快捷键(enter / shift+enter);generating 为 true 时发送按钮变停止按钮,点击触发 onStopGenerate。
富文本输入区
AIChatInput 使用 tiptap 作为富文本输入框的编辑器,用户可以在输入框中输入文本,使用 AIChatInput 内置的 extensions(包括 input-slot,select-slot,skill-slot)。用户也可以自定义 extensions 来扩展编辑器的功能。
input-slot支持用户输入文本,并支持 placeholder 占位符。select-slot支持用户进行简单的选择,选项仅支持 string 类型。skill-slot是用于技能展示的块,方便用户理解当前输入框中的技能。
可以通过 ref 方法 setContent 来设置输入框的内容,使用 focusEditor 方法可以将输入框的焦点设置到编辑器中。
tiptap 内核体积较大,本库全程动态 import,不进主 bundle——只有真正渲染 AIChatInput 时才会加载。
引用
通过 references 传入引用列表,renderReference 自定义单条渲染,onReferenceClick / onReferenceDelete 接收交互。
配置区域
通过 renderConfigureArea 自定义底部配置区,内部放 AIChatInput.Configure.Select / Button / Mcp / RadioButton(对齐 Semi AIChatInput.Configure 命名空间,也可从包直接具名导入 AIChatInputConfigureSelect 等);其值在发送时并入消息的 setup 字段,变更经 onConfigureChange 通知。
发送时的 setup:{}
如果有其他形式的配置需求,可以用 getConfigureItem 把任意受控组件(如 Cascader)接入配置区 context——与 Semi getConfigureItem(Component, opts) 同构的运行时 HOC:Svelte 5 组件本质是 (internals, props) => Exports 的函数,工厂函数返回一个新组件,原样转发 internals 给传入的组件,自己只负责合并 field/initValue/value/onChange 等 props。另有 AIChatInput.Configure.Item(render-prop 形态):模板里就地包装用它更顺手,不必先在 <script> 里调用工厂函数生成组件;两者按场景二选一。
发送时的 setup:{}
操作区域
输入框右下角为操作区域,用户可以通过 renderActionArea 自定义操作区域,展示自定义的操作按钮。
interface ActionAreaProps {
/** 默认的操作按钮组(上传 + 发送/停止),渲染它即可保留内置能力 */
menuItem: Snippet;
/** 默认容器类名,需自行挂到根节点上 */
className: string;
} 自定义上传按钮
底部操作区左侧默认会渲染上传按钮。你可以通过 renderUploadButton 仅自定义按钮 UI(例如改成图标按钮、加 Tooltip 等)。
注意:这不会影响上传/粘贴上传逻辑(Upload 仍由组件内部托管),openFileDialog 会触发内部 Upload 的文件选择。
底部按钮形状
用户可以通过 round API 配置底部按钮的形状,默认是 true,是圆角按钮,可以设置为 false 来配置为方形按钮。
建议
通过 suggestions 传入建议列表,聚焦空输入区时弹出;renderSuggestionItem 自定义单条渲染,onSuggestClick 接收选中。
技能及模版
通过 skills 传入技能列表,空输入区按 skillHotKey(默认 /)弹出面板;选中后插入技能槽节点。当前技能 hasTemplate 时展示模版按钮,点击弹出 renderTemplate。
默认情况下选中技能后 placeholder 即消失。开启 showPlaceholderWhenSkillOnly 后,「只选了技能、尚未输入正文」时 placeholder 仍然显示,并排在技能后方。
showPlaceholderWhenSkillOnly(选中技能后占位符仍在,显示在技能后方)
默认行为(选中技能后占位符消失)
自定义渲染顶部区域
通过 renderTopSlot 自定义渲染顶部区域,可自行渲染引用、上传内容以及配置项。可结合 showReference 和 showUploadFile 控制是否展示内置的引用与上传文件区域。另外,可通过 topSlotPosition 配置自定义渲染内容相对于引用区域、上传展示区域的相对位置。
自定义扩展
富文本区域可以自定义扩展,自定义扩展的实现可参考 Tiptap 自定义扩展。通过 extensions API 可将自定义扩展添加到 AIChatInput 组件中。如果添加了自定义扩展,需要在 transformer 中添加对应的转换规则,以保证在 onContentChange 中得到的该节点数据符合用户预期。
添加自定义扩展时有以下注意事项:
- 请在自定义扩展中添加
isCustomSlot的属性,该属性和自定义扩展前后的光标高度有关。 - 由于
AIChatInput使用Enter作为发送热键,如果自定义扩展有使用Enter作为快捷操作,需要自行设置editor.storage中的CdAIChatInput.allowHotKeySend用于表示热键是否应该被 AIChatInput 用于发送,避免热键冲突。
自定义扩展定义及注意事项的示例如下(@ 触发两级命令面板,选中条目插入自定义 referSlot 节点,renderTopSlot 结合 content 一并展示):
isCustomSlot 属性可用 AIChatInput.getCustomSlotAttribute()(静态方法)生成,无需手写 parseHTML/renderHTML。
本库补充:Semi 官网文档页此处只有上面的
CustomRichTextExtension一个 live demo;下面这个自定义粘贴规则示例并非文档页内容,而是照搬自 Semi 源码仓库packages/semi-ui/aiChatInput/_story/docSlot.jsx(Semi Storybook 用的真实代码,用nodePasteRule让粘贴匹配正则的文档链接自动转换为自定义节点),补充在此处作为extensionsAPI 的另一种用法示例。
设置内容
用 setContent 设置编辑器内容(可包含 <skill-slot> 等自定义节点);后续想保留已选技能、只改正文,用 setContentWhileSaveTool。getEditor() 取 tiptap Editor 实例,可读 getHTML()/getJSON()。
保留技能标记
keepSkillAfterSend 开启后,generating 由 false 变 true 时清空输入走 setContentWhileSaveTool(保留已选技能标记)而非整体 clearContent。
接入对话
本库补充:Semi 该页无此段。AIChatInput 与 AIChatDialogue 组合是最常见的落地形态, 单看输入框不易看出两者如何联动,故补一个可运行示例。
与 AIChatDialogue 组合使用,构成完整的对话界面。
API 参考
AIChatInput
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| canSend | 是否可以发送,未设置时根据输入框内容、上传内容、引用内容决定 | boolean | - |
| class | 自定义类名 | string | - |
| clearContentOnGenerating | 当 generating 从 false 变为 true 时,是否清空输入框内容和附件 | boolean | true |
| defaultContent | 输入框默认内容,支持 html string 及 tiptap Content | string | '' |
| extensions | 自定义扩展,类型同 tiptap 的 Extension | Extension[] | [] |
| generating | 是否正在生成中 | boolean | false |
| keepSkillAfterSend | 清空输入时是否保留已选技能标记;true 时走 setContentWhileSaveTool 而非整体清空 | boolean | false |
| onBlur | 富文本输入框失焦的回调 | (event: FocusEvent) => void | - |
| onConfigureChange | 配置区域发生变化的回调 | (value, changedValue) => void | - |
| onContentChange | 输入框内容变化时候的回调 | (payload) => void | - |
| onFocus | 富文本输入框聚焦的回调 | (event: FocusEvent) => void | - |
| onMessageSend | 发送消息回调 | (message: { references?, attachments?, inputContents?, setup? }) => void | - |
| onPaste | 监听输入框粘贴事件(携带粘贴的文件,不阻止默认粘贴行为) | (files: File[]) => void | - |
| onReferenceClick | 引用点击回调 | (reference) => void | - |
| onReferenceDelete | 引用删除回调 | (reference) => void | - |
| onSkillChange | 技能切换回调 | (skill) => void | - |
| onStopGenerate | 停止生成回调 | () => void | - |
| onSuggestClick | 建议点击回调 | (suggestion) => void | - |
| onTemplateVisibleChange | 模板弹出层可见性变化回调 | (visible: boolean) => void | - |
| onUploadChange | 上传文件相关回调 | (attachments) => void | - |
| dropdownMatchTriggerWidth | 建议/技能/模板浮层宽度是否跟随输入框宽度 | boolean | true |
| placeholder | 输入框占位符(对齐 Semi:无内置默认文案) | string | - |
| popoverProps | 承载建议/技能/模板浮层的 Popover 配置(trigger 固定 custom,显隐由组件驱动) | PopoverProps | - |
| references | 输入框引用列表 | AIChatInputReference[] | [] |
| renderActionArea | 自定义底部的操作区域(整块替换,menuItem 为默认的上传/发送按钮组,className 需自行挂到根节点) | Snippet<[{ menuItem, className }]> | - |
| renderConfigureArea | 自定义底部的配置区域 | Snippet | - |
| renderReference | 自定义渲染引用 | Snippet<[reference]> | - |
| renderSkillItem | 自定义技能列表的 item 渲染(整项替换,需自行渲染根节点并挂 className/onClick/onMouseEnter) | Snippet<[{ skill, className, onClick, onMouseEnter }]> | - |
| renderSuggestionItem | 自定义建议列表的 item 渲染(整项替换,需自行渲染根节点并挂 className/onClick/onMouseEnter) | Snippet<[{ suggestion, className, onClick, onMouseEnter }]> | - |
| renderTemplate | 自定义模板渲染 | Snippet<[{ skill, setContent }]> | - |
| renderTopSlot | 自定义顶部 slot | Snippet<[{ references, attachments, handleReferenceDelete, handleUploadFileDelete }]> | - |
| renderUploadButton | 自定义底部操作区上传按钮 UI(内置上传/粘贴逻辑仍由组件托管) | Snippet<[{ openFileDialog, disabled, attachments }]> | - |
| round | 底部的配置区域和操作区域形状是否为全圆角 | boolean | true |
| sendHotKey | 发送输入内容的键盘快捷键,支持 enter、shift+enter | string | enter |
| showPlaceholderWhenSkillOnly | 当仅选中技能(无其他内容)时是否显示 placeholder,开启后 placeholder 会显示在 skill 后方 | boolean | false |
| showReference | 是否展示引用区域,用于配合 renderTopSlot 使用 | boolean | true |
| showTemplateButton | 是否展示模板按钮 | boolean | true |
| showUploadButton | 是否显示上传按钮 | boolean | true |
| showUploadFile | 是否展示上传文件区域,用于配合 renderTopSlot 使用 | boolean | true |
| skillHotKey | 输入框中触发技能的热键 | string | / |
| skills | 技能列表 | AIChatInputSkill[] | [] |
| style | 自定义样式 | string | - |
| suggestions | 建议列表 | AIChatInputSuggestion[] | [] |
| topSlotPosition | 自定义顶部内容相对引用/上传区域的位置 | 'top' \| 'middle' \| 'bottom' | top |
| templatesCls | 模版浮层的附加类名 | string | - |
| templatesStyle | 模版浮层的附加内联样式 | string | - |
| transformer | 富文本节点归一覆盖(配合自定义 extensions) | Map<string, (node) => Content> | - |
| uploadProps | 透传给内部 Upload 的 props | object | - |
| uploadTipProps | 传了才给上传按钮包一层 Tooltip;透传 Tooltip props | TooltipProps | - |
与 Semi 的差异:
immediatelyRender是 tiptap 在 React SSR 下的专用开关,本库 SSR 机制不同(tiptap 全程动态 import、仅客户端实例化),无需该参数。
Configure.Select
同 SelectProps。
Configure.Button
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| field | 绑定的配置字段名(发送时并入消息 setup) | string | - |
| initValue | 初始值(注册到配置区,不触发 onConfigureChange) | boolean | - |
| icon | 前置图标 | Snippet | - |
| disabled | 禁用 | boolean | false |
| onChange | 附加变更回调 | (value: boolean) => void | - |
Configure.RadioButton
Configure.Item
通用配置字段包装(render-prop 形态),把任意受控组件接入配置区 context——模板里就地包装,不必先调用工厂函数生成新组件。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| field | 绑定的配置字段名(发送时并入消息 setup) | string | - |
| initValue | 初始值(注册到配置区,不触发 onConfigureChange) | unknown | - |
| children | 渲染受控组件,参数提供当前 value 与 onChange 写回 | Snippet<[{ value, onChange }]> | - |
getConfigureItem
function getConfigureItem<P>(
Inner: Component<P>,
opts?: {
valueKey?: string; // 内层组件读取当前值用的 prop 名,默认 'value'
onKeyChangeFnName?: string; // 内层组件变更回调 prop 名,默认 'onChange'
valuePath?: string; // onChange 回调参数里取值的路径,如 'target.value'
className?: string; // 追加类名(与调用方传入的 class 合并)
defaultProps?: Partial<P>; // 内层组件默认 props
},
): Component<P & { field: string; initValue?: unknown; onChange?: (value: unknown) => void; class?: string }>; 工厂函数(对齐 Semi getConfigureItem):接收任意 Svelte 组件,返回一个新组件,多接受 field/initValue/onChange/class 四个 props,其余透传给内层组件;写回逻辑同 Configure.Item。用于需要复用(如在多处 renderConfigureArea 里用同一个包装组件)的场景。
Configure.Mcp
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| field | 绑定的配置字段名(发送时并入消息 setup) | string | - |
| initValue | 初始已选的 MCP value 数组 | string[] | - |
| onChange | 选中项变化回调 | (selected: string[]) => void | - |
| options | 可选 MCP 服务列表 | McpOption[] | [] |
本库配置区各项统一由
field绑定到配置对象(值经onConfigureChange上抛、 发送时并入setup),故用field+initValue,而非 Semi 的受控value/defaultValue。
AIChatInput.getCustomSlotAttribute
function getCustomSlotAttribute(): TiptapAttributeConfig; 静态方法:生成 tiptap 自定义节点的 isCustomSlot 属性描述,供用户自定义扩展接入编辑区光标/零宽字符处理逻辑(见「自定义扩展」章节)。用法:AIChatInput.getCustomSlotAttribute(),放进自定义 Node.create({ addAttributes() { return { isCustomSlot: AIChatInput.getCustomSlotAttribute() } } })。也可从包具名导入 getCustomSlotAttribute。
Methods
通过 bind:this 获取组件实例后调用(对齐 Semi 的 ref 方法)。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| changeTemplateVisible | 切换模板弹出层的可见性 | (visible: boolean) => void | - |
| clearContent | 清空输入框内容 | () => void | - |
| deleteContent | 删除富文本中的某一项 | (content: Content) => void | - |
| deleteUploadFile | 删除上传文件中的某一项 | (item: Attachment) => void | - |
| focusEditor | 聚焦输入框,默认聚焦到输入框末尾 | () => void | - |
| getConfigureValue | 获取当前配置区的值 | () => AIChatInputConfigureValue | - |
| getEditor | 获取当前 tiptap 的 editor 实例 | () => Editor \| undefined | - |
| getHTML | 获取输入框内容的 HTML 字符串 | () => string | - |
| getText | 获取输入框内容的纯文本 | () => string | - |
| setContent | 设置输入框内容 | (content: string) => void | - |
| setContentWhileSaveTool | 保留技能项的同时设置输入框内容 | (content: string) => void | - |
Semi 的 7 个方法本库全部具备;
clearContent/getText/getHTML/getConfigureValue为本库补充(Svelte 无getEditor()之外的通用取值途径,这几个更顺手)。
设计变量
变量 | 默认值 | 用法 |
|---|---|---|
| --cd-ai-chat-input-motion-duration | var(--cd-motion-duration-fast) | 描边/按钮悬浮过渡时长 |
- 1