2222 @item-click =" handlePromptItemClick"
2323 ></tr-prompts >
2424 </div >
25- <tr-bubble-provider v-else :content-renderers = " contentRenderers " >
26- <tr-bubble-list :items =" messages" :roles = " roles " auto-scroll class =" robot-bubble-list" > </tr-bubble-list >
25+ <tr-bubble-provider v-else :box-rules = " boxRules " : content-rules = " contentRules " >
26+ <tr-bubble-list :messages =" messages" :role-configs = " roleConfigs " auto-scroll class =" robot-bubble-list" > </tr-bubble-list >
2727 </tr-bubble-provider >
2828 </div >
2929
3939 :showWordLimit =" false"
4040 @submit =" handleSendMessage"
4141 @cancel =" handleAbortRequest"
42- :allowFiles =" selectedAttachments.length < 1 && props.allowFiles"
43- uploadTooltip =" 支持上传1张图片"
44- @files-selected =" handleSingleFilesSelected"
4542 >
4643 <template #header v-if =" selectedAttachments .length > 0 " >
4744 <div >
5552 </tr-attachments >
5653 </div >
5754 </template >
58- <template #footer-left >
55+ <template #footer >
5956 <slot name =" footer-left" ></slot >
6057 </template >
58+ <template #footer-right >
59+ <VoiceButton
60+ :speech-config =" { lang: 'zh-CN', continuous: false }"
61+ @speech-start =" handleSpeechStart"
62+ @speech-end =" handleSpeechEnd"
63+ @speech-error =" handleSpeechError"
64+ />
65+ <UploadButton
66+ v-if =" selectedAttachments.length < 1 && props.allowFiles"
67+ accept =" image/*"
68+ :multiple =" false"
69+ @select =" handleSingleFilesSelected"
70+ />
71+ </template >
6172 </tr-sender >
6273 </div >
6374 </template >
@@ -74,11 +85,14 @@ import {
7485 TrSender ,
7586 TrWelcome ,
7687 TrAttachments ,
88+ UploadButton ,
89+ VoiceButton ,
7790 type BubbleRoleConfig ,
7891 type PromptProps ,
7992 type RawFileAttachment
8093} from ' @opentiny/tiny-robot'
81- import { type ChatMessage , GeneratingStatus } from ' @opentiny/tiny-robot-kit'
94+ import { type ChatMessage } from ' @opentiny/tiny-robot-kit'
95+ import { GeneratingStatus } from ' ../../constants/status'
8296import { LoadingRenderer , MarkdownRenderer , ImgRenderer } from ' ../renderers'
8397import { useNotify } from ' @opentiny/tiny-engine-meta-register'
8498
@@ -172,30 +186,72 @@ const handleSingleFileRetry = (file: RawFileAttachment) => {
172186 handleSingleFilesSelected ([file .rawFile ], true )
173187}
174188
189+ // 语音输入处理
190+ const handleSpeechStart = () => {
191+ }
192+
193+ const handleSpeechEnd = (transcript : string ) => {
194+ if (transcript ) {
195+ inputMessage .value = transcript
196+ }
197+ }
198+
199+ const handleSpeechError = () => {
200+ useNotify ({
201+ type: ' error' ,
202+ message: ' 语音识别失败,请重试'
203+ })
204+ }
205+
175206const getSvgIcon = (name : string , style ? : CSSProperties ) => {
176207 return h (resolveComponent (' svg-icon' ), { name , style: { fontSize: ' 32px' , ... style } })
177208}
178209const aiAvatar = getSvgIcon (' AI' )
179210const welcomeIcon = getSvgIcon (' AI' , { fontSize: ' 48px' })
180211
181- const contentRenderers = computed (() => ({
182- markdown: MarkdownRenderer ,
183- loading: LoadingRenderer ,
184- img: ImgRenderer ,
185- ... props .bubbleRenderers
186- }))
212+ // const contentRenderers = computed(() => ({
213+ // markdown: MarkdownRenderer,
214+ // loading: LoadingRenderer,
215+ // img: ImgRenderer,
216+ // ...props.bubbleRenderers
217+ // }))
218+
219+ // 0.4.x 使用 match rules 配置渲染器
220+ const contentRules = computed (() => [
221+ {
222+ priority: 100 ,
223+ find : (message : any ) => message ?.renderContent ?.[0 ]?.type === ' markdown' || message ?.renderContent ?.[0 ]?.type === ' text' ,
224+ renderer: MarkdownRenderer
225+ },
226+ {
227+ priority: 100 ,
228+ find : (message : any ) => message ?.renderContent ?.[0 ]?.type === ' img' || message ?.renderContent ?.[0 ]?.type === ' image' ,
229+ renderer: ImgRenderer
230+ },
231+ {
232+ priority: 100 ,
233+ find : (message : any ) => message ?.renderContent ?.[0 ]?.type === ' loading' || message ?.renderContent ?.[0 ]?.type === ' agent-loading' ,
234+ renderer: LoadingRenderer
235+ },
236+ // 支持自定义渲染器
237+ ... Object .entries (props .bubbleRenderers ).map (([type , renderer ]) => ({
238+ priority: 50 ,
239+ find : (message : any ) => message ?.renderContent ?.[0 ]?.type === type ,
240+ renderer: renderer as any
241+ }))
242+ ])
243+
244+ const boxRules = computed (() => [])
187245
188- const roles : Record <string , BubbleRoleConfig > = {
246+ const roleConfigs : Record <string , BubbleRoleConfig > = {
189247 assistant: {
190248 placement: ' start' ,
191249 avatar: aiAvatar ,
192- contentRenderer: MarkdownRenderer ,
193- customContentField: ' renderContent'
250+ contentResolver : (message : any ) => message .renderContent || message .content
194251 },
195252 user: {
196253 placement: ' end' ,
197- contentRenderer: MarkdownRenderer ,
198- customContentField: ' renderContent'
254+ contentResolver : (message : any ) => message .renderContent || message .content
199255 },
200256 system: {
201257 hidden: true
0 commit comments