8282
8383<script lang="ts">
8484import { ref , onMounted , watchEffect , type CSSProperties , h , resolveComponent } from ' vue'
85- import { Notify , Loading , TinyPopover , TinyDialogBox , TinyButton } from ' @opentiny/vue'
85+ import { Notify , Loading , TinyPopover , TinyDialogBox } from ' @opentiny/vue'
8686import { useCanvas , usePage , useModal , getMetaApi , META_SERVICE } from ' @opentiny/tiny-engine-meta-register'
87- import {
88- TrContainer ,
89- TrWelcome ,
90- TrPrompts ,
91- TrBubbleList ,
92- TrSender ,
93- TrFeedback ,
94- TrAttachments
95- } from ' @opentiny/tiny-robot'
87+ import { TrContainer , TrWelcome , TrBubbleList , TrSender , TrFeedback , TrAttachments } from ' @opentiny/tiny-robot'
9688import { IconNewSession } from ' @opentiny/tiny-robot-svgs'
9789import SchemaRenderer from ' @opentiny/tiny-schema-renderer'
9890import RobotSettingPopover from ' ./RobotSettingPopover.vue'
@@ -105,19 +97,17 @@ import {
10597} from ' ./js/robotSetting'
10698import { PROMPTS } from ' ./js/prompts'
10799import * as jsonpatch from ' fast-json-patch'
100+ import { chatStream } from ' ./js/utils'
108101
109102export default {
110103 components: {
111- TinyPopover ,
112- TinyDialogBox ,
113- TinyButton ,
104+ TinyPopover: TinyPopover as unknown ,
105+ TinyDialogBox: TinyDialogBox as unknown ,
114106 RobotSettingPopover ,
115107 TrContainer ,
116108 TrWelcome ,
117- TrPrompts ,
118109 TrBubbleList ,
119110 TrSender ,
120- TrFeedback ,
121111 TrAttachments ,
122112 IconNewSession ,
123113 SchemaRenderer
@@ -215,7 +205,7 @@ export default {
215205 showPreview .value = false
216206 }
217207
218- const fixedJson = (op ) => {
208+ const _fixedJson = (op ) => {
219209 // 修正 path:替换 ; 和 : 为 /
220210 if (op .path ) {
221211 op .path = op .path .replace (/ [;:] / g , ' /' ).replace (/ \/ + / g , ' /' )
@@ -235,55 +225,109 @@ export default {
235225 op .value = { [key .trim ()]: val .trim () }
236226 }
237227 } catch (e ) {
228+ // eslint-disable-next-line no-console
238229 console .warn (' Failed to parse style:' , op .value )
239230 }
240231 }
241232
242233 return op
243234 }
244235
245- const sendRequest = () => {
246- getMetaApi (META_SERVICE .Http )
247- .post (' /app-center/api/ai/chat' , getSendSeesionProcess (), { timeout: 600000 })
248- .then ((res : any ) => {
249- const { choices } = res
250- const chatMessage = choices [0 ]?.message
251- try {
252- const regex = / ```json([\s\S ] *? )```/
253- const match = chatMessage ?.content .match (regex )
254-
255- if (match && match [1 ] && JSON .parse (match [1 ]) && isValidFastJsonPatch (JSON .parse (match [1 ]))) {
256- const newValue = JSON .parse (match [1 ])
257- // 使用 applyPatch 修改 Schema
258- const result = newValue .reduce (jsonpatch .applyReducer , pageState .pageSchema )
259-
260- sessionProcess .messages .push (
261- getAiRespMessage (JSON .stringify (pageState .pageSchema , null , 2 ), chatMessage .role )
262- )
263- sessionProcess .displayMessages .push (getAiDisplayMessage (MESSAGE_TIP , chatMessage .role , result , res .id ))
264- messages .value [messages .value .length - 1 ].content = MESSAGE_TIP
265- messages .value [messages .value .length - 1 ].schema = result
266- messages .value [messages .value .length - 1 ].id = res .id
267- } else {
268- sessionProcess .messages .push (getAiRespMessage (chatMessage ?.content ))
269- sessionProcess .displayMessages .push (getAiRespMessage (chatMessage ?.content ))
270- messages .value [messages .value .length - 1 ].content = chatMessage ?.content
236+ // 处理响应
237+ const handleResponse = (res : { id: string ; chatMessage: any }) => {
238+ try {
239+ const regex = / ```json([\s\S ] *? )```/
240+ const match = chatMessage ?.content .match (regex )
241+
242+ if (match && match [1 ] && JSON .parse (match [1 ]) && isValidFastJsonPatch (JSON .parse (match [1 ]))) {
243+ const newValue = JSON .parse (match [1 ])
244+ // 使用 applyPatch 修改 Schema
245+ const result = newValue .reduce (jsonpatch .applyReducer , pageState .pageSchema )
246+
247+ sessionProcess .messages .push (
248+ getAiRespMessage (JSON .stringify (pageState .pageSchema , null , 2 ), chatMessage .role )
249+ )
250+ sessionProcess .displayMessages .push (getAiDisplayMessage (MESSAGE_TIP , chatMessage .role , result , res .id ))
251+ messages .value [messages .value .length - 1 ].content = MESSAGE_TIP
252+ messages .value [messages .value .length - 1 ].schema = result
253+ messages .value [messages .value .length - 1 ].id = res .id
254+ } else {
255+ sessionProcess .messages .push (getAiRespMessage (chatMessage ?.content ))
256+ sessionProcess .displayMessages .push (getAiRespMessage (chatMessage ?.content ))
257+ messages .value [messages .value .length - 1 ].content = chatMessage ?.content
258+ }
259+ setContextSession ()
260+ inProcesing .value = false
261+ connectedFailed .value = false
262+ } catch (e ) {
263+ messages .value [messages .value .length - 1 ].content = ' 处理响应时出错'
264+ inProcesing .value = false
265+ connectedFailed .value = false
266+ }
267+ }
268+
269+ // 发送流式请求
270+ const _sendStreamRequest = async () => {
271+ const requestData = getSendSeesionProcess ()
272+ if (requestData .foundationModel ) {
273+ requestData .foundationModel .stream = true
274+ }
275+
276+ let streamContent = ' '
277+ const chatId = Date .now ().toString ()
278+ await chatStream (
279+ {
280+ requestUrl: ' /app-center/api/ai/chat' ,
281+ requestData
282+ },
283+ {
284+ onData : (data ) => {
285+ const choice = data .choices ?.[0 ]
286+ if (choice && choice .delta .content ) {
287+ if (messages .value .length === 0 || messages .value [messages .value .length - 1 ].role !== ' assistant' ) {
288+ messages .value .push (getAiDisplayMessage (' ' , ' assistant' , {}, chatId ))
289+ }
290+ if (streamContent !== messages .value [messages .value .length - 1 ].content ) {
291+ messages .value [messages .value .length - 1 ].content = ' '
292+ }
293+ streamContent += choice .delta .content
294+ messages .value [messages .value .length - 1 ].content += choice .delta .content
271295 }
272- setContextSession ()
273- inProcesing .value = false
274- connectedFailed .value = false
275- } catch (e ) {
276- messages .value [messages .value .length - 1 ].content = ' 处理响应时出错'
296+ },
297+ onError : (error ) => {
298+ messages .value [messages .value .length - 1 ].content = ' 连接失败'
299+ localStorage .removeItem (' aiChat' )
277300 inProcesing .value = false
278301 connectedFailed .value = false
302+ // eslint-disable-next-line no-console
303+ console .error (' Stream error:' , error )
304+ },
305+ onDone : () => {
306+ handleResponse ({
307+ id: chatId ,
308+ chatMessage: {
309+ role: ' assistant' ,
310+ content: streamContent || ' 没有返回内容' ,
311+ name: ' AI'
312+ }
313+ })
279314 }
315+ }
316+ )
317+ }
318+
319+ const sendRequest = async () => {
320+ try {
321+ const res: any = await getMetaApi (META_SERVICE .Http ).post (' /app-center/api/ai/chat' , getSendSeesionProcess (), {
322+ timeout: 600000
280323 })
281- .catch (() => {
282- messages .value [messages .value .length - 1 ].content = ' 连接失败'
283- localStorage .removeItem (' aiChat' )
284- inProcesing .value = false
285- connectedFailed .value = false
286- })
324+ handleResponse ({ id: res .id , chatMessage: res .choices [0 ].message })
325+ } catch (error ) {
326+ messages .value [messages .value .length - 1 ].content = ' 连接失败'
327+ localStorage .removeItem (' aiChat' )
328+ inProcesing .value = false
329+ connectedFailed .value = false
330+ }
287331 }
288332
289333 const scrollContent = async () => {
@@ -387,7 +431,7 @@ export default {
387431 await sleep (1000 )
388432 messages .value .push (getAiDisplayMessage (' 好的,正在执行相关操作,请稍等片刻...' ))
389433 await scrollContent ()
390- sendRequest ()
434+ await sendRequest ()
391435 }
392436 }
393437
@@ -505,6 +549,10 @@ export default {
505549 placement: ' start' ,
506550 avatar: aiAvatar ,
507551 maxWidth: ' 80%' ,
552+ type: ' markdown' ,
553+ mdConfig: {
554+ breaks: true
555+ },
508556 slots: {
509557 footer : ({ bubbleProps }) => {
510558 return h (TrFeedback , {
@@ -528,7 +576,15 @@ export default {
528576 }
529577 }
530578 },
531- user: { placement: ' end' , avatar: userAvatar , maxWidth: ' 80%' }
579+ user: {
580+ placement: ' end' ,
581+ avatar: userAvatar ,
582+ maxWidth: ' 80%' ,
583+ type: ' markdown' ,
584+ mdConfig: {
585+ breaks: true
586+ }
587+ }
532588 })
533589
534590 // 处理文件选择事件
@@ -588,6 +644,7 @@ export default {
588644 }
589645 })
590646 } catch (error ) {
647+ // eslint-disable-next-line no-console
591648 console .error (' 上传失败' , error )
592649 }
593650 }
0 commit comments