Browse Source

feat: change history by supplementary node information (#25294)

Co-authored-by: alleschen <alleschen@tencent.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
tags/1.9.0
XiamuSanhua 1 month ago
parent
commit
ac2aa967c4
No account linked to committer's email address

+ 2
- 2
web/app/components/workflow/candidate-node.tsx View File

}) })
setNodes(newNodes) setNodes(newNodes)
if (candidateNode.type === CUSTOM_NOTE_NODE) if (candidateNode.type === CUSTOM_NOTE_NODE)
saveStateToHistory(WorkflowHistoryEvent.NoteAdd)
saveStateToHistory(WorkflowHistoryEvent.NoteAdd, { nodeId: candidateNode.id })
else else
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: candidateNode.id })


workflowStore.setState({ candidateNode: undefined }) workflowStore.setState({ candidateNode: undefined })



+ 24
- 3
web/app/components/workflow/header/view-workflow-history.tsx View File



const calculateChangeList: ChangeHistoryList = useMemo(() => { const calculateChangeList: ChangeHistoryList = useMemo(() => {
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => { const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
const nodes = (state.nodes || store.getState().nodes) || []
const nodeId = state?.workflowHistoryEventMeta?.nodeId
const targetTitle = nodes.find(n => n.id === nodeId)?.data?.title ?? ''
return { return {
label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent), label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent),
index: reverse ? list.length - 1 - index - startIndex : index - startIndex, index: reverse ? list.length - 1 - index - startIndex : index - startIndex,
state,
state: {
...state,
workflowHistoryEventMeta: state.workflowHistoryEventMeta ? {
...state.workflowHistoryEventMeta,
nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle,
} : undefined,
},
} }
}).filter(Boolean) }).filter(Boolean)


} }
}, [futureStates, getHistoryLabel, pastStates, store]) }, [futureStates, getHistoryLabel, pastStates, store])


const composeHistoryItemLabel = useCallback((nodeTitle: string | undefined, baseLabel: string) => {
if (!nodeTitle)
return baseLabel
return `${nodeTitle} ${baseLabel}`
}, [])

return ( return (
( (
<PortalToFollowElem <PortalToFollowElem
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)} )}
> >
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
</div> </div>
</div> </div>
</div> </div>
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
)} )}
> >
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)})
{composeHistoryItemLabel(
item?.state?.workflowHistoryEventMeta?.nodeTitle,
item?.label || t('workflow.changeHistory.sessionStart'),
)} ({calculateStepLabel(item?.index)})
</div> </div>
</div> </div>
</div> </div>

+ 8
- 8
web/app/components/workflow/hooks/use-nodes-interactions.ts View File



if (x !== 0 && y !== 0) { if (x !== 0 && y !== 0) {
// selecting a note will trigger a drag stop event with x and y as 0 // selecting a note will trigger a drag stop event with x and y as 0
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop)
saveStateToHistory(WorkflowHistoryEvent.NodeDragStop, { nodeId: node.id })
} }
} }
}, [workflowStore, getNodesReadOnly, saveStateToHistory, handleSyncWorkflowDraft]) }, [workflowStore, getNodesReadOnly, saveStateToHistory, handleSyncWorkflowDraft])
setEdges(newEdges) setEdges(newEdges)


handleSyncWorkflowDraft() handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.NodeConnect)
saveStateToHistory(WorkflowHistoryEvent.NodeConnect, { nodeId: targetNode?.id })
} }
else { else {
const { const {
handleSyncWorkflowDraft() handleSyncWorkflowDraft()


if (currentNode.type === CUSTOM_NOTE_NODE) if (currentNode.type === CUSTOM_NOTE_NODE)
saveStateToHistory(WorkflowHistoryEvent.NoteDelete)
saveStateToHistory(WorkflowHistoryEvent.NoteDelete, { nodeId: currentNode.id })


else else
saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
saveStateToHistory(WorkflowHistoryEvent.NodeDelete, { nodeId: currentNode.id })
}, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t]) }, [getNodesReadOnly, store, deleteNodeInspectorVars, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])


const handleNodeAdd = useCallback<OnNodeAdd>(( const handleNodeAdd = useCallback<OnNodeAdd>((
setEdges(newEdges) setEdges(newEdges)
} }
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNode.id })
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit]) }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit])


const handleNodeChange = useCallback(( const handleNodeChange = useCallback((
setEdges(newEdges) setEdges(newEdges)
handleSyncWorkflowDraft() handleSyncWorkflowDraft()


saveStateToHistory(WorkflowHistoryEvent.NodeChange)
saveStateToHistory(WorkflowHistoryEvent.NodeChange, { nodeId: currentNodeId })
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory]) }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory])


const handleNodesCancelSelected = useCallback(() => { const handleNodesCancelSelected = useCallback(() => {


setNodes([...nodes, ...nodesToPaste]) setNodes([...nodes, ...nodesToPaste])
setEdges([...edges, ...edgesToPaste]) setEdges([...edges, ...edgesToPaste])
saveStateToHistory(WorkflowHistoryEvent.NodePaste)
saveStateToHistory(WorkflowHistoryEvent.NodePaste, { nodeId: nodesToPaste?.[0]?.id })
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
} }
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy]) }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy])
}) })
setNodes(newNodes) setNodes(newNodes)
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.NodeResize)
saveStateToHistory(WorkflowHistoryEvent.NodeResize, { nodeId })
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory]) }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory])


const handleNodeDisconnect = useCallback((nodeId: string) => { const handleNodeDisconnect = useCallback((nodeId: string) => {

+ 6
- 4
web/app/components/workflow/hooks/use-workflow-history.ts View File

} from 'reactflow' } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWorkflowHistoryStore } from '../workflow-history-store' import { useWorkflowHistoryStore } from '../workflow-history-store'
import type { WorkflowHistoryEventMeta } from '../workflow-history-store'


/** /**
* All supported Events that create a new history state. * All supported Events that create a new history state.
// Some events may be triggered multiple times in a short period of time. // Some events may be triggered multiple times in a short period of time.
// We debounce the history state update to avoid creating multiple history states // We debounce the history state update to avoid creating multiple history states
// with minimal changes. // with minimal changes.
const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent) => {
const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
workflowHistoryStore.setState({ workflowHistoryStore.setState({
workflowHistoryEvent: event, workflowHistoryEvent: event,
workflowHistoryEventMeta: meta,
nodes: store.getState().getNodes(), nodes: store.getState().getNodes(),
edges: store.getState().edges, edges: store.getState().edges,
}) })
}, 500)) }, 500))


const saveStateToHistory = useCallback((event: WorkflowHistoryEvent) => {
const saveStateToHistory = useCallback((event: WorkflowHistoryEvent, meta?: WorkflowHistoryEventMeta) => {
switch (event) { switch (event) {
case WorkflowHistoryEvent.NoteChange: case WorkflowHistoryEvent.NoteChange:
// Hint: Note change does not trigger when note text changes, // Hint: Note change does not trigger when note text changes,
// because the note editors have their own history states. // because the note editors have their own history states.
saveStateToHistoryRef.current(event)
saveStateToHistoryRef.current(event, meta)
break break
case WorkflowHistoryEvent.NodeTitleChange: case WorkflowHistoryEvent.NodeTitleChange:
case WorkflowHistoryEvent.NodeDescriptionChange: case WorkflowHistoryEvent.NodeDescriptionChange:
case WorkflowHistoryEvent.NoteAdd: case WorkflowHistoryEvent.NoteAdd:
case WorkflowHistoryEvent.LayoutOrganize: case WorkflowHistoryEvent.LayoutOrganize:
case WorkflowHistoryEvent.NoteDelete: case WorkflowHistoryEvent.NoteDelete:
saveStateToHistoryRef.current(event)
saveStateToHistoryRef.current(event, meta)
break break
default: default:
// We do not create a history state for every event. // We do not create a history state for every event.

+ 2
- 2
web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx View File



const handleTitleBlur = useCallback((title: string) => { const handleTitleBlur = useCallback((title: string) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { title } }) handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange, { nodeId: id })
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
const handleDescriptionChange = useCallback((desc: string) => { const handleDescriptionChange = useCallback((desc: string) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } }) handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange, { nodeId: id })
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])


const isChildNode = !!(data.isInIteration || data.isInLoop) const isChildNode = !!(data.isInIteration || data.isInLoop)

+ 2
- 2
web/app/components/workflow/note-node/hooks.ts View File



const handleThemeChange = useCallback((theme: NoteTheme) => { const handleThemeChange = useCallback((theme: NoteTheme) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { theme } }) handleNodeDataUpdateWithSyncDraft({ id, data: { theme } })
saveStateToHistory(WorkflowHistoryEvent.NoteChange)
saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id })
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])


const handleEditorChange = useCallback((editorState: EditorState) => { const handleEditorChange = useCallback((editorState: EditorState) => {


const handleShowAuthorChange = useCallback((showAuthor: boolean) => { const handleShowAuthorChange = useCallback((showAuthor: boolean) => {
handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } }) handleNodeDataUpdateWithSyncDraft({ id, data: { showAuthor } })
saveStateToHistory(WorkflowHistoryEvent.NoteChange)
saveStateToHistory(WorkflowHistoryEvent.NoteChange, { nodeId: id })
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory]) }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])


return { return {

+ 8
- 0
web/app/components/workflow/workflow-history-store.tsx View File

setState: (state: WorkflowHistoryState) => { setState: (state: WorkflowHistoryState) => {
store.setState({ store.setState({
workflowHistoryEvent: state.workflowHistoryEvent, workflowHistoryEvent: state.workflowHistoryEvent,
workflowHistoryEventMeta: state.workflowHistoryEventMeta,
nodes: state.nodes.map((node: Node) => ({ ...node, data: { ...node.data, selected: false } })), nodes: state.nodes.map((node: Node) => ({ ...node, data: { ...node.data, selected: false } })),
edges: state.edges.map((edge: Edge) => ({ ...edge, selected: false }) as Edge), edges: state.edges.map((edge: Edge) => ({ ...edge, selected: false }) as Edge),
}) })
(set, get) => { (set, get) => {
return { return {
workflowHistoryEvent: undefined, workflowHistoryEvent: undefined,
workflowHistoryEventMeta: undefined,
nodes: storeNodes, nodes: storeNodes,
edges: storeEdges, edges: storeEdges,
getNodes: () => get().nodes, getNodes: () => get().nodes,
nodes: Node[] nodes: Node[]
edges: Edge[] edges: Edge[]
workflowHistoryEvent: WorkflowHistoryEvent | undefined workflowHistoryEvent: WorkflowHistoryEvent | undefined
workflowHistoryEventMeta?: WorkflowHistoryEventMeta
} }


export type WorkflowHistoryActions = { export type WorkflowHistoryActions = {
edges: Edge[] edges: Edge[]
children: ReactNode children: ReactNode
} }

export type WorkflowHistoryEventMeta = {
nodeId?: string
nodeTitle?: string
}

Loading…
Cancel
Save