diff --git a/web/app/components/workflow/hooks/use-current-vars.ts b/web/app/components/workflow/hooks/use-current-vars.ts index 1d20b308bb..b674da158b 100644 --- a/web/app/components/workflow/hooks/use-current-vars.ts +++ b/web/app/components/workflow/hooks/use-current-vars.ts @@ -2,11 +2,11 @@ import { useWorkflowStore } from '../store' const useCurrentVars = () => { const workflowStore = useWorkflowStore() const { - currentNodes, - getCurrentVar, - setCurrentVar, - clearCurrentVars, - clearCurrentNodeVars, + nodes: currentNodes, + getInspectVar: getCurrentVar, + setInspectVar: setCurrentVar, + clearInspectVars: clearCurrentVars, + clearNodeInspectVars: clearCurrentNodeVars, getLastRunVar, getLastRunInfos, } = workflowStore.getState() diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx index 508802a55b..cca5836926 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx @@ -5,13 +5,19 @@ import { NodeRunningStatus } from '@/app/components/workflow/types' import type { FC } from 'react' import React, { useEffect, useState } from 'react' import NoData from './no-data' +import { useLastRun } from '@/service/use-workflow' +import Loading from '@/app/components/base/loading' type Props = { + isDataFromHistory: boolean + appId: string nodeId: string - runningStatus: NodeRunningStatus + runningStatus?: NodeRunningStatus } const LastRun: FC = ({ + isDataFromHistory, + appId, nodeId, runningStatus, }) => { @@ -20,22 +26,31 @@ const LastRun: FC = ({ const { getLastRunNodeInfo, } = workflowStore.getState() - const [runResult, setRunResult] = useState(getLastRunNodeInfo(nodeId)) + const { data: runResultFromHistory, isFetching } = useLastRun(appId, nodeId, isDataFromHistory) + const [runResultFromSingleRun, setRunResult] = useState(isDataFromHistory ? getLastRunNodeInfo(nodeId) : null) + const runResult = isDataFromHistory ? runResultFromHistory : runResultFromSingleRun const isRunning = runningStatus === NodeRunningStatus.Running + // get data from current running result useEffect(() => { + if (isDataFromHistory) + return + setRunResult(getLastRunNodeInfo(nodeId)) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [runningStatus]) + }, [runningStatus, isDataFromHistory]) const handleSingleRun = () => { console.log('run') } + if (isDataFromHistory && isFetching) + return + if (isRunning) return - if (!runResult) { + if (!runResultFromSingleRun) { return ( ) diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index 7044756abd..74ea1df288 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -156,7 +156,7 @@ const useOneStepRun = ({ const workflowStore = useWorkflowStore() const { setLastRunNodeInfo, - setCurrentNodeVars, + setNodeInspectVars: setCurrentNodeVars, setShowSingleRunPanel, } = workflowStore.getState() const [runResult, doSetRunResult] = useState(null) diff --git a/web/app/components/workflow/store/workflow/current-vars-slice.ts b/web/app/components/workflow/store/workflow/current-vars-slice.ts deleted file mode 100644 index a72a2e0d26..0000000000 --- a/web/app/components/workflow/store/workflow/current-vars-slice.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { StateCreator } from 'zustand' -import produce from 'immer' -import type { NodeTracing } from '@/types/workflow' - -// TODO: Missing var type -type NodeVars = NodeTracing - -type CurrentVarsState = { - currentNodes: NodeVars[] -} - -type CurrentVarsActions = { - setCurrentVars: (vars: NodeVars[]) => void - getCurrentVars: () => NodeVars[] - clearCurrentVars: () => void - setCurrentNodeVars: (nodeId: string, payload: NodeVars) => void - clearCurrentNodeVars: (nodeId: string) => void - getCurrentNodeVars: (nodeId: string) => NodeVars | undefined - hasCurrentNodeVars: (nodeId: string) => boolean - setCurrentVar: (nodeId: string, key: string, value: any) => void - getCurrentVar: (nodeId: string, key: string) => any -} - -export type CurrentVarsSliceShape = CurrentVarsState & CurrentVarsActions - -export const createCurrentVarsSlice: StateCreator = (set, get) => { - return ({ - currentNodes: [], - setCurrentVars: (vars) => { - set(() => ({ - currentNodes: vars, - })) - }, - getCurrentVars: () => { - return get().currentNodes - }, - clearCurrentVars: () => { - set(() => ({ - currentNodes: [], - })) - }, - setCurrentNodeVars: (nodeId, payload) => { - set((state) => { - const prevNodes = state.currentNodes - const nodes = produce(prevNodes, (draft) => { - const index = prevNodes.findIndex(node => node.id === nodeId) - if (index === -1) - draft.push(payload) - else - draft[index] = payload - }) - - return { - currentNodes: nodes, - } - }) - }, - clearCurrentNodeVars: (nodeId) => { - set(produce((state: CurrentVarsSliceShape) => { - const nodes = state.currentNodes.filter(node => node.node_id !== nodeId) - state.currentNodes = nodes - }, - )) - }, - getCurrentNodeVars: (nodeId) => { - const nodes = get().currentNodes - return nodes.find(node => node.node_id === nodeId) - }, - hasCurrentNodeVars: (nodeId) => { - return !!get().getCurrentNodeVars(nodeId) - }, - setCurrentVar: (nodeId, key, value) => { - set(produce((state: CurrentVarsSliceShape) => { - const nodes = state.currentNodes.map((node) => { - if (node.id === nodeId) { - return produce(node, (draft) => { - if (!draft.outputs) - draft.outputs = {} - draft.outputs[key] = value - }) - } - return node - }) - state.currentNodes = nodes - })) - }, - getCurrentVar(nodeId, key) { - const node = get().getCurrentNodeVars(nodeId) - if (!node) - return undefined - - const variable = node.outputs?.[key] - return variable - }, - }) -} diff --git a/web/app/components/workflow/store/workflow/index.ts b/web/app/components/workflow/store/workflow/index.ts index 3ad69369fa..a3f368561d 100644 --- a/web/app/components/workflow/store/workflow/index.ts +++ b/web/app/components/workflow/store/workflow/index.ts @@ -30,8 +30,8 @@ import type { WorkflowSliceShape } from './workflow-slice' import { createWorkflowSlice } from './workflow-slice' import type { LastRunSliceShape } from './last-run-slice' import { createLastRunSlice } from './last-run-slice' -import type { CurrentVarsSliceShape } from './current-vars-slice' -import { createCurrentVarsSlice } from './current-vars-slice' +import type { CurrentVarsSliceShape } from './var-inspect-slice' +import { createInspectVarsSlice } from './var-inspect-slice' import { WorkflowContext } from '@/app/components/workflow/context' import type { LayoutSliceShape } from './layout-slice' @@ -75,7 +75,7 @@ export const createWorkflowStore = (params: CreateWorkflowStoreParams) => { ...createWorkflowDraftSlice(...args), ...createWorkflowSlice(...args), ...createLastRunSlice(...args), - ...createCurrentVarsSlice(...args), + ...createInspectVarsSlice(...args), ...createLayoutSlice(...args), ...(injectWorkflowStoreSliceFn?.(...args) || {} as WorkflowAppSliceShape), })) diff --git a/web/app/components/workflow/store/workflow/var-inspect-slice.ts b/web/app/components/workflow/store/workflow/var-inspect-slice.ts new file mode 100644 index 0000000000..db1bdefed4 --- /dev/null +++ b/web/app/components/workflow/store/workflow/var-inspect-slice.ts @@ -0,0 +1,96 @@ +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 + nodes: NodeWithVar[] // the nodes have data + conversationVars: VarInInspect[] +} + +type InspectVarsActions = { + getAllInspectVars: () => NodeWithVar[] + clearInspectVars: () => void + setNodeInspectVars: (nodeId: string, payload: NodeWithVar) => void + clearNodeInspectVars: (nodeId: string) => void + getNodeInspectVars: (nodeId: string) => NodeWithVar | undefined + hasNodeInspectVars: (nodeId: string) => boolean + setInspectVar: (nodeId: string, selector: ValueSelector, value: any) => void + getInspectVar: (nodeId: string, selector: ValueSelector) => any +} + +export type CurrentVarsSliceShape = InspectVarsState & InspectVarsActions + +export const createInspectVarsSlice: StateCreator = (set, get) => { + return ({ + currentFocusNodeId: null, + nodes: [], + conversationVars: [], + getAllInspectVars: () => { + return get().nodes + }, + clearInspectVars: () => { + set(() => ({ + nodes: [], + })) + }, + setNodeInspectVars: (nodeId, payload) => { + set((state) => { + const prevNodes = state.nodes + const nodes = produce(prevNodes, (draft) => { + const index = prevNodes.findIndex(node => node.nodeId === nodeId) + if (index === -1) + draft.push(payload) + else + draft[index] = payload + }) + + return { + nodes, + } + }) + }, + clearNodeInspectVars: (nodeId) => { + set(produce((state: CurrentVarsSliceShape) => { + const nodes = state.nodes.filter(node => node.nodeId !== nodeId) + state.nodes = nodes + }, + )) + }, + getNodeInspectVars: (nodeId) => { + const nodes = get().nodes + return nodes.find(node => node.nodeId === nodeId) + }, + hasNodeInspectVars: (nodeId) => { + return !!get().getNodeInspectVars(nodeId) + }, + setInspectVar: (nodeId, selector, value) => { + set(produce((state: CurrentVarsSliceShape) => { + const nodes = state.nodes.map((node) => { + if (node.nodeId === nodeId) { + return produce(node, (draft) => { + const needChangeVarIndex = draft.vars.findIndex((varItem) => { + return varItem.selector.join('.') === selector.join('.') + }) + if (needChangeVarIndex !== -1) + draft.vars[needChangeVarIndex].value = value + }) + } + return node + }) + state.nodes = nodes + })) + }, + getInspectVar(nodeId, key) { + const node = get().getNodeInspectVars(nodeId) + if (!node) + return undefined + + const variable = node.vars.find((varItem) => { + return varItem.selector.join('.') === key.join('.') + })?.value + return variable + }, + }) +} diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts index 4321552cc7..a118e5f3b8 100644 --- a/web/service/use-workflow.ts +++ b/web/service/use-workflow.ts @@ -4,12 +4,14 @@ import type { FetchWorkflowDraftPageParams, FetchWorkflowDraftPageResponse, FetchWorkflowDraftResponse, + NodeTracing, PublishWorkflowParams, UpdateWorkflowParams, WorkflowConfigResponse, } from '@/types/workflow' import type { CommonResponse } from '@/models/common' import { useReset } from './use-base' +import { sleep } from '@/utils' const NAME_SPACE = 'workflow' @@ -85,3 +87,39 @@ export const usePublishWorkflow = (appId: string) => { }), }) } + +export const useLastRun = (appID: string, nodeId: string, enabled: boolean) => { + return useQuery({ + enabled, + queryKey: [NAME_SPACE, 'last-run', appID, nodeId], + queryFn: async () => { + // TODO: mock data + await sleep(1000) + return Promise.resolve({ + node_id: nodeId, + status: 'success', + node_type: 'llm', + title: 'LLM', + inputs: null, + outputs: { + text: '"abc" is a simple sequence of three letters. Is there anything specific you\'d like to know about it, or are you just testing the system? \n\nLet me know if you have any other questions or tasks! 😊 \n', + usage: { + prompt_tokens: 3, + prompt_unit_price: '0', + prompt_price_unit: '0.000001', + prompt_price: '0', + completion_tokens: 48, + completion_unit_price: '0', + completion_price_unit: '0.000001', + completion_price: '0', + total_tokens: 51, + total_price: '0', + currency: 'USD', + latency: 0.7095853444188833, + }, + finish_reason: '1', + }, + } as any) + }, + }) +} diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 3ec801afbe..f67cf067b1 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -1,5 +1,5 @@ import type { Viewport } from 'reactflow' -import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, InputVar, Node, Variable } from '@/app/components/workflow/types' +import type { BlockEnum, ConversationVariable, Edge, EnvironmentVariable, InputVar, Node, ValueSelector, VarType, Variable } from '@/app/components/workflow/types' import type { TransferMethod } from '@/types/app' import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import type { BeforeRunFormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form' @@ -370,3 +370,28 @@ export type PanelProps = { } export type NodeRunResult = NodeTracing + +// Var Inspect +export enum VarInInspectType { + conversation = 'conversation', + environment = 'environment', + node = 'node', +} + +export type VarInInspect = { + id: string + type: VarInInspectType + name: string + description: string + selector: ValueSelector + value_type: VarType + value: any + edited: boolean +} + +export type NodeWithVar = { + nodeId: string + nodeType: BlockEnum + title: string + vars: VarInInspect[] +}