Appearance
Sender 消息输入框
重大版本升级 v0.4.0
Sender 在 v0.4.0 进行了重大升级。
从 v0.3.0 升级?选择你的迁移方式:
方式一:快速迁移(推荐) 🚀
- 使用
SenderCompat组件,保持大部分 API 兼容 - 修改导入语句 + 处理少量破坏性变更
- 👉 查看 SenderCompat 快速迁移指南
方式二:完全升级 📖
新项目: 直接使用下方 v0.4.0 的 API 和示例即可。
Sender 是一个高度可组合的聊天输入组件,支持文本输入、自动联想、提及功能、模板填充、语音输入和文件上传等多种功能。
代码示例
输入模式
Sender 支持单行和多行两种输入模式,通过 mode 属性控制。
单行模式自动切换
在单行模式下,当输入内容超出宽度时,会自动切换为多行模式。
当 submitType="enter" 时,按 Ctrl+Enter 或 Shift+Enter 也会自动切换为多行模式并换行。
状态控制
通过 loading 和 disabled 属性控制组件状态。加载状态下可点击图标取消操作。
内容管理
字数限制
通过 maxLength 和 showWordLimit 属性实现字数限制和统计。
超出限制行为
超出字数限制时,不会自动截断内容,但会以红色标示真实字数,且无法提交。
输入增强
Sender 采用可插拔的扩展架构,通过 extensions prop 灵活添加功能。所有扩展都支持响应式数据自动同步。
扩展使用
提供两种集成方式:
import { TrSender } from '@opentiny/tiny-robot'
// 便捷函数(推荐)
TrSender.mention(mentions, '@')
TrSender.suggestion(suggestions) // 不过滤
TrSender.suggestion(suggestions, { filterFn: customFilter }) // 自定义过滤
TrSender.template(templates)
// 标准配置(用于复杂场景)
TrSender.Mention.configure({ items: mentions, char: '@', allowSpaces: false })
TrSender.Suggestion.configure({ items: suggestions, filterFn: customFilter })模板编辑
使用 Template 扩展实现模板填充功能,支持动态设置模板内容,光标自动聚焦到第一个可编辑字段。
响应式数据
通过 items 配置项传入响应式 ref,模板数据变化时会自动更新编辑器内容。
配置详见:扩展属性 - Template
提及功能
使用 Mention 扩展实现 @提及功能,输入触发字符(默认 @)触发提及选择,快速引用预设的助手或对象,支持键盘导航和搜索过滤。
自定义触发字符
支持自定义触发字符,例如使用 # 代替 @。配置 char: '#' 后,输入 # 即可触发提及列表,选中后显示为 #标签名 的格式。
删除提及
按 Backspace 删除提及项时会保留触发字符(如 @ 或 #),可继续选择其他项。
配置详见:扩展属性 - Mention
智能联想
使用 Suggestion 扩展实现智能联想功能,支持键盘导航(↑↓ 选择,Enter 确认)和自动补全提示。
自动补全提示
选中建议项时,输入框会以灰色文本显示剩余部分,并显示 "TAB" 提示,按 Tab 键快速应用补全。
基础用法
不传 filterFn 时,直接显示所有建议项,不做任何过滤。
自定义过滤
通过 filterFn 自定义过滤逻辑,实现模糊匹配、前缀匹配等。
高亮模式
支持三种高亮模式,满足不同的使用场景:
- 自动匹配:不设置
highlights,自动高亮与输入内容匹配的部分 - 精确指定:通过
highlights数组精确指定需要高亮的文本片段 - 自定义函数:通过
highlights函数完全控制高亮逻辑,实现复杂的高亮规则
配置详见:扩展属性 - Suggestion
语音输入
通过 VoiceButton 组件实现语音输入功能,支持浏览器内置语音识别和第三方语音识别服务。
组件化设计
语音输入功能通过独立的 VoiceButton 组件实现,可按需添加到 footer 插槽中,无需额外配置。
基础语音识别
使用浏览器内置的语音识别功能,支持混合输入和连续识别两种模式。
自定义语音服务
支持集成第三方语音识别服务(如阿里云、百度、Azure 等)。
参考实现
speechHandlers.ts 提供了阿里云一句话识别和实时识别的完整示例,包括录音处理、API 调用、流式识别等。
自定义录音 UI
支持完全自定义语音录制界面,适用于移动端按住说话等场景。
配置详见:VoiceButton 属性
按钮配置
默认按钮配置
通过 defaultActions 属性统一配置默认按钮(Clear、Submit)的状态和提示。
增强按钮
通过插槽添加增强按钮(Upload、Voice 等),每个按钮都有独立的配置。
配置详见:UploadButton 属性、VoiceButton 属性
交互定制
取消操作
在 loading 状态下,点击停止按钮会触发 cancel 事件,用于取消正在进行的操作(如 AI 响应)。
提交方式
通过 submitType 属性控制提交快捷键,支持 enter、ctrlEnter、shiftEnter 三种方式。
快捷键参考
| 快捷键 | 功能 | 适用条件 |
|---|---|---|
| Enter | 提交内容 / 换行 | submitType="enter" |
| Ctrl+Enter | 提交内容 / 换行 | submitType="ctrlEnter" / submitType="enter" |
| Shift+Enter | 提交内容 / 换行 | submitType="shiftEnter" / submitType="enter" |
| Tab | 选中联想项 | 联想开启时 |
| Esc | 关闭联想 | 联想开启时 |
| ↑ / ↓ | 导航联想项 | 联想开启时 |
换行与提交行为说明
submitType="enter"时:按Enter提交,按Ctrl+Enter或Shift+Enter换行submitType="ctrlEnter"时:按Ctrl+Enter提交,按Enter换行submitType="shiftEnter"时:按Shift+Enter提交,按Enter换行
在单行模式下使用换行快捷键时,会自动切换为多行模式。
自定义选中按键
通过 activeSuggestionKeys 可自定义选中联想项的按键。默认支持 Enter 和 Tab。
自定义插槽
Sender 提供了多个插槽位置,方便扩展功能:
header- 顶部区域,可添加标题、提示信息等prefix- 输入框前缀区域,可添加图标、标签等(位于输入框内部)footer- 底部左侧区域,可添加功能按钮footer-right- 底部右侧区域,可添加操作按钮
插槽作用域
footer 和 footer-right 插槽提供了作用域数据,包括 editor、hasContent、disabled、loading 等状态,以及 focus、insert、append、replace 等操作方法,可用于实现自定义功能按钮。
方法调用
样式配置
主题支持
主题继承
主题会根据父级 ThemeProvider 的配置自动继承,无需重复设置。
组件尺寸
通过 size 属性控制组件尺寸,支持 normal(默认)和 small(紧凑)两种尺寸。
Props
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| modelValue | 绑定值(v-model) | string | '' |
| defaultValue | 默认值(非响应式) | string | '' |
| placeholder | 输入框占位文本 | string | '请输入内容...' |
| mode | 输入模式 | 'single' | 'multiple' | 'single' |
| size 0.4.0 | 组件尺寸 | 'normal' | 'small' | 'normal' |
| disabled | 是否禁用 | boolean | false |
| loading | 是否加载中 | boolean | false |
| autofocus | 自动获取焦点 | boolean | false |
| autoSize | 自动调整高度 | boolean | { minRows: number, maxRows: number } | { minRows: 1, maxRows: 5 } |
| clearable | 是否可清空 | boolean | false |
| maxLength | 最大输入长度 | number | Infinity |
| showWordLimit | 是否显示字数统计 | boolean | false |
| submitType | 提交方式 | 'enter' | 'ctrlEnter' | 'shiftEnter' | 'enter' |
| stopText | 停止按钮文字 | string | '停止响应' |
| defaultActions 0.4.0 | 默认操作按钮配置 | DefaultActions | undefined |
| extensions 0.4.0 | 扩展列表 (Template, Mention, Suggestion 等) | Extension[] | [] |
扩展系统
使用 extensions 属性配置功能扩展,提供灵活的配置和完整的类型支持。
Template
模板填充功能扩展,支持动态设置模板内容。
// 便捷函数
TrSender.template(templates)
// 标准配置
TrSender.Template.configure({ items: templates })| 配置项 | 类型 | 说明 |
|---|---|---|
items | TemplateItem[] | Ref<TemplateItem[]> | 模板数据列表 |
Mention
@提及功能扩展,支持快速引用预设的助手或对象,支持自定义触发字符。
// 便捷函数(使用默认 '@' 触发)
TrSender.mention(mentions)
// 便捷函数(自定义触发字符)
TrSender.mention(mentions, '#') // 使用 '#' 触发
// 标准配置
TrSender.Mention.configure({ items: mentions, char: '@', allowSpaces: false })| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
items | MentionItem[] | Ref<MentionItem[]> | [] | 提及项列表,支持响应式 ref |
char | string | '@' | 触发字符,支持任意字符(如 '@'、'#'、'!' 等) |
allowSpaces | boolean | false | 是否允许在触发字符后输入空格 |
onSelect | Function | - | 选中提及项时的回调函数 |
Suggestion
智能联想功能扩展,支持自动过滤、自定义过滤和多种高亮方式。
// 便捷函数
TrSender.suggestion(suggestions) // 不过滤,显示所有项
TrSender.suggestion(suggestions, { filterFn: customFilter }) // 自定义过滤
// 标准配置
TrSender.Suggestion.configure({
items: suggestions,
filterFn: (items, query) => items.filter((item) => item.content.includes(query)),
showAutoComplete: true,
})| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
items | SuggestionItem[] | Ref<SuggestionItem[]> | [] | 建议项列表 |
filterFn | Function | undefined | 过滤函数(不传则不过滤) |
showAutoComplete | boolean | true | 自动补全 |
activeSuggestionKeys | string[] | ['Enter'] | 激活按键 |
popupWidth | number | string | 400 | 弹窗宽度 |
onSelect | (item) => void | false | - | 选中回调,返回 false 阻止默认回填 |
popupWidth 格式
支持数字(如 500)、百分比(如 '100%')、CSS 单位(如 '20rem')
高亮方式:
{ content: 'ECS-云服务器' } // 自动匹配
{ content: 'RDS-数据库', highlights: ['RDS', '数据库'] } // 精确指定
{ content: 'OSS-存储', highlights: (text, query) => [...] } // 自定义函数onSelect 回调:
选中建议项时触发,返回 false 可阻止默认回填行为:
// 默认行为:自动回填
onSelect: (item) => {
console.log('Selected:', item)
// 不返回 false,内容会自动回填到编辑器
}
// 阻止默认回填并自定义
onSelect: (item) => {
editor.commands.setContent(`前缀-${item.content}-后缀`)
return false // 阻止默认回填
}
// 条件性阻止
onSelect: (item) => {
if (item.data?.needsValidation) {
validateAndFill(item)
return false
}
// 否则使用默认回填
}回调参数
item 包含完整的 SuggestionItem 信息(content、label、data、highlights),可用于业务逻辑处理。
UploadButton
文件上传按钮组件,支持文件类型过滤、大小限制和数量限制。
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| disabled | 是否禁用 | boolean | false |
| accept | 接受的文件类型 | string | '*' |
| multiple | 是否支持多选 | boolean | false |
| reset | 选择后是否重置 input | boolean | true |
| maxSize | 文件大小限制(MB) | number | - |
| maxCount | 最大文件数量 | number | - |
| tooltip | Tooltip | TooltipContent | - |
| tooltipPlacement | Tooltip 位置 | TooltipPlacement | 'top' |
| icon | 自定义图标 | VNode | Component | IconUpload |
| size | 按钮尺寸 | number | string | 32 |
VoiceButton
语音输入按钮组件,支持浏览器内置语音识别和第三方语音识别服务。
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| icon | 自定义图标 | VNode | Component | IconVoice |
| disabled | 是否禁用 | boolean | false |
| size | 按钮尺寸 | 'small' | 'normal' | 'normal' |
| tooltip | Tooltip | TooltipContent | - |
| tooltipPlacement | Tooltip 位置 | TooltipPlacement | 'top' |
| speechConfig | 语音配置 | SpeechConfig | - |
| autoInsert | 是否自动插入识别结果到编辑器 | boolean | true |
| onButtonClick | 按钮点击拦截器 | Function | - |
Slots
| 插槽名称 | 描述 | 作用域参数 |
|---|---|---|
| header | 头部插槽,位于输入框上方 | - |
| prefix | 前缀插槽,位于输入框左侧 | - |
| content 0.4.0 | 内容插槽,用于完全自定义编辑器内容 | { editor } |
| actions-inline 0.4.0 | 单行模式下的操作按钮区域 | - |
| footer | 底部自定义区域 | { editor, hasContent, disabled, loading } |
| footer-right | 底部右侧区域 | - |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 内容更新 | (value: string) |
| submit | 提交内容 | (text: string, data?: StructuredData) |
| clear | 清空内容 | () |
| focus | 获得焦点 | (event: FocusEvent) |
| blur | 失去焦点 | (event: FocusEvent) |
| input | 输入变化 | (value: string) |
| cancel 0.4.0 | 在 loading 状态下点击停止按钮时触发,用于取消正在进行的操作(如 AI 响应) | () |
UploadButton Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| select | 文件选择成功 | (files: File[]) |
| error | 文件验证失败 | (error: Error, file?: File) |
VoiceButton Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| speech-start | 开始录音 | () |
| speech-interim | 中间结果 | (transcript: string) |
| speech-final | 最终结果 | (transcript: string) |
| speech-end | 结束录音 | (transcript?: string) |
| speech-error | 识别错误 | (error: Error) |
Methods
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| focus | 使输入框获取焦点 | - | void |
| blur | 使输入框失去焦点 | - | void |
| clear | 清空输入内容 | - | void |
| submit | 手动触发提交 | - | void |
| setContent 0.4.0 | 设置编辑器内容 | (content: string) | void |
| getContent 0.4.0 | 获取编辑器内容 | - | string |
| cancel 0.4.0 | 手动触发取消 | - | void |
UploadButton Methods
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| open | 打开文件选择器 | - | void |
VoiceButton Methods
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| start | 开始录音 | - | void |
| stop | 停止录音 | - | void |
Types
// DefaultActions 默认按钮配置
interface DefaultActions {
submit?: {
disabled?: boolean // 是否禁用提交按钮
tooltip?: string // 提交按钮提示文本
tooltipPlacement?: TooltipPlacement // Tooltip 位置
}
clear?: {
disabled?: boolean // 是否禁用清空按钮
tooltip?: string // 清空按钮提示文本
tooltipPlacement?: TooltipPlacement // Tooltip 位置
}
}
// ToolTip 内容
type TooltipContent = string | (() => string | VNode)
// Tooltip 位置
type TooltipPlacement =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'
// SpeechConfig 语音配置
interface SpeechConfig {
customHandler?: SpeechHandler // 自定义语音处理器
lang?: string // 识别语言,默认浏览器语言
continuous?: boolean // 是否持续识别
interimResults?: boolean // 是否返回中间结果
autoReplace?: boolean // 是否自动替换内容
onVoiceButtonClick?: (isRecording, preventDefault) => void // 按钮点击拦截器
}
// 模板项(联合类型)
type TemplateItem =
| {
id?: string // 模板 ID(可选,组件会自动生成)
type: 'text' // 类型:普通文本
content: string // 内容
}
| {
id?: string // 模板 ID(可选,组件会自动生成)
type: 'block' // 类型:模板块(可编辑)
content: string // 内容
}
| {
id?: string // 模板 ID(可选,组件会自动生成)
type: 'select' // 类型:选择器
content: string // 内容(选中的值)
placeholder?: string // 占位文字(仅用于输入配置)
options?: SelectOption[] // 选项列表(仅用于输入配置)
value?: string // 当前选中的值(仅用于输入配置)
}
// 选择器选项
interface SelectOption {
label: string // 显示文本
value: string // 选择后的值
}
// 提及项(输入配置)
interface MentionItem {
label: string // 显示名称,如 "小小画家"
value: string // 关联值
}
// 提及项(输出结构)
type MentionStructuredItem =
| { type: 'text', content: string }
| { type: 'mention', content: string, value: string }
// 建议项
interface SuggestionItem {
content: string // 建议项内容(必填)
highlights?: string[] | HighlightFunction // 高亮方式(可选)
}
// 高亮函数类型
type HighlightFunction = (suggestionText: string, inputText: string) => SuggestionTextPart[]
// 高亮文本片段
interface SuggestionTextPart {
text: string // 文本片段
isMatch: boolean // 是否高亮
}
// 结构化数据(submit 事件返回)
type StructuredData = TemplateItem[] | MentionStructuredItem[]
// 输入模式
type InputMode = 'single' | 'multiple'
// 扩展类型
import type { Extension } from '@tiptap/core'CSS 变量
Sender 组件提供了丰富的 CSS 变量用于自定义样式。
基础颜色
| 变量名 | 说明 |
|---|---|
--tr-sender-bg-color | 背景颜色 |
--tr-sender-text-color | 文本颜色 |
--tr-sender-placeholder-color | 占位符颜色 |
--tr-sender-button-hover-bg | 按钮悬停背景 |
尺寸和间距
| 变量名 | 说明 |
|---|---|
--tr-sender-font-size | 字体大小 |
--tr-sender-line-height | 行高 |
--tr-sender-border-radius | 圆角大小 |
--tr-sender-padding | 内边距 |
--tr-sender-gap | 元素间距 |
--tr-sender-footer-gap | 底部元素间距 |
Header 区域
| 变量名 | 说明 |
|---|---|
--tr-sender-header-padding | 头部内边距 |
--tr-sender-header-divider-inset | 头部分割线缩进 |
--tr-sender-multi-main-padding | 多行模式主区域内边距 |
Footer 区域
| 变量名 | 说明 |
|---|---|
--tr-sender-footer-padding | 底部内边距 |
前缀和操作区
| 变量名 | 说明 |
|---|---|
--tr-sender-prefix-padding-right | 前缀区域右内边距 |
--tr-sender-actions-padding-right | 操作区域右内边距 |
按钮
| 变量名 | 说明 |
|---|---|
--tr-sender-button-size | 按钮尺寸 |
--tr-sender-button-size-submit | 提交按钮尺寸 |
尺寸变体
所有变量都支持通过 size 属性自动切换。当 size="small" 时,组件会使用对应的 -small 变体(如 --tr-sender-font-size-small)。
使用示例
/* 自定义背景色 */
.my-sender {
--tr-sender-bg-color: #f5f5f5;
--tr-sender-text-color: #333;
}
/* 自定义按钮尺寸 */
.my-sender {
--tr-sender-button-size: 40px;
--tr-sender-button-size-submit: 44px;
}已移除的 API
以下 API 在 v0.4.0 中已被移除,请参考替代方案进行迁移。
Props
| 属性名 | 原说明 | 替代方案 |
|---|---|---|
| allowSpeech | 是否开启语音输入 | 使用 VoiceButton 组件 |
| speech | 语音识别配置 | 使用 VoiceButton.speechConfig |
| allowFiles | 是否允许文件上传 | 使用 UploadButton 组件 |
| buttonGroup | 按钮组配置 | 使用 defaultActions 和插槽 |
| theme | 主题样式 | 使用 ThemeProvider 包裹 |
| suggestions | 输入建议列表 | 使用 Suggestion 扩展 |
| suggestionPopupWidth | 建议弹窗宽度 | 使用 Suggestion 扩展配置 |
| activeSuggestionKeys | 激活建议项的按键 | 使用 Suggestion 扩展配置 |
| templateData | 模板数据 | 使用 Template 扩展 |
Slots
| 插槽名称 | 替代方案 |
|---|---|
| actions | 改用 actions-inline |
| footer-left | 改用 footer |
| decorativeContent | 改用 disabled + content |
Events
| 事件名 | 替代方案 |
|---|---|
| change | 使用 blur 事件 |
| files-selected | 使用 UploadButton 的 select 事件 |
| speech-start | 使用 VoiceButton 的 speech-start 事件 |
| speech-end | 使用 VoiceButton 的 speech-end 事件 |
| speech-interim | 使用 VoiceButton 的 speech-interim 事件 |
| speech-error | 使用 VoiceButton 的 speech-error 事件 |
| suggestion-select | 使用 Suggestion 扩展的 onSelect 回调 |
Methods
| 方法名 | 替代方案 |
|---|---|
| startSpeech | 使用 VoiceButton.start() |
| stopSpeech | 使用 VoiceButton.stop() |
| activateTemplateFirstField | 自动处理,无需调用 |