1515 <tr-welcome title =" AI助手" description =" 您好,我是您的开发小助手" :icon =" welcomeIcon" class =" robot-welcome" >
1616 </tr-welcome >
1717 <tr-prompts
18- :items =" promptItems"
18+ :items =" props. promptItems"
1919 :wrap =" true"
2020 item-class =" prompt-item"
2121 class =" tiny-prompts"
3333 ref =" senderRef"
3434 mode =" multiple"
3535 v-model =" inputMessage"
36- :placeholder =" GeneratingStatus.includes(status) ? '正在思考中...' : '请输入您的问题'"
36+ :placeholder =" GeneratingStatus.includes(props. status) ? '正在思考中...' : '请输入您的问题'"
3737 :clearable =" true"
38- :loading =" GeneratingStatus.includes(status)"
38+ :loading =" GeneratingStatus.includes(props. status)"
3939 :showWordLimit =" true"
4040 :maxLength =" 4000"
4141 @submit =" handleSendMessage"
4242 @cancel =" handleAbortRequest"
43- :allowFiles =" singleAttachmentItems .length < 1 && allowFiles"
43+ :allowFiles =" selectedAttachments .length < 1 && props. allowFiles"
4444 uploadTooltip =" 支持上传1张图片"
4545 @files-selected =" handleSingleFilesSelected"
4646 >
47- <template #header v-if =" singleAttachmentItems .length > 0 " >
47+ <template #header v-if =" selectedAttachments .length > 0 " >
4848 <div >
4949 <tr-attachments
5050 ref =" singleAttachmentRef"
51- v-model:items =" singleAttachmentItems "
51+ v-model:items =" selectedAttachments "
5252 variant =" card"
5353 wrap
54- @file-remove =" handleSingleFileRemove"
55- @file-retry =" handleSingleFileRetry"
54+ @retry =" handleSingleFileRetry"
5655 >
5756 </tr-attachments >
5857 </div >
6766</template >
6867
6968<script setup lang="ts">
70- import { ref , computed , h , resolveComponent , type Component , type CSSProperties , type PropType } from ' vue'
71- import { Notify } from ' @opentiny/vue'
69+ import { ref , computed , h , resolveComponent , type Component , type CSSProperties , type PropType , watch } from ' vue'
7270import {
7371 TrBubbleList ,
7472 TrBubbleProvider ,
@@ -78,12 +76,14 @@ import {
7876 TrWelcome ,
7977 TrAttachments ,
8078 type BubbleRoleConfig ,
81- type PromptProps
79+ type PromptProps ,
80+ type RawFileAttachment
8281} from ' @opentiny/tiny-robot'
8382import { type ChatMessage , GeneratingStatus } from ' @opentiny/tiny-robot-kit'
8483import { LoadingRenderer , MarkdownRenderer , ImgRenderer } from ' ../renderers'
84+ import { useNotify } from ' @opentiny/tiny-engine-meta-register'
8585
86- const { promptItems, status, promptClickHandler, allowFiles, bubbleRenderers, beforeSubmit } = defineProps ({
86+ const props = defineProps ({
8787 promptItems: {
8888 type: Array as PropType <PromptProps []>,
8989 default : () => []
@@ -108,71 +108,69 @@ const { promptItems, status, promptClickHandler, allowFiles, bubbleRenderers, be
108108
109109const emit = defineEmits ([' fileSelected' , ' sendMessage' , ' abort' ])
110110
111- const singleAttachmentItems = ref ([])
111+ const selectedAttachments = ref ([])
112112
113113const robotVisible = defineModel <boolean >(' show' , { required: true })
114114const fullscreen = defineModel <boolean >(' fullscreen' )
115115const inputMessage = defineModel <string >(' input' , { required: true })
116116const messages = defineModel <ChatMessage []>(' messages' , { required: true })
117117
118- const imageUrl = ref (' ' )
118+ watch (
119+ () => props .allowFiles ,
120+ (value ) => {
121+ if (! value ) {
122+ selectedAttachments .value = []
123+ }
124+ }
125+ )
119126
120127// 处理文件选择事件
121- const handleSingleFilesSelected = (files : FileList | null , retry = false ) => {
128+ const handleSingleFilesSelected = (files : File [] | null , retry = false ) => {
129+ if (! files ?.length ) return
122130 if (retry ) {
123- singleAttachmentItems . value [0 ]. status = ' uploading '
124- singleAttachmentItems . value [ 0 ]. isUploading = true
125- singleAttachmentItems . value [ 0 ]. messageType = ' uploading '
131+ Object . assign ( selectedAttachments . value [0 ], {
132+ status: ' uploading '
133+ })
126134 } else {
127- if (! files .length ) return
128-
129- if (files && files .length > 1 ) {
130- Notify ({
135+ if (files .length > 1 ) {
136+ useNotify ({
131137 type: ' error' ,
132- message: ' 当前仅支持上传一张图片' ,
133- position: ' top-right' ,
134- duration: 5000
138+ message: ' 当前仅支持上传一张图片'
135139 })
136140 return
137141 }
138142
139- if (files && files .length > 0 ) {
140- // 将选中的文件转换为 Attachment 格式并添加到附件列表
141- const newAttachments = Array .from (files ).map ((file ) => ({
142- size: file .size ,
143- rawFile: file
144- }))
145- singleAttachmentItems .value .push (... newAttachments )
146- }
143+ // 将选中的文件转换为 Attachment 格式并添加到附件列表
144+ const newAttachments = Array .from (files ).map ((file ) => ({
145+ size: file .size ,
146+ rawFile: file
147+ }))
148+ selectedAttachments .value .push (... newAttachments )
147149 }
148150
149151 // 开始上传
150152 const formData = new FormData ()
151- const fileData = retry ? files : files [0 ]
153+ const fileData = files [0 ]
152154 formData .append (' file' , fileData )
153155
154156 const updateAttachment = (resourceUrl : string ) => {
155157 if (resourceUrl ) {
156- singleAttachmentItems . value [0 ]. status = ' done '
157- singleAttachmentItems . value [ 0 ]. isUploading = false
158- singleAttachmentItems . value [ 0 ]. messageType = ' success '
159- singleAttachmentItems . value [ 0 ]. url = resourceUrl
158+ Object . assign ( selectedAttachments . value [0 ], {
159+ status: ' success ' ,
160+ url: resourceUrl
161+ })
160162 } else {
161- singleAttachmentItems . value [0 ]. status = ' error '
162- singleAttachmentItems . value [ 0 ]. isUploading = false
163- singleAttachmentItems . value [ 0 ]. messageType = ' error '
163+ Object . assign ( selectedAttachments . value [0 ], {
164+ status: ' error '
165+ })
164166 }
165167 }
166168
167169 emit (' fileSelected' , formData , updateAttachment )
168170}
169171
170- const handleSingleFileRemove = () => {
171- imageUrl .value = ' '
172- }
173-
174- const handleSingleFileRetry = (file : any ) => {
175- handleSingleFilesSelected (file .file , true )
172+ const handleSingleFileRetry = (file : RawFileAttachment ) => {
173+ handleSingleFilesSelected ([file .rawFile ], true )
176174}
177175
178176const getSvgIcon = (name : string , style ? : CSSProperties ) => {
@@ -185,7 +183,7 @@ const contentRenderers = computed(() => ({
185183 markdown: MarkdownRenderer ,
186184 loading: LoadingRenderer ,
187185 img: ImgRenderer ,
188- ... bubbleRenderers
186+ ... props . bubbleRenderers
189187}))
190188
191189const roles: Record <string , BubbleRoleConfig > = {
@@ -214,7 +212,7 @@ const handleSendMessage = async (content: string) => {
214212 return
215213 }
216214
217- let result = beforeSubmit ?.(content )
215+ let result = props . beforeSubmit ?.(content )
218216 if (result && typeof result .then === ' function' ) {
219217 result = await result
220218 }
@@ -226,7 +224,7 @@ const handleSendMessage = async (content: string) => {
226224 role: ' user' ,
227225 content: messageContent
228226 }
229- const files = singleAttachmentItems .value .filter ((item ) => item .status === ' done' )
227+ const files = selectedAttachments .value .filter ((item ) => item .status === ' done' )
230228 if (files .length > 0 ) {
231229 const fileMessages: ChatMessage [] = files .map ((file ) => ({
232230 role: ' user' ,
@@ -259,7 +257,7 @@ const handleSendMessage = async (content: string) => {
259257 }
260258 messages .value .push (userMessage )
261259 inputMessage .value = ' '
262- singleAttachmentItems .value = []
260+ selectedAttachments .value = []
263261 emit (' sendMessage' )
264262}
265263
@@ -268,8 +266,8 @@ const handleAbortRequest = () => {
268266}
269267
270268const handlePromptItemClick = (ev : unknown , item : { description? : string }) => {
271- if (promptClickHandler && typeof promptClickHandler === ' function' ) {
272- promptClickHandler (item )
269+ if (props . promptClickHandler && typeof props . promptClickHandler === ' function' ) {
270+ props . promptClickHandler (item )
273271 } else {
274272 handleSendMessage (item .description )
275273 }
0 commit comments