Skip to content

Commit acb3ffd

Browse files
committed
fix:fix review
1 parent 4b6a9ac commit acb3ffd

3 files changed

Lines changed: 83 additions & 69 deletions

File tree

packages/canvas/DesignCanvas/src/api/useCanvas.ts

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const rootSchema = ref([
8787
])
8888

8989
// 初始化单个节点的AI状态(使用独立的aiNodesStatus,避免与nodesStatus可见性冲突)
90-
const initializeNodeAIStatus = (node: object, initialStatus: Partial<NodeAIStatus> = {}) => {
90+
const initializeNodeAIStatus = (node: Node, initialStatus: Partial<NodeAIStatus> = {}) => {
9191
pageState.aiNodesStatus[node.id] = {
9292
state: 'hidden',
9393
originalNodeData: deepClone(node),
@@ -211,14 +211,25 @@ const jsonDiffPatchInstance = jsonDiffPatch.create({
211211
const { publish } = useMessage()
212212

213213
// 重置画布数据
214-
const resetCanvasState = async (state: Partial<PageState> = {}) => {
214+
// preserveAINodeStatus: 为true时保留aiNodesStatus并为新增节点补初始化(适用于AI/robot等schema热更新场景)
215+
const resetCanvasState = async (state: Partial<PageState> = {}, options?: { preserveAINodeStatus?: boolean }) => {
215216
const previousSchema = JSON.parse(JSON.stringify(pageState.pageSchema))
217+
const preserveAINodeStatus = options?.preserveAINodeStatus ?? false
218+
219+
// 保留旧aiNodesStatus快照,用于后续diff补初始化
220+
const oldAINodesStatus = preserveAINodeStatus ? { ...pageState.aiNodesStatus } : null
216221

217222
Object.assign(pageState, defaultPageState, state)
218223

219224
nodesMap.value.clear()
220-
// 切换页面时清空所有节点的AI状态,避免旧页面的AI状态残留
221-
pageState.aiNodesStatus = {}
225+
226+
if (preserveAINodeStatus) {
227+
// 保留aiNodesStatus,后续只为新增节点补初始化
228+
pageState.aiNodesStatus = oldAINodesStatus
229+
} else {
230+
// 切换页面时清空所有节点的AI状态,避免旧页面的AI状态残留
231+
pageState.aiNodesStatus = {}
232+
}
222233

223234
if (pageState.pageSchema) {
224235
if (!pageState.pageSchema.children) {
@@ -238,8 +249,17 @@ const resetCanvasState = async (state: Partial<PageState> = {}) => {
238249

239250
generateNodesMap(pageState.pageSchema.children, pageState.pageSchema)
240251

241-
// 初始化所有节点的AI状态
242-
initializeAllNodesAIStatus()
252+
if (preserveAINodeStatus) {
253+
// 为新增的节点初始化AI状态(已存在的不覆盖)
254+
nodesMap.value.forEach(({ node }) => {
255+
if (node.id && !pageState.aiNodesStatus[node.id]) {
256+
initializeNodeAIStatus(node)
257+
}
258+
})
259+
} else {
260+
// 初始化所有节点的AI状态
261+
initializeAllNodesAIStatus()
262+
}
243263
}
244264

245265
const diffPatch = jsonDiffPatchInstance.diff(previousSchema, pageState.pageSchema)
@@ -248,40 +268,9 @@ const resetCanvasState = async (state: Partial<PageState> = {}) => {
248268
publish({ topic: 'schemaImport', data: { current: pageState.pageSchema, previous: previousSchema, diffPatch } })
249269
}
250270

251-
// 更新页面schema,保留AI状态(不清空nodesStatus
271+
// 更新页面schema,保留AI状态(委托resetCanvasState + preserveAINodeStatus
252272
const updatePageSchema = (newPageSchema: any) => {
253-
const previousSchema = JSON.parse(JSON.stringify(pageState.pageSchema))
254-
255-
pageState.pageSchema = newPageSchema
256-
257-
if (!newPageSchema.children) {
258-
newPageSchema.children = []
259-
}
260-
261-
rootSchema.value = [
262-
{
263-
id: 0,
264-
componentName: 'div',
265-
props: newPageSchema.props || {},
266-
children: newPageSchema.children
267-
}
268-
]
269-
270-
// 重建 nodesMap
271-
nodesMap.value.clear()
272-
nodesMap.value.set(0, { node: rootSchema.value, parent: newPageSchema })
273-
generateNodesMap(newPageSchema.children, newPageSchema)
274-
275-
// 为新增的节点初始化AI状态(已存在的不覆盖)
276-
nodesMap.value.forEach(({ node }) => {
277-
if (node.id && !pageState.aiNodesStatus[node.id]) {
278-
initializeNodeAIStatus(node)
279-
}
280-
})
281-
282-
const diffPatch = jsonDiffPatchInstance.diff(previousSchema, newPageSchema)
283-
284-
publish({ topic: 'schemaImport', data: { current: newPageSchema, previous: previousSchema, diffPatch } })
273+
resetCanvasState({ ...pageState, pageSchema: newPageSchema }, { preserveAINodeStatus: true })
285274
}
286275

287276
// 页面重置画布数据
@@ -693,7 +682,7 @@ const patchLatestSchema = (schema: unknown) => {
693682
}
694683
}
695684

696-
const importSchema = (data: any) => {
685+
const importSchema = (data: any, options?: { preserveAINodeStatus?: boolean }) => {
697686
let importData = data
698687

699688
if (typeof data === 'string') {
@@ -705,11 +694,7 @@ const importSchema = (data: any) => {
705694
}
706695
}
707696

708-
// JSON 格式校验
709-
resetCanvasState({
710-
...pageState,
711-
pageSchema: importData
712-
})
697+
resetCanvasState({ ...pageState, pageSchema: importData }, options)
713698
}
714699

715700
const exportSchema = () => {

packages/canvas/container/src/components/CanvasAction.vue

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ export default {
262262
263263
// AI请求的AbortController,用于取消正在进行的请求
264264
let aiChatAbortController = null
265+
// 递增token,用于防止并发请求的响应竞争
266+
let aiChatRequestToken = 0
265267
266268
const remove = () => {
267269
removeNodeById(getCurrent().schema?.id)
@@ -378,14 +380,20 @@ export default {
378380
return shouldShowNodeAILoading(currentSchema.id)
379381
})
380382
381-
const showAIPopover = computed(() => {
382-
const currentSchema = getCurrent().schema
383-
if (!currentSchema?.id) {
384-
return false
383+
const showAIPopover = ref(false)
384+
watch(
385+
() => {
386+
const currentSchema = getCurrent().schema
387+
if (!currentSchema?.id) {
388+
return false
389+
}
390+
const status = getNodeAIStatus(currentSchema?.id)
391+
return status?.state !== 'hidden' && !status?.collapsed
392+
},
393+
(val) => {
394+
showAIPopover.value = val
385395
}
386-
const status = getNodeAIStatus(currentSchema?.id)
387-
return status?.state !== 'hidden' && !status?.collapsed
388-
})
396+
)
389397
390398
// 切换AI助手显示/隐藏
391399
const openAIHelper = () => {
@@ -781,7 +789,6 @@ export default {
781789
if (!currentSchema?.id) {
782790
return
783791
}
784-
const params = await buildAIChatRequest(content)
785792
786793
// 先进入加载状态
787794
startNodeAILoading(currentSchema.id, 'AI正在处理您的请求...')
@@ -796,26 +803,38 @@ export default {
796803
}
797804
}
798805
799-
// 创建新的AbortController用于取消请求
806+
// 创建新的AbortController用于取消请求,递增token防止响应竞争
800807
aiChatAbortController = new AbortController()
801-
let response
808+
const currentToken = ++aiChatRequestToken
809+
802810
try {
803-
response = await chat(params, aiChatAbortController.signal)
811+
const params = await buildAIChatRequest(content)
812+
const response = await chat(params, aiChatAbortController.signal)
813+
814+
// 响应到达后校验token:如果有更新的请求已经发出,丢弃本次响应
815+
if (currentToken !== aiChatRequestToken) {
816+
return
817+
}
818+
819+
// 响应到达后再次检查:如果用户已经取消,不应用AI补丁
820+
const status = getNodeAIStatus(currentSchema.id)
821+
if (!status || status.state !== 'loading') {
822+
return
823+
}
824+
825+
// AI运行完:设置chatContent、aiModifiedNodeData,修改画布schema为AI的schema
826+
// 应用失败则取消loading,避免UI永久转圈
827+
if (!applyAIPatches(currentSchema.id, response, content)) {
828+
cancelNodeAILoading(currentSchema.id)
829+
}
804830
} catch (error) {
805831
// 请求被取消时不再应用补丁
806832
if (error.name === 'AbortError' || error.name === 'CanceledError') {
807833
return
808834
}
835+
// 其他错误:取消loading状态,避免UI永久转圈
836+
cancelNodeAILoading(currentSchema.id)
809837
}
810-
811-
// 响应到达后再次检查:如果用户已经取消,不应用AI补丁
812-
const currentStatus = getNodeAIStatus(currentSchema.id)
813-
if (!currentStatus || currentStatus.state !== 'loading') {
814-
return
815-
}
816-
817-
// AI运行完:设置chatContent、aiModifiedNodeData,修改画布schema为AI的schema
818-
applyAIPatches(currentSchema.id, response, content)
819838
}
820839
821840
// AI加载取消处理
@@ -829,6 +848,8 @@ export default {
829848
if (aiChatAbortController) {
830849
aiChatAbortController.abort()
831850
aiChatAbortController = null
851+
// 递增token,使任何未完成的请求响应失效
852+
aiChatRequestToken++
832853
}
833854
834855
// 取消加载状态
@@ -873,18 +894,26 @@ export default {
873894
874895
try {
875896
const params = await buildAIChatRequest(chatContent)
876-
// 创建新的AbortController用于重新生成的请求
897+
// 创建新的AbortController用于重新生成的请求,递增token防止响应竞争
898+
const refreshToken = ++aiChatRequestToken
877899
aiChatAbortController = new AbortController()
878900
const response = await chat(params, aiChatAbortController.signal)
879901
902+
// 响应到达后校验token:如果有更新的请求已经发出,丢弃本次响应
903+
if (refreshToken !== aiChatRequestToken) {
904+
return
905+
}
906+
880907
// 响应到达后检查是否已被取消
881908
const status = getNodeAIStatus(nodeId)
882909
if (!status || status.state !== 'loading') {
883910
return
884911
}
885912
886-
// AI运行完操作和 handleAIChatComplete 一样
887-
applyAIPatches(nodeId, response, chatContent)
913+
// AI运行完操作和 handleAIChatComplete 一样,应用失败则取消loading
914+
if (!applyAIPatches(nodeId, response, chatContent)) {
915+
cancelNodeAILoading(nodeId)
916+
}
888917
} catch (error) {
889918
// 请求被取消时不做额外处理
890919
if (error.name === 'AbortError' || error.name === 'CanceledError') {

packages/canvas/container/src/composables/useAIChat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ const applyPatchesToSchema = (patches: any[], pageSchema: object, parentPath: st
586586
* @returns 应用成功返回 true,失败返回 false
587587
*/
588588
const applyAIPatches = (nodeId: string, chatResponse: any, chatContent?: string): boolean => {
589-
if (!chatResponse) {
589+
if (!chatResponse?.choices?.[0]?.message?.content) {
590590
return false
591591
}
592592

0 commit comments

Comments
 (0)