Merge pull request #553 from zxrys/feature/fix-frontend-lag-issue

perf: frontend lag issue
This commit is contained in:
Yufan Dang 2026-02-27 13:51:12 +08:00 committed by GitHub
commit 6fe4fd1a0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 117 additions and 88 deletions

View File

@ -297,7 +297,9 @@ const setBaseYamlFromSource = (source) => {
} }
try { try {
let parsed = source let parsed = source
let yamlString = ''
if (typeof source === 'string') { if (typeof source === 'string') {
yamlString = source
parsed = yaml.load(source) || {} parsed = yaml.load(source) || {}
} else if (typeof source === 'object') { } else if (typeof source === 'object') {
parsed = JSON.parse(JSON.stringify(source)) parsed = JSON.parse(JSON.stringify(source))
@ -305,7 +307,9 @@ const setBaseYamlFromSource = (source) => {
parsed = {} parsed = {}
} }
baseYamlObject.value = parsed baseYamlObject.value = parsed
baseYamlString.value = yaml.dump(parsed ?? null, yamlDumpOptions) // Avoid expensive dump on modal open for object sources.
// Save path will generate YAML from current object when needed.
baseYamlString.value = yamlString
} catch (error) { } catch (error) {
console.error('Failed to set base YAML from provided source:', error) console.error('Failed to set base YAML from provided source:', error)
baseYamlObject.value = null baseYamlObject.value = null
@ -1046,7 +1050,7 @@ watch(() => props.initialYaml, (newValue) => {
baseYamlObject.value = null baseYamlObject.value = null
baseYamlString.value = '' baseYamlString.value = ''
} }
}, { immediate: true, deep: true }) }, { immediate: true })
watch(() => props.workflowName, async (newName, oldName) => { watch(() => props.workflowName, async (newName, oldName) => {
if (!newName) { if (!newName) {

View File

@ -602,29 +602,30 @@ const deleteNodeById = async (nodeId) => {
if (!nodeId) { if (!nodeId) {
return return
} }
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph) { if (!source?.graph) {
return return
} }
const nodesArr = Array.isArray(snapshot.graph.nodes) ? snapshot.graph.nodes : [] const sourceGraph = source.graph
const edgesArr = Array.isArray(snapshot.graph.edges) ? snapshot.graph.edges : [] const nodesArr = Array.isArray(sourceGraph.nodes) ? sourceGraph.nodes : []
const edgesArr = Array.isArray(sourceGraph.edges) ? sourceGraph.edges : []
// Remove the node and its related edges // Remove the node and its related edges
const nextNodes = nodesArr.filter(node => node?.id !== nodeId) const nextNodes = nodesArr.filter(node => node?.id !== nodeId)
const nextEdges = edgesArr.filter(edge => edge?.from !== nodeId && edge?.to !== nodeId) const nextEdges = edgesArr.filter(edge => edge?.from !== nodeId && edge?.to !== nodeId)
// Remove node ID from graph.start/end // Remove node ID from graph.start/end
const nextStart = Array.isArray(snapshot.graph.start) const nextStart = Array.isArray(sourceGraph.start)
? snapshot.graph.start.filter(id => id !== nodeId) ? sourceGraph.start.filter(id => id !== nodeId)
: snapshot.graph.start : sourceGraph.start
const nextEnd = Array.isArray(snapshot.graph.end) const nextEnd = Array.isArray(sourceGraph.end)
? snapshot.graph.end.filter(id => id !== nodeId) ? sourceGraph.end.filter(id => id !== nodeId)
: snapshot.graph.end : sourceGraph.end
const nextSnapshot = { const nextSnapshot = {
...snapshot, ...source,
graph: { graph: {
...snapshot.graph, ...sourceGraph,
nodes: nextNodes, nodes: nextNodes,
edges: nextEdges, edges: nextEdges,
start: nextStart, start: nextStart,
@ -647,13 +648,14 @@ const deleteEdgeByEndpoints = async (fromId, toId) => {
if (!fromId || !toId) { if (!fromId || !toId) {
return return
} }
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph || !Array.isArray(snapshot.graph.edges)) { if (!source?.graph || !Array.isArray(source.graph.edges)) {
return return
} }
const sourceGraph = source.graph
let removed = false let removed = false
const nextEdges = snapshot.graph.edges.filter(edge => { const nextEdges = sourceGraph.edges.filter(edge => {
if (!removed && edge?.from === fromId && edge?.to === toId) { if (!removed && edge?.from === fromId && edge?.to === toId) {
removed = true removed = true
return false return false
@ -662,11 +664,11 @@ const deleteEdgeByEndpoints = async (fromId, toId) => {
}) })
// Delete from .start if edge is from Start Node // Delete from .start if edge is from Start Node
let nextStart = snapshot.graph.start let nextStart = sourceGraph.start
if (fromId === START_NODE_ID) { if (fromId === START_NODE_ID) {
nextStart = Array.isArray(snapshot.graph.start) nextStart = Array.isArray(sourceGraph.start)
? snapshot.graph.start.filter(id => id !== toId) ? sourceGraph.start.filter(id => id !== toId)
: snapshot.graph.start : sourceGraph.start
// Empty start node array is not allowed // Empty start node array is not allowed
const startArray = Array.isArray(nextStart) ? nextStart : [] const startArray = Array.isArray(nextStart) ? nextStart : []
@ -677,9 +679,9 @@ const deleteEdgeByEndpoints = async (fromId, toId) => {
} }
const nextSnapshot = { const nextSnapshot = {
...snapshot, ...source,
graph: { graph: {
...snapshot.graph, ...sourceGraph,
edges: nextEdges, edges: nextEdges,
start: nextStart start: nextStart
} }
@ -888,6 +890,11 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
const currentNodes = nodes.value || [] const currentNodes = nodes.value || []
const currentEdges = edges.value || [] const currentEdges = edges.value || []
const defaultCenterPosition = getCentralPosition()
const getDefaultCenterPosition = () => ({
x: defaultCenterPosition.x,
y: defaultCenterPosition.y
})
const existingNodeById = preserveExistingLayout const existingNodeById = preserveExistingLayout
? new Map(currentNodes.map(node => [node.id, node])) ? new Map(currentNodes.map(node => [node.id, node]))
@ -947,8 +954,9 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
} }
} }
while (q.length) { let queueIndex = 0
const id = q.shift() while (queueIndex < q.length) {
const id = q[queueIndex++]
const baseLevel = levelById.get(id) || 0 const baseLevel = levelById.get(id) || 0
const neighbors = adj.get(id) || new Set() const neighbors = adj.get(id) || new Set()
for (const nb of neighbors) { for (const nb of neighbors) {
@ -1033,7 +1041,7 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
} }
} }
const pos = positions.get(id) || getCentralPosition() const pos = positions.get(id) || getDefaultCenterPosition()
return { return {
id, id,
type: 'workflow-node', type: 'workflow-node',
@ -1051,7 +1059,7 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
id: yamlNode.id, id: yamlNode.id,
type: 'workflow-node', type: 'workflow-node',
label: yamlNode.id, label: yamlNode.id,
position: getCentralPosition(), position: getDefaultCenterPosition(),
data: yamlNode data: yamlNode
})) }))
nodes.value = nextNodes nodes.value = nextNodes
@ -1097,13 +1105,13 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
// Place start node to the left of the leftmost column // Place start node to the left of the leftmost column
const yamlNodesInGraph = (nodes.value || []).filter(n => n && n.id !== START_NODE_ID) const yamlNodesInGraph = (nodes.value || []).filter(n => n && n.id !== START_NODE_ID)
if (yamlNodesInGraph.length) { if (yamlNodesInGraph.length) {
const xs = yamlNodesInGraph.map(n => (n?.position && typeof n.position.x === 'number') ? n.position.x : getCentralPosition().x) const xs = yamlNodesInGraph.map(n => (n?.position && typeof n.position.x === 'number') ? n.position.x : defaultCenterPosition.x)
const minX = Math.min(...xs) const minX = Math.min(...xs)
// Find nodes in that left column // Find nodes in that left column
const tol = 1 const tol = 1
const leftColumn = yamlNodesInGraph.filter(n => Math.abs((n?.position?.x || 0) - minX) <= tol) const leftColumn = yamlNodesInGraph.filter(n => Math.abs((n?.position?.x || 0) - minX) <= tol)
const ys = leftColumn.map(n => (n?.position && typeof n.position.y === 'number') ? n.position.y : getCentralPosition().y) const ys = leftColumn.map(n => (n?.position && typeof n.position.y === 'number') ? n.position.y : defaultCenterPosition.y)
const avgY = ys.length ? ys.reduce((a, b) => a + b, 0) / ys.length : getCentralPosition().y const avgY = ys.length ? ys.reduce((a, b) => a + b, 0) / ys.length : defaultCenterPosition.y
const startXOffset = -100 const startXOffset = -100
const startYOffset = 80 const startYOffset = 80
startNode = { startNode = {
@ -1118,7 +1126,7 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
id: START_NODE_ID, id: START_NODE_ID,
type: 'start-node', type: 'start-node',
label: 'Start', label: 'Start',
position: getCentralPosition(), position: getDefaultCenterPosition(),
data: { id: START_NODE_ID, label: 'Start' } data: { id: START_NODE_ID, label: 'Start' }
} }
} }
@ -1128,7 +1136,7 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
id: START_NODE_ID, id: START_NODE_ID,
type: 'start-node', type: 'start-node',
label: 'Start', label: 'Start',
position: getCentralPosition(), position: getDefaultCenterPosition(),
data: { id: START_NODE_ID, label: 'Start' } data: { id: START_NODE_ID, label: 'Start' }
} }
} }
@ -1167,13 +1175,14 @@ const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
}).filter(Boolean) }).filter(Boolean)
// Combine YAML edges with visual start edges (preserve any existing non-yaml edges) // Combine YAML edges with visual start edges (preserve any existing non-yaml edges)
const nextYamlEdgeIdSet = new Set(nextYamlEdges.map(edge => edge.id))
edges.value = [ edges.value = [
// keep any existing edges that are not YAML edges (e.g., visual-only) when preserving layout // keep any existing edges that are not YAML edges (e.g., visual-only) when preserving layout
// but always exclude previous Start edges so they are replaced by the newly computed ones // but always exclude previous Start edges so they are replaced by the newly computed ones
...(preserveExistingLayout ? currentEdges.filter(e => { ...(preserveExistingLayout ? currentEdges.filter(e => {
const k = `${e.source}-${e.target}` const k = `${e.source}-${e.target}`
// drop if it's a YAML-defined edge or a previous Start edge // drop if it's a YAML-defined edge or a previous Start edge
const isYamlEdge = nextYamlEdges.some(ne => ne.id === k) const isYamlEdge = nextYamlEdgeIdSet.has(k)
const isStartEdge = e.source === START_NODE_ID const isStartEdge = e.source === START_NODE_ID
// Also drop if it looks like a YAML edge (has data.from/to) but isn't in nextYamlEdges (stale) // Also drop if it looks like a YAML edge (has data.from/to) but isn't in nextYamlEdges (stale)
const isStaleYamlEdge = e.data?.from && e.data?.to const isStaleYamlEdge = e.data?.from && e.data?.to
@ -1292,69 +1301,81 @@ const updateVueFlowNodeId = (oldId, newId) => {
} }
// FormGenerator integration // FormGenerator integration
const snapshotYamlContent = () => cloneDeep(yamlContent.value ?? null) // Build YAML without specific node (shallow clone path to avoid full deep-clone on editor open)
// Build YAML without specific node
const buildYamlWithoutNode = (nodeId) => { const buildYamlWithoutNode = (nodeId) => {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph?.nodes || !Array.isArray(snapshot.graph.nodes)) { if (!source?.graph?.nodes || !Array.isArray(source.graph.nodes)) {
return snapshot return source
}
return {
...source,
graph: {
...source.graph,
nodes: source.graph.nodes.filter(node => node?.id !== nodeId)
}
} }
snapshot.graph.nodes = snapshot.graph.nodes.filter(node => node?.id !== nodeId)
return snapshot
} }
const buildYamlWithoutEdge = (fromId, toId) => { const buildYamlWithoutEdge = (fromId, toId) => {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph?.edges || !Array.isArray(snapshot.graph.edges)) { if (!source?.graph?.edges || !Array.isArray(source.graph.edges)) {
return snapshot return source
} }
let removed = false let removed = false
snapshot.graph.edges = snapshot.graph.edges.filter(edge => { const filteredEdges = source.graph.edges.filter(edge => {
if (!removed && edge?.from === fromId && edge?.to === toId) { if (!removed && edge?.from === fromId && edge?.to === toId) {
removed = true removed = true
return false return false
} }
return true return true
}) })
return snapshot return {
...source,
graph: {
...source.graph,
edges: filteredEdges
}
}
} }
const buildYamlWithoutVars = () => { const buildYamlWithoutVars = () => {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot || typeof snapshot !== 'object') { if (!source || typeof source !== 'object') {
return snapshot return source
} }
if (!Object.prototype.hasOwnProperty.call(snapshot, 'vars')) { if (!Object.prototype.hasOwnProperty.call(source, 'vars')) {
return snapshot return source
} }
const sanitized = { ...snapshot } const sanitized = { ...source }
delete sanitized.vars delete sanitized.vars
return sanitized return sanitized
} }
const buildYamlWithoutMemory = () => { const buildYamlWithoutMemory = () => {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph) { if (!source?.graph) {
return snapshot return source
} }
if (Object.prototype.hasOwnProperty.call(snapshot.graph, 'memory')) { if (Object.prototype.hasOwnProperty.call(source.graph, 'memory')) {
const newGraph = { ...snapshot.graph } const newGraph = { ...source.graph }
delete newGraph.memory delete newGraph.memory
snapshot.graph = newGraph return {
...source,
graph: newGraph
}
} }
return snapshot return source
} }
const buildYamlWithoutGraph = () => { const buildYamlWithoutGraph = () => {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot || typeof snapshot !== 'object') { if (!source || typeof source !== 'object') {
return snapshot return source
} }
if (!Object.prototype.hasOwnProperty.call(snapshot, 'graph')) { if (!Object.prototype.hasOwnProperty.call(source, 'graph')) {
return snapshot return source
} }
const sanitized = { ...snapshot } const sanitized = { ...source }
delete sanitized.graph delete sanitized.graph
return sanitized return sanitized
} }
@ -1362,17 +1383,18 @@ const buildYamlWithoutGraph = () => {
const autoAddStartEdge = async (nextNodeId) => { const autoAddStartEdge = async (nextNodeId) => {
const workflowNodes = (yamlContent.value?.graph?.nodes || []).filter(node => node?.id !== START_NODE_ID) const workflowNodes = (yamlContent.value?.graph?.nodes || []).filter(node => node?.id !== START_NODE_ID)
if (workflowNodes.length === 1 && workflowNodes[0]?.id === nextNodeId) { if (workflowNodes.length === 1 && workflowNodes[0]?.id === nextNodeId) {
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph) { const sourceGraph = source?.graph && typeof source.graph === 'object' ? source.graph : {}
snapshot.graph = {} const currentStart = Array.isArray(sourceGraph.start) ? sourceGraph.start : []
} if (!currentStart.includes(nextNodeId)) {
if (!Array.isArray(snapshot.graph.start)) { const nextSnapshot = {
snapshot.graph.start = [] ...source,
} graph: {
if (!snapshot.graph.start.includes(nextNodeId)) { ...sourceGraph,
// Add node start: [...currentStart, nextNodeId]
snapshot.graph.start.push(nextNodeId) }
const ok = await persistYamlSnapshot(snapshot) }
const ok = await persistYamlSnapshot(nextSnapshot)
if (ok) { if (ok) {
await loadYamlFile() await loadYamlFile()
syncVueNodesAndEdgesData() syncVueNodesAndEdgesData()
@ -1398,12 +1420,10 @@ const openDynamicFormGenerator = (type, options = {}) => {
const hasCustomYaml = Object.prototype.hasOwnProperty.call(options, 'initialYaml') const hasCustomYaml = Object.prototype.hasOwnProperty.call(options, 'initialYaml')
const yamlSource = hasCustomYaml ? options.initialYaml : yamlContent.value const yamlSource = hasCustomYaml ? options.initialYaml : yamlContent.value
formGeneratorInitialYaml.value = yamlSource ? cloneDeep(yamlSource) : null formGeneratorInitialYaml.value = yamlSource || null
if (Object.prototype.hasOwnProperty.call(options, 'initialFormData')) { if (Object.prototype.hasOwnProperty.call(options, 'initialFormData')) {
formGeneratorInitialFormData.value = options.initialFormData formGeneratorInitialFormData.value = options.initialFormData || null
? cloneDeep(options.initialFormData)
: null
} else { } else {
formGeneratorInitialFormData.value = null formGeneratorInitialFormData.value = null
} }
@ -1632,25 +1652,30 @@ const onConnect = async (connection) => {
// Special handling for StartNode connections // Special handling for StartNode connections
if (connection.source === START_NODE_ID) { if (connection.source === START_NODE_ID) {
// Add target node to graph.start array instead of opening FormGenerator // Add target node to graph.start array instead of opening FormGenerator
const snapshot = snapshotYamlContent() const source = yamlContent.value
if (!snapshot?.graph) { if (!source?.graph) {
setTimeout(() => { setTimeout(() => {
isCreatingConnection.value = false isCreatingConnection.value = false
}, 10) }, 10)
return return
} }
const sourceGraph = source.graph
// Ensure graph.start exists as an array // Ensure graph.start exists as an array
if (!Array.isArray(snapshot.graph.start)) { const currentStart = Array.isArray(sourceGraph.start) ? sourceGraph.start : []
snapshot.graph.start = []
}
// Add target node to start array if not already present // Add target node to start array if not already present
if (!snapshot.graph.start.includes(connection.target)) { if (!currentStart.includes(connection.target)) {
snapshot.graph.start.push(connection.target) const nextSnapshot = {
...source,
graph: {
...sourceGraph,
start: [...currentStart, connection.target]
}
}
// Persist the updated YAML // Persist the updated YAML
const ok = await persistYamlSnapshot(snapshot) const ok = await persistYamlSnapshot(nextSnapshot)
if (ok) { if (ok) {
await loadYamlFile() await loadYamlFile()
syncVueNodesAndEdgesData() syncVueNodesAndEdgesData()