AIChatInput

基于富文本编辑器的 AI 聊天输入框,支持引用、附件、建议、技能与配置区。

设计文档(新窗口打开)

使用场景

在 AI 聊天场景下,用户可通过 AIChatInput 实现富文本输入、上传、引用、建议、模版、功能配置、及丰富自定义展示等需求。

AIChatInput 的富文本输入是基于 tiptap 实现,tiptap 是一款现代的富文本编辑器开发框架,具备极强的可定制性和扩展性。其组件化能力优秀,性能优良,内置多种常用拓展,并支持用户自定义节点、命令、插件与菜单,使复杂 AI 场景下的富文本输入能力能够灵活适配和扩展。本库的 AIChatInput 组件对 tiptap 进行了封装,开发者可开箱即用或按需按业务扩展。

代码演示

如何引入

import { AIChatInput } from '@chenzy-design/svelte';

基本用法

支持文本输入以及文件上传,使用时可按需配置以下参数:

  • uploadProps 配置文件上传相关的参数,详见 UploadProps
  • onUploadChange 获取文件上传变化
  • 删除上传文件时,会触发 uploadProps.onRemove,并遵循 uploadProps.beforeRemove(支持 Promise)
  • placeholder 输入框的占位符
  • defaultContent 输入框的默认内容
  • onContentChange 输入框内容变化时的回调函数,参数为当前输入框的内容

消息发送

sendHotKey 设置发送快捷键(enter / shift+enter);generating 为 true 时发送按钮变停止按钮,点击触发 onStopGenerate。

dy.jpeg
dy.jpeg
jpeg 130kb
resso.jpeg
resso.jpeg
jpeg 222kb

富文本输入区

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 配置自定义渲染内容相对于引用区域、上传展示区域的相对位置。

horizontalScroller.tsx
AIChatInput
web
recentChange
Branch Diff with Main Branch
From 1-2

自定义扩展

富文本区域可以自定义扩展,自定义扩展的实现可参考 Tiptap 自定义扩展。通过 extensions API 可将自定义扩展添加到 AIChatInput 组件中。如果添加了自定义扩展,需要在 transformer 中添加对应的转换规则,以保证在 onContentChange 中得到的该节点数据符合用户预期。

添加自定义扩展时有以下注意事项:

  • 请在自定义扩展中添加 isCustomSlot 的属性,该属性和自定义扩展前后的光标高度有关。
  • 由于 AIChatInput 使用 Enter 作为发送热键,如果自定义扩展有使用 Enter 作为快捷操作,需要自行设置 editor.storage 中的 CdAIChatInput.allowHotKeySend 用于表示热键是否应该被 AIChatInput 用于发送,避免热键冲突。

自定义扩展定义及注意事项的示例如下(@ 触发两级命令面板,选中条目插入自定义 referSlot 节点,renderTopSlot 结合 content 一并展示):

horizontalScroller.tsx
AIChatInput
web
recentChange
Branch Diff with Main Branch
From 1-2

isCustomSlot 属性可用 AIChatInput.getCustomSlotAttribute()(静态方法)生成,无需手写 parseHTML/renderHTML。

本库补充:Semi 官网文档页此处只有上面的 CustomRichTextExtension 一个 live demo;下面这个自定义粘贴规则示例并非文档页内容,而是照搬自 Semi 源码仓库 packages/semi-ui/aiChatInput/_story/docSlot.jsx(Semi Storybook 用的真实代码,用 nodePasteRule 让粘贴匹配正则的文档链接自动转换为自定义节点),补充在此处作为 extensions API 的另一种用法示例。




设置内容

用 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 时,是否清空输入框内容和附件booleantrue
defaultContent输入框默认内容,支持 html string 及 tiptap Contentstring''
extensions自定义扩展,类型同 tiptap 的 ExtensionExtension[][]
generating是否正在生成中booleanfalse
keepSkillAfterSend清空输入时是否保留已选技能标记;true 时走 setContentWhileSaveTool 而非整体清空booleanfalse
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建议/技能/模板浮层宽度是否跟随输入框宽度booleantrue
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自定义顶部 slotSnippet<[{ references, attachments, handleReferenceDelete, handleUploadFileDelete }]>-
renderUploadButton自定义底部操作区上传按钮 UI(内置上传/粘贴逻辑仍由组件托管)Snippet<[{ openFileDialog, disabled, attachments }]>-
round底部的配置区域和操作区域形状是否为全圆角booleantrue
sendHotKey发送输入内容的键盘快捷键,支持 enter、shift+enterstringenter
showPlaceholderWhenSkillOnly当仅选中技能(无其他内容)时是否显示 placeholder,开启后 placeholder 会显示在 skill 后方booleanfalse
showReference是否展示引用区域,用于配合 renderTopSlot 使用booleantrue
showTemplateButton是否展示模板按钮booleantrue
showUploadButton是否显示上传按钮booleantrue
showUploadFile是否展示上传文件区域,用于配合 renderTopSlot 使用booleantrue
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 的 propsobject-
uploadTipProps传了才给上传按钮包一层 Tooltip;透传 Tooltip propsTooltipProps-

与 Semi 的差异:

  • immediatelyRender 是 tiptap 在 React SSR 下的专用开关,本库 SSR 机制不同(tiptap 全程动态 import、仅客户端实例化),无需该参数。

Configure.Select

同 SelectProps。

Configure.Button

属性说明类型默认值
field绑定的配置字段名(发送时并入消息 setup)string-
initValue初始值(注册到配置区,不触发 onConfigureChange)boolean-
icon前置图标Snippet-
disabled禁用booleanfalse
onChange附加变更回调(value: boolean) => void-

Configure.RadioButton

同 RadioGroupProps。

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 条-第 1 条,共 1 条
  • 1