@@ -130,13 +130,13 @@ import {
130130 TrPrompts ,
131131 TrBubbleList ,
132132 TrSender ,
133- TrFeedback ,
134133 TrAttachments ,
135134 TrBubbleProvider
136135} from ' @opentiny/tiny-robot'
137136import type { BubbleRoleConfig , PromptProps } from ' @opentiny/tiny-robot'
138137import { IconNewSession } from ' @opentiny/tiny-robot-svgs'
139138import SchemaRenderer from ' @opentiny/tiny-schema-renderer'
139+ import { utils } from ' @opentiny/tiny-engine-utils'
140140import RobotSettingPopover from ' ./RobotSettingPopover.vue'
141141import {
142142 getBlockContent ,
@@ -151,7 +151,7 @@ import {
151151} from ' ./js/robotSetting'
152152import { PROMPTS } from ' ./js/prompts'
153153import * as jsonpatch from ' fast-json-patch'
154- import { chatStream } from ' ./js/utils'
154+ import { chatStream , checkComponentNameExists } from ' ./js/utils'
155155import McpServer from ' ./mcp/McpServer.vue'
156156import useMcpServer from ' ./mcp/useMcp'
157157import MarkdownRenderer from ' ./mcp/MarkdownRenderer.vue'
@@ -162,6 +162,7 @@ import RobotTypeSelect from './RobotTypeSelect.vue'
162162import McpIconComponent from ' ./icon-prompt/mcp-icon.vue'
163163import PageIconComponent from ' ./icon-prompt/page-icon.vue'
164164import StudyIconComponent from ' ./icon-prompt/study-icon.vue'
165+ import { jsonrepair } from ' jsonrepair'
165166
166167export default {
167168 components: {
@@ -208,10 +209,11 @@ export default {
208209 const showPreview = ref (false )
209210 const singleAttachmentItems = ref ([])
210211 const imageUrl = ref (' ' )
211- const MESSAGE_TIP = ' 已生成新的页面效果,请点击下方按钮应用schema '
212+ const MESSAGE_TIP = ' 已生成新的页面效果。 '
212213 const aiType = ref (TALK_TYPE )
213214 const chatContainerRef = ref (null )
214215 const showTeleport = ref (false )
216+ const { deepClone, string2Obj, reactiveObj2String : obj2String } = utils
215217 const sleep = (delay ) => new Promise ((resolve ) => setTimeout (resolve , delay ))
216218 watchEffect (() => {
217219 avatarUrl .value = ' img/defaultAvator.png'
@@ -296,19 +298,20 @@ export default {
296298 id
297299 })
298300
299- const setSchema = ( ) => {
301+ const setSchema = async ( schema : any ) => {
300302 const value = {
301303 ... pageState .pageSchema ,
302- ... currentSchema . value ,
304+ ... schema ,
303305 componentName: pageState .pageSchema .componentName
304306 }
305307 importSchema (value )
306308 setSaved (false )
307309 showPreview .value = false
310+ await nextTick ()
308311 }
309312
310313 // 处理响应
311- const handleResponse = ({ id , chatMessage }: { id: string ; chatMessage: any }) => {
314+ const handleResponse = ({ id , chatMessage }: { id: string ; chatMessage: any }, currentJson ) => {
312315 try {
313316 if (aiType .value === BUILD_TYPE ) {
314317 const regex = / ```json([\s\S ] *? )```/
@@ -317,7 +320,7 @@ export default {
317320 if (match && match [1 ] && JSON .parse (match [1 ]) && isValidFastJsonPatch (JSON .parse (match [1 ]))) {
318321 const newValue = JSON .parse (match [1 ])
319322 // 使用 applyPatch 修改 Schema
320- const result = newValue .reduce (jsonpatch .applyReducer , pageState . pageSchema )
323+ const result = newValue .reduce (jsonpatch .applyReducer , currentJson )
321324
322325 sessionProcess .messages .push (getAiRespMessage (JSON .stringify (result , null , 2 ), chatMessage .role ))
323326 sessionProcess .displayMessages .push (getAiDisplayMessage (MESSAGE_TIP , chatMessage .role , result , id ))
@@ -385,6 +388,9 @@ export default {
385388
386389 let streamContent = ' '
387390 const chatId = Date .now ().toString ()
391+ const currentJson = deepClone (pageState .pageSchema )
392+ let lastExecutionTime = 0
393+ const throttleDelay = 3000
388394 await chatStream (
389395 {
390396 requestUrl: ' /app-center/api/ai/chat' ,
@@ -402,6 +408,24 @@ export default {
402408 }
403409 streamContent += choice .delta .content
404410 messages .value [messages .value .length - 1 ].content += choice .delta .content
411+ const currentTime = Date .now ()
412+ if (currentTime - lastExecutionTime > throttleDelay ) {
413+ try {
414+ const repaired = jsonrepair (streamContent )
415+ const parsedJson = JSON .parse (repaired )
416+ const result = parsedJson .reduce ((acc , patch ) => {
417+ return jsonpatch .applyPatch (acc , [patch ], false , false ).newDocument
418+ }, currentJson )
419+ const editorValue = string2Obj (obj2String (result ))
420+
421+ if (editorValue && checkComponentNameExists (result )) {
422+ setSchema (result )
423+ }
424+ } catch (error ) {
425+ // error
426+ }
427+ lastExecutionTime = currentTime
428+ }
405429 }
406430 },
407431 onError : (error ) => {
@@ -413,14 +437,17 @@ export default {
413437 console .error (' Stream error:' , error )
414438 },
415439 onDone : () => {
416- handleResponse ({
417- id: chatId ,
418- chatMessage: {
419- role: ' assistant' ,
420- content: streamContent || ' 没有返回内容' ,
421- name: ' AI'
422- }
423- })
440+ handleResponse (
441+ {
442+ id: chatId ,
443+ chatMessage: {
444+ role: ' assistant' ,
445+ content: streamContent || ' 没有返回内容' ,
446+ name: ' AI'
447+ }
448+ },
449+ currentJson
450+ )
424451 }
425452 },
426453 {
@@ -645,21 +672,13 @@ export default {
645672 sendContent (item .description , true )
646673 }
647674
648- const getItemSchema = (item ) => {
649- const targetMessage = messages .value .find ((message ) => message .id && message .id === item .id )
650-
651- return targetMessage
652- }
653-
654675 // Icon
655676 const getSvgIcon = (name : string , style ? : CSSProperties ) => {
656677 return h (resolveComponent (' svg-icon' ), { name , style: { fontSize: ' 32px' , ... style } })
657678 }
658679 const aiAvatar = getSvgIcon (' AI' )
659680 const userAvatar = getSvgIcon (' user-head' , { color: ' #dfe1e6' })
660681 const welcomeIcon = getSvgIcon (' AI' , { fontSize: ' 48px' })
661- const saveIcon = getSvgIcon (' save' , { fontSize: ' 20px' })
662- const previewIcon = getSvgIcon (' preview' , { fontSize: ' 20px' })
663682
664683 // 处理文件选择事件
665684 const handleSingleFilesSelected = (files : FileList | null , retry = false ) => {
@@ -742,29 +761,7 @@ export default {
742761 avatar: aiAvatar ,
743762 maxWidth: ' 90%' ,
744763 contentRenderer: MarkdownRenderer ,
745- customContentField: ' renderContent' ,
746- slots: {
747- footer : ({ bubbleProps }) => {
748- return h (TrFeedback , {
749- style: {
750- display: getItemSchema (bubbleProps )?.schema && aiType .value === BUILD_TYPE ? ' block' : ' none'
751- },
752- actions: [
753- { name: ' run' , label: ' 应用' , icon: saveIcon },
754- { name: ' preview' , label: ' 预览' , icon: previewIcon }
755- ],
756- onAction(name ) {
757- currentSchema .value = getItemSchema (bubbleProps )?.schema || {}
758- if (name === ' preview' ) {
759- showPreview .value = true
760- }
761- if (name === ' run' ) {
762- setSchema ()
763- }
764- }
765- })
766- }
767- }
764+ customContentField: ' renderContent'
768765 },
769766 user: { placement: ' end' , avatar: userAvatar , maxWidth: ' 90%' , contentRenderer: MarkdownRenderer },
770767 system: { hidden: true }
0 commit comments