feat: last run frontend (#21369)

The frontend of feat: Persist Variables for Enhanced Debugging Workflow (#20699).

Co-authored-by: jZonG <jzongcode@gmail.com>
This commit is contained in:
Joel
2025-06-24 09:10:30 +08:00
committed by GitHub
parent 10b738a296
commit 1a1bfd4048
122 changed files with 5888 additions and 2061 deletions

View File

@@ -0,0 +1,142 @@
import type { StateCreator } from 'zustand'
import produce from 'immer'
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
import type { ValueSelector } from '../../../types'
type InspectVarsState = {
currentFocusNodeId: string | null
nodesWithInspectVars: NodeWithVar[] // the nodes have data
conversationVars: VarInInspect[]
}
type InspectVarsActions = {
setCurrentFocusNodeId: (nodeId: string | null) => void
setNodesWithInspectVars: (payload: NodeWithVar[]) => void
deleteAllInspectVars: () => void
setNodeInspectVars: (nodeId: string, payload: VarInInspect[]) => void
deleteNodeInspectVars: (nodeId: string) => void
setInspectVarValue: (nodeId: string, name: string, value: any) => void
resetToLastRunVar: (nodeId: string, varId: string, value: any) => void
renameInspectVarName: (nodeId: string, varId: string, selector: ValueSelector) => void
deleteInspectVar: (nodeId: string, varId: string) => void
}
export type InspectVarsSliceShape = InspectVarsState & InspectVarsActions
export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set, get) => {
return ({
currentFocusNodeId: null,
nodesWithInspectVars: [],
conversationVars: [],
setCurrentFocusNodeId: (nodeId) => {
set(() => ({
currentFocusNodeId: nodeId,
}))
},
setNodesWithInspectVars: (payload) => {
set(() => ({
nodesWithInspectVars: payload,
}))
},
deleteAllInspectVars: () => {
set(() => ({
nodesWithInspectVars: [],
}))
},
setNodeInspectVars: (nodeId, payload) => {
set((state) => {
const prevNodes = state.nodesWithInspectVars
const nodes = produce(prevNodes, (draft) => {
const index = prevNodes.findIndex(node => node.nodeId === nodeId)
if (index !== -1) {
draft[index].vars = payload
draft[index].isValueFetched = true
}
})
return {
nodesWithInspectVars: nodes,
}
})
},
deleteNodeInspectVars: (nodeId) => {
set((state: InspectVarsSliceShape) => {
const nodes = state.nodesWithInspectVars.filter(node => node.nodeId !== nodeId)
return {
nodesWithInspectVars: nodes,
}
},
)
},
setInspectVarValue: (nodeId, varId, value) => {
set((state: InspectVarsSliceShape) => {
const nodes = produce(state.nodesWithInspectVars, (draft) => {
const targetNode = draft.find(node => node.nodeId === nodeId)
if (!targetNode)
return
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
if(!targetVar)
return
targetVar.value = value
targetVar.edited = true
},
)
return {
nodesWithInspectVars: nodes,
}
})
},
resetToLastRunVar: (nodeId, varId, value) => {
set((state: InspectVarsSliceShape) => {
const nodes = produce(state.nodesWithInspectVars, (draft) => {
const targetNode = draft.find(node => node.nodeId === nodeId)
if (!targetNode)
return
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
if(!targetVar)
return
targetVar.value = value
targetVar.edited = false
},
)
return {
nodesWithInspectVars: nodes,
}
})
},
renameInspectVarName: (nodeId, varId, selector) => {
set((state: InspectVarsSliceShape) => {
const nodes = produce(state.nodesWithInspectVars, (draft) => {
const targetNode = draft.find(node => node.nodeId === nodeId)
if (!targetNode)
return
const targetVar = targetNode.vars.find(varItem => varItem.id === varId)
if(!targetVar)
return
targetVar.name = selector[1]
targetVar.selector = selector
},
)
return {
nodesWithInspectVars: nodes,
}
})
},
deleteInspectVar: (nodeId, varId) => {
set((state: InspectVarsSliceShape) => {
const nodes = produce(state.nodesWithInspectVars, (draft) => {
const targetNode = draft.find(node => node.nodeId === nodeId)
if (!targetNode)
return
const needChangeVarIndex = targetNode.vars.findIndex(varItem => varItem.id === varId)
if (needChangeVarIndex !== -1)
targetNode.vars.splice(needChangeVarIndex, 1)
},
)
return {
nodesWithInspectVars: nodes,
}
})
},
})
}

View File

@@ -0,0 +1,72 @@
import { VarType } from '../../../types'
import type { VarInInspect } from '@/types/workflow'
import { VarInInspectType } from '@/types/workflow'
export const vars: VarInInspect[] = [
{
id: 'xxx',
type: VarInInspectType.node,
name: 'text00',
description: '',
selector: ['1745476079387', 'text'],
value_type: VarType.string,
value: 'text value...',
edited: false,
},
{
id: 'fdklajljgldjglkagjlk',
type: VarInInspectType.node,
name: 'text',
description: '',
selector: ['1712386917734', 'text'],
value_type: VarType.string,
value: 'made zhizhang',
edited: false,
},
]
export const conversationVars: VarInInspect[] = [
{
id: 'con1',
type: VarInInspectType.conversation,
name: 'conversationVar 1',
description: '',
selector: ['conversation', 'var1'],
value_type: VarType.string,
value: 'conversation var value...',
edited: false,
},
{
id: 'con2',
type: VarInInspectType.conversation,
name: 'conversationVar 2',
description: '',
selector: ['conversation', 'var2'],
value_type: VarType.number,
value: 456,
edited: false,
},
]
export const systemVars: VarInInspect[] = [
{
id: 'sys1',
type: VarInInspectType.system,
name: 'query',
description: '',
selector: ['sys', 'query'],
value_type: VarType.string,
value: 'Hello robot!',
edited: false,
},
{
id: 'sys2',
type: VarInInspectType.system,
name: 'user_id',
description: '',
selector: ['sys', 'user_id'],
value_type: VarType.string,
value: 'djflakjerlkjdlksfjslakjsdfl',
edited: false,
},
]

View File

@@ -28,7 +28,12 @@ import type { WorkflowDraftSliceShape } from './workflow-draft-slice'
import { createWorkflowDraftSlice } from './workflow-draft-slice'
import type { WorkflowSliceShape } from './workflow-slice'
import { createWorkflowSlice } from './workflow-slice'
import type { InspectVarsSliceShape } from './debug/inspect-vars-slice'
import { createInspectVarsSlice } from './debug/inspect-vars-slice'
import { WorkflowContext } from '@/app/components/workflow/context'
import type { LayoutSliceShape } from './layout-slice'
import { createLayoutSlice } from './layout-slice'
import type { WorkflowSliceShape as WorkflowAppSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice'
export type Shape =
@@ -43,6 +48,8 @@ export type Shape =
VersionSliceShape &
WorkflowDraftSliceShape &
WorkflowSliceShape &
InspectVarsSliceShape &
LayoutSliceShape &
WorkflowAppSliceShape
type CreateWorkflowStoreParams = {
@@ -64,6 +71,8 @@ export const createWorkflowStore = (params: CreateWorkflowStoreParams) => {
...createVersionSlice(...args),
...createWorkflowDraftSlice(...args),
...createWorkflowSlice(...args),
...createInspectVarsSlice(...args),
...createLayoutSlice(...args),
...(injectWorkflowStoreSliceFn?.(...args) || {} as WorkflowAppSliceShape),
}))
}

View File

@@ -0,0 +1,44 @@
import type { StateCreator } from 'zustand'
export type LayoutSliceShape = {
workflowCanvasWidth?: number
workflowCanvasHeight?: number
setWorkflowCanvasWidth: (width: number) => void
setWorkflowCanvasHeight: (height: number) => void
// rightPanelWidth - otherPanelWidth = nodePanelWidth
rightPanelWidth?: number
setRightPanelWidth: (width: number) => void
nodePanelWidth: number
setNodePanelWidth: (width: number) => void
otherPanelWidth: number
setOtherPanelWidth: (width: number) => void
bottomPanelWidth: number // min-width = 400px; default-width = auto || 480px;
setBottomPanelWidth: (width: number) => void
bottomPanelHeight: number
setBottomPanelHeight: (height: number) => void
variableInspectPanelHeight: number // min-height = 120px; default-height = 320px;
setVariableInspectPanelHeight: (height: number) => void
maximizeCanvas: boolean
setMaximizeCanvas: (maximize: boolean) => void
}
export const createLayoutSlice: StateCreator<LayoutSliceShape> = set => ({
workflowCanvasWidth: undefined,
workflowCanvasHeight: undefined,
setWorkflowCanvasWidth: width => set(() => ({ workflowCanvasWidth: width })),
setWorkflowCanvasHeight: height => set(() => ({ workflowCanvasHeight: height })),
rightPanelWidth: undefined,
setRightPanelWidth: width => set(() => ({ rightPanelWidth: width })),
nodePanelWidth: localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 400,
setNodePanelWidth: width => set(() => ({ nodePanelWidth: width })),
otherPanelWidth: 400,
setOtherPanelWidth: width => set(() => ({ otherPanelWidth: width })),
bottomPanelWidth: 480,
setBottomPanelWidth: width => set(() => ({ bottomPanelWidth: width })),
bottomPanelHeight: 324,
setBottomPanelHeight: height => set(() => ({ bottomPanelHeight: height })),
variableInspectPanelHeight: localStorage.getItem('workflow-variable-inpsect-panel-height') ? Number.parseFloat(localStorage.getItem('workflow-variable-inpsect-panel-height')!) : 320,
setVariableInspectPanelHeight: height => set(() => ({ variableInspectPanelHeight: height })),
maximizeCanvas: localStorage.getItem('workflow-canvas-maximize') === 'true',
setMaximizeCanvas: maximize => set(() => ({ maximizeCanvas: maximize })),
})

View File

@@ -15,6 +15,10 @@ export type PanelSliceShape = {
left: number
}
setPanelMenu: (panelMenu: PanelSliceShape['panelMenu']) => void
showVariableInspectPanel: boolean
setShowVariableInspectPanel: (showVariableInspectPanel: boolean) => void
initShowLastRunTab: boolean
setInitShowLastRunTab: (initShowLastRunTab: boolean) => void
}
export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({
@@ -29,4 +33,8 @@ export const createPanelSlice: StateCreator<PanelSliceShape> = set => ({
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
panelMenu: undefined,
setPanelMenu: panelMenu => set(() => ({ panelMenu })),
showVariableInspectPanel: false,
setShowVariableInspectPanel: showVariableInspectPanel => set(() => ({ showVariableInspectPanel })),
initShowLastRunTab: false,
setInitShowLastRunTab: initShowLastRunTab => set(() => ({ initShowLastRunTab })),
})