feat: last run frontend (#21369)
The frontend of feat: Persist Variables for Enhanced Debugging Workflow (#20699). Co-authored-by: jZonG <jzongcode@gmail.com>pull/21395/head
parent
10b738a296
commit
1a1bfd4048
@ -0,0 +1,125 @@
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import {
|
||||
RiEqualizer2Line,
|
||||
RiMenuLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import AppIcon from '../base/app-icon'
|
||||
import Divider from '../base/divider'
|
||||
import AppInfo from './app-info'
|
||||
import NavLink from './navLink'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { NavIcon } from './navLink'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
navigation: Array<{
|
||||
name: string
|
||||
href: string
|
||||
icon: NavIcon
|
||||
selectedIcon: NavIcon
|
||||
}>
|
||||
}
|
||||
|
||||
const AppSidebarDropdown = ({ navigation }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const [detailExpand, setDetailExpand] = useState(false)
|
||||
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
const setOpen = useCallback((v: boolean) => {
|
||||
doSetOpen(v)
|
||||
openRef.current = v
|
||||
}, [doSetOpen])
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
if (!appDetail)
|
||||
return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='fixed left-2 top-2 z-20'>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: -41,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<div className={cn('flex cursor-pointer items-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-1 shadow-lg backdrop-blur-sm hover:bg-background-default-hover', open && 'bg-background-default-hover')}>
|
||||
<AppIcon
|
||||
size='small'
|
||||
iconType={appDetail.icon_type}
|
||||
icon={appDetail.icon}
|
||||
background={appDetail.icon_background}
|
||||
imageUrl={appDetail.icon_url}
|
||||
/>
|
||||
<RiMenuLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className={cn('w-[305px] rounded-xl border-[0.5px] border-components-panel-border bg-background-default-subtle shadow-lg')}>
|
||||
<div className='p-2'>
|
||||
<div
|
||||
className={cn('flex flex-col gap-2 rounded-lg p-2 pb-2.5', isCurrentWorkspaceEditor && 'cursor-pointer hover:bg-state-base-hover')}
|
||||
onClick={() => {
|
||||
setDetailExpand(true)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-between self-stretch'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={appDetail.icon_type}
|
||||
icon={appDetail.icon}
|
||||
background={appDetail.icon_background}
|
||||
imageUrl={appDetail.icon_url}
|
||||
/>
|
||||
<div className='flex items-center justify-center rounded-md p-0.5'>
|
||||
<div className='flex h-5 w-5 items-center justify-center'>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col items-start gap-1'>
|
||||
<div className='flex w-full'>
|
||||
<div className='system-md-semibold truncate text-text-secondary'>{appDetail.name}</div>
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4'>
|
||||
<Divider bgStyle='gradient' />
|
||||
</div>
|
||||
<nav className='space-y-0.5 px-3 pb-6 pt-4'>
|
||||
{navigation.map((item, index) => {
|
||||
return (
|
||||
<NavLink key={index} mode='expand' iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</div>
|
||||
<div className='z-20'>
|
||||
<AppInfo expand onlyShowDetail openState={detailExpand} onDetailExpand={setDetailExpand} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppSidebarDropdown
|
||||
@ -0,0 +1,68 @@
|
||||
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
|
||||
import { useWorkflowStore } from '../../workflow/store'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { fetchAllInspectVars } from '@/service/workflow'
|
||||
import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow'
|
||||
import { useNodesInteractionsWithoutSync } from '../../workflow/hooks/use-nodes-interactions-without-sync'
|
||||
const useSetWorkflowVarsWithValue = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { setNodesWithInspectVars, appId } = workflowStore.getState()
|
||||
const store = useStoreApi()
|
||||
const invalidateConversationVarValues = useInvalidateConversationVarValues(appId)
|
||||
const invalidateSysVarValues = useInvalidateSysVarValues(appId)
|
||||
const { handleCancelAllNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||
|
||||
const setInspectVarsToStore = (inspectVars: VarInInspect[]) => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodeArr = getNodes()
|
||||
const nodesKeyValue: Record<string, Node> = {}
|
||||
nodeArr.forEach((node) => {
|
||||
nodesKeyValue[node.id] = node
|
||||
})
|
||||
|
||||
const withValueNodeIds: Record<string, boolean> = {}
|
||||
inspectVars.forEach((varItem) => {
|
||||
const nodeId = varItem.selector[0]
|
||||
|
||||
const node = nodesKeyValue[nodeId]
|
||||
if (!node)
|
||||
return
|
||||
withValueNodeIds[nodeId] = true
|
||||
})
|
||||
const withValueNodes = Object.keys(withValueNodeIds).map((nodeId) => {
|
||||
return nodesKeyValue[nodeId]
|
||||
})
|
||||
|
||||
const res: NodeWithVar[] = withValueNodes.map((node) => {
|
||||
const nodeId = node.id
|
||||
const varsUnderTheNode = inspectVars.filter((varItem) => {
|
||||
return varItem.selector[0] === nodeId
|
||||
})
|
||||
const nodeWithVar = {
|
||||
nodeId,
|
||||
nodePayload: node.data,
|
||||
nodeType: node.data.type,
|
||||
title: node.data.title,
|
||||
vars: varsUnderTheNode,
|
||||
isSingRunRunning: false,
|
||||
isValueFetched: false,
|
||||
}
|
||||
return nodeWithVar
|
||||
})
|
||||
setNodesWithInspectVars(res)
|
||||
}
|
||||
|
||||
const fetchInspectVars = async () => {
|
||||
invalidateConversationVarValues()
|
||||
invalidateSysVarValues()
|
||||
const data = await fetchAllInspectVars(appId)
|
||||
setInspectVarsToStore(data)
|
||||
handleCancelAllNodeSuccessStatus() // to make sure clear node output show the unset status
|
||||
}
|
||||
return {
|
||||
fetchInspectVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSetWorkflowVarsWithValue
|
||||
@ -0,0 +1,241 @@
|
||||
import { fetchNodeInspectVars } from '@/service/workflow'
|
||||
import { useStore, useWorkflowStore } from '../store'
|
||||
import type { ValueSelector } from '../types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import {
|
||||
useConversationVarValues,
|
||||
useDeleteAllInspectorVars,
|
||||
useDeleteInspectVar,
|
||||
useDeleteNodeInspectorVars,
|
||||
useEditInspectorVar,
|
||||
useInvalidateConversationVarValues,
|
||||
useInvalidateSysVarValues,
|
||||
useLastRun,
|
||||
useResetConversationVar,
|
||||
useResetToLastRunValue,
|
||||
useSysVarValues,
|
||||
} from '@/service/use-workflow'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { isConversationVar, isENV, isSystemVar } from '../nodes/_base/components/variable/utils'
|
||||
import produce from 'immer'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync'
|
||||
import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'
|
||||
|
||||
const useInspectVarsCrud = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const nodesWithInspectVars = useStore(s => s.nodesWithInspectVars)
|
||||
const {
|
||||
appId,
|
||||
setNodeInspectVars,
|
||||
setInspectVarValue,
|
||||
renameInspectVarName: renameInspectVarNameInStore,
|
||||
deleteAllInspectVars: deleteAllInspectVarsInStore,
|
||||
deleteNodeInspectVars: deleteNodeInspectVarsInStore,
|
||||
deleteInspectVar: deleteInspectVarInStore,
|
||||
setNodesWithInspectVars,
|
||||
resetToLastRunVar: resetToLastRunVarInStore,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const { data: conversationVars } = useConversationVarValues(appId)
|
||||
const invalidateConversationVarValues = useInvalidateConversationVarValues(appId)
|
||||
const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId)
|
||||
const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId)
|
||||
const { data: systemVars } = useSysVarValues(appId)
|
||||
const invalidateSysVarValues = useInvalidateSysVarValues(appId)
|
||||
|
||||
const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId)
|
||||
const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId)
|
||||
const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId)
|
||||
|
||||
const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId)
|
||||
const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
||||
const getNodeInspectVars = useCallback((nodeId: string) => {
|
||||
const node = nodesWithInspectVars.find(node => node.nodeId === nodeId)
|
||||
return node
|
||||
}, [nodesWithInspectVars])
|
||||
|
||||
const getVarId = useCallback((nodeId: string, varName: string) => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
const varId = node.vars.find((varItem) => {
|
||||
return varItem.selector[1] === varName
|
||||
})?.id
|
||||
return varId
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
|
||||
const variable = node.vars.find((varItem) => {
|
||||
return varItem.name === name
|
||||
})
|
||||
return variable
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => {
|
||||
const isEnv = isENV([nodeId])
|
||||
if (isEnv) // always have value
|
||||
return true
|
||||
const isSys = isSystemVar([nodeId])
|
||||
if (isSys)
|
||||
return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name)
|
||||
const isChatVar = isConversationVar([nodeId])
|
||||
if (isChatVar)
|
||||
return conversationVars.some(varItem => varItem.selector?.[1] === name)
|
||||
return getInspectVar(nodeId, name) !== undefined
|
||||
}, [getInspectVar])
|
||||
|
||||
const hasNodeInspectVars = useCallback((nodeId: string) => {
|
||||
return !!getNodeInspectVars(nodeId)
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const fetchInspectVarValue = async (selector: ValueSelector) => {
|
||||
const nodeId = selector[0]
|
||||
const isSystemVar = nodeId === 'sys'
|
||||
const isConversationVar = nodeId === 'conversation'
|
||||
if (isSystemVar) {
|
||||
invalidateSysVarValues()
|
||||
return
|
||||
}
|
||||
if (isConversationVar) {
|
||||
invalidateConversationVarValues()
|
||||
return
|
||||
}
|
||||
const vars = await fetchNodeInspectVars(appId, nodeId)
|
||||
setNodeInspectVars(nodeId, vars)
|
||||
}
|
||||
|
||||
// after last run would call this
|
||||
const appendNodeInspectVars = (nodeId: string, payload: VarInInspect[], allNodes: Node[]) => {
|
||||
const nodes = produce(nodesWithInspectVars, (draft) => {
|
||||
const nodeInfo = allNodes.find(node => node.id === nodeId)
|
||||
if (nodeInfo) {
|
||||
const index = draft.findIndex(node => node.nodeId === nodeId)
|
||||
if (index === -1) {
|
||||
draft.push({
|
||||
nodeId,
|
||||
nodeType: nodeInfo.data.type,
|
||||
title: nodeInfo.data.title,
|
||||
vars: payload,
|
||||
})
|
||||
}
|
||||
else {
|
||||
draft[index].vars = payload
|
||||
}
|
||||
}
|
||||
})
|
||||
setNodesWithInspectVars(nodes)
|
||||
handleCancelNodeSuccessStatus(nodeId)
|
||||
}
|
||||
|
||||
const hasNodeInspectVar = (nodeId: string, varId: string) => {
|
||||
const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId)
|
||||
if(!targetNode || !targetNode.vars)
|
||||
return false
|
||||
return targetNode.vars.some(item => item.id === varId)
|
||||
}
|
||||
|
||||
const deleteInspectVar = async (nodeId: string, varId: string) => {
|
||||
if(hasNodeInspectVar(nodeId, varId)) {
|
||||
await doDeleteInspectVar(varId)
|
||||
deleteInspectVarInStore(nodeId, varId)
|
||||
}
|
||||
}
|
||||
|
||||
const resetConversationVar = async (varId: string) => {
|
||||
await doResetConversationVar(varId)
|
||||
invalidateConversationVarValues()
|
||||
}
|
||||
|
||||
const deleteNodeInspectorVars = async (nodeId: string) => {
|
||||
if (hasNodeInspectVars(nodeId)) {
|
||||
await doDeleteNodeInspectorVars(nodeId)
|
||||
deleteNodeInspectVarsInStore(nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAllInspectorVars = async () => {
|
||||
await doDeleteAllInspectorVars()
|
||||
await invalidateConversationVarValues()
|
||||
await invalidateSysVarValues()
|
||||
deleteAllInspectVarsInStore()
|
||||
handleEdgeCancelRunningStatus()
|
||||
}
|
||||
|
||||
const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => {
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
value,
|
||||
})
|
||||
setInspectVarValue(nodeId, varId, value)
|
||||
if (nodeId === VarInInspectType.conversation)
|
||||
invalidateConversationVarValues()
|
||||
if (nodeId === VarInInspectType.system)
|
||||
invalidateSysVarValues()
|
||||
}, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarValue])
|
||||
|
||||
const [currNodeId, setCurrNodeId] = useState<string | null>(null)
|
||||
const [currEditVarId, setCurrEditVarId] = useState<string | null>(null)
|
||||
const { data } = useLastRun(appId, currNodeId || '', !!currNodeId)
|
||||
useEffect(() => {
|
||||
if (data && currNodeId && currEditVarId) {
|
||||
const inspectVar = getNodeInspectVars(currNodeId)?.vars?.find(item => item.id === currEditVarId)
|
||||
resetToLastRunVarInStore(currNodeId, currEditVarId, data.outputs?.[inspectVar?.selector?.[1] || ''])
|
||||
}
|
||||
}, [data, currNodeId, currEditVarId, getNodeInspectVars, editInspectVarValue, resetToLastRunVarInStore])
|
||||
|
||||
const renameInspectVarName = async (nodeId: string, oldName: string, newName: string) => {
|
||||
const varId = getVarId(nodeId, oldName)
|
||||
if (!varId)
|
||||
return
|
||||
|
||||
const newSelector = [nodeId, newName]
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
name: newName,
|
||||
})
|
||||
renameInspectVarNameInStore(nodeId, varId, newSelector)
|
||||
}
|
||||
|
||||
const isInspectVarEdited = useCallback((nodeId: string, name: string) => {
|
||||
const inspectVar = getInspectVar(nodeId, name)
|
||||
if (!inspectVar)
|
||||
return false
|
||||
|
||||
return inspectVar.edited
|
||||
}, [getInspectVar])
|
||||
|
||||
const resetToLastRunVar = async (nodeId: string, varId: string) => {
|
||||
await doResetToLastRunValue(varId)
|
||||
setCurrNodeId(nodeId)
|
||||
setCurrEditVarId(varId)
|
||||
}
|
||||
|
||||
return {
|
||||
conversationVars: conversationVars || [],
|
||||
systemVars: systemVars || [],
|
||||
nodesWithInspectVars,
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
fetchInspectVarValue,
|
||||
editInspectVarValue,
|
||||
renameInspectVarName,
|
||||
appendNodeInspectVars,
|
||||
deleteInspectVar,
|
||||
deleteNodeInspectorVars,
|
||||
deleteAllInspectorVars,
|
||||
isInspectVarEdited,
|
||||
resetToLastRunVar,
|
||||
invalidateSysVarValues,
|
||||
resetConversationVar,
|
||||
invalidateConversationVarValues,
|
||||
}
|
||||
}
|
||||
|
||||
export default useInspectVarsCrud
|
||||
@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
export type Props = {
|
||||
nodeName: string
|
||||
onHide: () => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const PanelWrap: FC<Props> = ({
|
||||
nodeName,
|
||||
onHide,
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='absolute inset-0 z-10 rounded-2xl bg-background-overlay-alt'>
|
||||
<div className='flex h-full flex-col rounded-2xl bg-components-panel-bg'>
|
||||
<div className='flex h-8 shrink-0 items-center justify-between pl-4 pr-3 pt-3'>
|
||||
<div className='truncate text-base font-semibold text-text-primary'>
|
||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||
</div>
|
||||
<div className='ml-2 shrink-0 cursor-pointer p-1' onClick={() => {
|
||||
onHide()
|
||||
}}>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary ' />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(PanelWrap)
|
||||
@ -0,0 +1,429 @@
|
||||
import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NextStep from '../next-step'
|
||||
import PanelOperator from '../panel-operator'
|
||||
import NodePosition from '@/app/components/workflow/nodes/_base/components/node-position'
|
||||
import HelpLink from '../help-link'
|
||||
import {
|
||||
DescriptionInput,
|
||||
TitleInput,
|
||||
} from '../title-description-input'
|
||||
import ErrorHandleOnPanel from '../error-handle/error-handle-on-panel'
|
||||
import RetryOnPanel from '../retry/retry-on-panel'
|
||||
import { useResizePanel } from '../../hooks/use-resize-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import {
|
||||
WorkflowHistoryEvent,
|
||||
useAvailableBlocks,
|
||||
useNodeDataUpdate,
|
||||
useNodesInteractions,
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
useWorkflowHistory,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { BlockEnum, type Node, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import Tab, { TabType } from './tab'
|
||||
import LastRun from './last-run'
|
||||
import useLastRun from './last-run/use-last-run'
|
||||
import BeforeRunForm from '../before-run-form'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { NODES_EXTRA_DATA } from '@/app/components/workflow/constants'
|
||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
||||
import PanelWrap from '../before-run-form/panel-wrap'
|
||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
|
||||
type BasePanelProps = {
|
||||
children: ReactNode
|
||||
} & Node
|
||||
|
||||
const BasePanel: FC<BasePanelProps> = ({
|
||||
id,
|
||||
data,
|
||||
children,
|
||||
position,
|
||||
width,
|
||||
height,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
})))
|
||||
const isSingleRunning = data._singleRunningStatus === NodeRunningStatus.Running
|
||||
|
||||
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
||||
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||
const otherPanelWidth = useStore(s => s.otherPanelWidth)
|
||||
const setNodePanelWidth = useStore(s => s.setNodePanelWidth)
|
||||
|
||||
const maxNodePanelWidth = useMemo(() => {
|
||||
if (!workflowCanvasWidth)
|
||||
return 720
|
||||
if (!otherPanelWidth)
|
||||
return workflowCanvasWidth - 400
|
||||
|
||||
return workflowCanvasWidth - otherPanelWidth - 400
|
||||
}, [workflowCanvasWidth, otherPanelWidth])
|
||||
|
||||
const updateNodePanelWidth = useCallback((width: number) => {
|
||||
// Ensure the width is within the min and max range
|
||||
const newValue = Math.min(Math.max(width, 400), maxNodePanelWidth)
|
||||
localStorage.setItem('workflow-node-panel-width', `${newValue}`)
|
||||
setNodePanelWidth(newValue)
|
||||
}, [maxNodePanelWidth, setNodePanelWidth])
|
||||
|
||||
const handleResize = useCallback((width: number) => {
|
||||
updateNodePanelWidth(width)
|
||||
}, [updateNodePanelWidth])
|
||||
|
||||
const {
|
||||
triggerRef,
|
||||
containerRef,
|
||||
} = useResizePanel({
|
||||
direction: 'horizontal',
|
||||
triggerDirection: 'left',
|
||||
minWidth: 400,
|
||||
maxWidth: maxNodePanelWidth,
|
||||
onResize: debounce(handleResize),
|
||||
})
|
||||
|
||||
const debounceUpdate = debounce(updateNodePanelWidth)
|
||||
useEffect(() => {
|
||||
if (!workflowCanvasWidth)
|
||||
return
|
||||
if (workflowCanvasWidth - 400 <= nodePanelWidth + otherPanelWidth)
|
||||
debounceUpdate(workflowCanvasWidth - 400 - otherPanelWidth)
|
||||
}, [nodePanelWidth, otherPanelWidth, workflowCanvasWidth, updateNodePanelWidth])
|
||||
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
const toolIcon = useToolIcon(data)
|
||||
|
||||
const { saveStateToHistory } = useWorkflowHistory()
|
||||
|
||||
const {
|
||||
handleNodeDataUpdate,
|
||||
handleNodeDataUpdateWithSyncDraft,
|
||||
} = useNodeDataUpdate()
|
||||
|
||||
const handleTitleBlur = useCallback((title: string) => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||
const handleDescriptionChange = useCallback((desc: string) => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||
|
||||
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||
const isSupportSingleRun = canRunBySingle(data.type, isChildNode)
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
|
||||
const hasClickRunning = useRef(false)
|
||||
const [isPaused, setIsPaused] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if(data._singleRunningStatus === NodeRunningStatus.Running) {
|
||||
hasClickRunning.current = true
|
||||
setIsPaused(false)
|
||||
}
|
||||
else if(data._isSingleRun && data._singleRunningStatus === undefined && hasClickRunning) {
|
||||
setIsPaused(true)
|
||||
hasClickRunning.current = false
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const updateNodeRunningStatus = useCallback((status: NodeRunningStatus) => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: status,
|
||||
},
|
||||
})
|
||||
}, [handleNodeDataUpdate, id, data])
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(`id changed: ${id}, hasClickRunning: ${hasClickRunning.current}`)
|
||||
hasClickRunning.current = false
|
||||
}, [id])
|
||||
|
||||
const {
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
runningStatus,
|
||||
handleStop,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
runResult,
|
||||
getInputVars,
|
||||
toVarInputs,
|
||||
tabType,
|
||||
isRunAfterSingleRun,
|
||||
setTabType,
|
||||
singleRunParams,
|
||||
nodeInfo,
|
||||
setRunInputData,
|
||||
handleSingleRun,
|
||||
handleRunWithParams,
|
||||
getExistVarValuesInForms,
|
||||
getFilteredExistVarForms,
|
||||
} = useLastRun<typeof data>({
|
||||
id,
|
||||
data,
|
||||
defaultRunInputData: NODES_EXTRA_DATA[data.type]?.defaultRunInputData || {},
|
||||
isPaused,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsPaused(false)
|
||||
}, [tabType])
|
||||
|
||||
const logParams = useLogs()
|
||||
const passedLogParams = (() => {
|
||||
if ([BlockEnum.Tool, BlockEnum.Agent, BlockEnum.Iteration, BlockEnum.Loop].includes(data.type))
|
||||
return logParams
|
||||
|
||||
return {}
|
||||
})()
|
||||
|
||||
if(logParams.showSpecialResultPanel) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
)}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
style={{
|
||||
width: `${nodePanelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<PanelWrap
|
||||
nodeName={data.title}
|
||||
onHide={hideSingleRun}
|
||||
>
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<SpecialResultPanel {...passedLogParams} />
|
||||
</div>
|
||||
</PanelWrap>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (isShowSingleRun) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
)}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
style={{
|
||||
width: `${nodePanelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<BeforeRunForm
|
||||
nodeName={data.title}
|
||||
nodeType={data.type}
|
||||
onHide={hideSingleRun}
|
||||
onRun={handleRunWithParams}
|
||||
{...singleRunParams!}
|
||||
{...passedLogParams}
|
||||
existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)}
|
||||
filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
showMessageLogModal && '!absolute -top-[5px] right-[416px] z-0 !mr-0 w-[384px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
||||
)}>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'>
|
||||
<div className='h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid'></div>
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
style={{
|
||||
width: `${nodePanelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<div className='sticky top-0 z-10 shrink-0 border-b-[0.5px] border-divider-regular bg-components-panel-bg'>
|
||||
<div className='flex items-center px-4 pb-1 pt-4'>
|
||||
<BlockIcon
|
||||
className='mr-1 shrink-0'
|
||||
type={data.type}
|
||||
toolIcon={toolIcon}
|
||||
size='md'
|
||||
/>
|
||||
<TitleInput
|
||||
value={data.title || ''}
|
||||
onBlur={handleTitleBlur}
|
||||
/>
|
||||
<div className='flex shrink-0 items-center text-text-tertiary'>
|
||||
{
|
||||
isSupportSingleRun && !nodesReadOnly && (
|
||||
<Tooltip
|
||||
popupContent={t('workflow.panel.runThisStep')}
|
||||
popupClassName='mr-1'
|
||||
disabled={isSingleRunning}
|
||||
>
|
||||
<div
|
||||
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
if(isSingleRunning) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
_isSingleRun: false,
|
||||
_singleRunningStatus: undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
handleSingleRun()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{
|
||||
isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' />
|
||||
: <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
<NodePosition nodePosition={position} nodeWidth={width} nodeHeight={height}></NodePosition>
|
||||
<HelpLink nodeType={data.type} />
|
||||
<PanelOperator id={id} data={data} showHelpLink={false} />
|
||||
<div className='mx-3 h-3.5 w-[1px] bg-divider-regular' />
|
||||
<div
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||
onClick={() => handleNodeSelect(id, true)}
|
||||
>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2'>
|
||||
<DescriptionInput
|
||||
value={data.desc || ''}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='pl-4'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
onChange={setTabType}
|
||||
/>
|
||||
</div>
|
||||
<Split />
|
||||
</div>
|
||||
|
||||
{tabType === TabType.settings && (
|
||||
<>
|
||||
<div>
|
||||
{cloneElement(children as any, {
|
||||
id,
|
||||
data,
|
||||
panelProps: {
|
||||
getInputVars,
|
||||
toVarInputs,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
runInputDataRef,
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
<Split />
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!availableNextBlocks.length && (
|
||||
<div className='border-t-[0.5px] border-divider-regular p-4'>
|
||||
<div className='system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary'>
|
||||
{t('workflow.panel.nextStep').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='system-xs-regular mb-2 text-text-tertiary'>
|
||||
{t('workflow.panel.addNextStep')}
|
||||
</div>
|
||||
<NextStep selectedNode={{ id, data } as Node} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
{tabType === TabType.lastRun && (
|
||||
<LastRun
|
||||
appId={appDetail?.id || ''}
|
||||
nodeId={id}
|
||||
canSingleRun={isSupportSingleRun}
|
||||
runningStatus={runningStatus}
|
||||
isRunAfterSingleRun={isRunAfterSingleRun}
|
||||
updateNodeRunningStatus={updateNodeRunningStatus}
|
||||
onSingleRunClicked={handleSingleRun}
|
||||
nodeInfo={nodeInfo}
|
||||
singleRunResult={runResult!}
|
||||
isPaused={isPaused}
|
||||
{...passedLogParams}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(BasePanel)
|
||||
@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
import type { ResultPanelProps } from '@/app/components/workflow/run/result-panel'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import NoData from './no-data'
|
||||
import { useLastRun } from '@/service/use-workflow'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
type Props = {
|
||||
appId: string
|
||||
nodeId: string
|
||||
canSingleRun: boolean
|
||||
isRunAfterSingleRun: boolean
|
||||
updateNodeRunningStatus: (status: NodeRunningStatus) => void
|
||||
nodeInfo?: NodeTracing
|
||||
runningStatus?: NodeRunningStatus
|
||||
onSingleRunClicked: () => void
|
||||
singleRunResult?: NodeTracing
|
||||
isPaused?: boolean
|
||||
} & Partial<ResultPanelProps>
|
||||
|
||||
const LastRun: FC<Props> = ({
|
||||
appId,
|
||||
nodeId,
|
||||
canSingleRun,
|
||||
isRunAfterSingleRun,
|
||||
updateNodeRunningStatus,
|
||||
nodeInfo,
|
||||
runningStatus: oneStepRunRunningStatus,
|
||||
onSingleRunClicked,
|
||||
singleRunResult,
|
||||
isPaused,
|
||||
...otherResultPanelProps
|
||||
}) => {
|
||||
const isOneStepRunSucceed = oneStepRunRunningStatus === NodeRunningStatus.Succeeded
|
||||
const isOneStepRunFailed = oneStepRunRunningStatus === NodeRunningStatus.Failed
|
||||
// hide page and return to page would lost the oneStepRunRunningStatus
|
||||
const [hidePageOneStepFinishedStatus, setHidePageOneStepFinishedStatus] = React.useState<NodeRunningStatus | null>(null)
|
||||
const [pageHasHide, setPageHasHide] = useState(false)
|
||||
const [pageShowed, setPageShowed] = useState(false)
|
||||
|
||||
const hidePageOneStepRunFinished = [NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(hidePageOneStepFinishedStatus!)
|
||||
const canRunLastRun = !isRunAfterSingleRun || isOneStepRunSucceed || isOneStepRunFailed || (pageHasHide && hidePageOneStepRunFinished)
|
||||
const { data: lastRunResult, isFetching, error } = useLastRun(appId, nodeId, canRunLastRun)
|
||||
const isRunning = useMemo(() => {
|
||||
if(isPaused)
|
||||
return false
|
||||
|
||||
if(!isRunAfterSingleRun)
|
||||
return isFetching
|
||||
return [NodeRunningStatus.Running, NodeRunningStatus.NotStart].includes(oneStepRunRunningStatus!)
|
||||
}, [isFetching, isPaused, isRunAfterSingleRun, oneStepRunRunningStatus])
|
||||
|
||||
const noLastRun = (error as any)?.status === 404
|
||||
const runResult = (canRunLastRun ? lastRunResult : singleRunResult) || lastRunResult || {}
|
||||
|
||||
const resetHidePageStatus = useCallback(() => {
|
||||
setPageHasHide(false)
|
||||
setPageShowed(false)
|
||||
setHidePageOneStepFinishedStatus(null)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (pageShowed && hidePageOneStepFinishedStatus && (!oneStepRunRunningStatus || oneStepRunRunningStatus === NodeRunningStatus.NotStart)) {
|
||||
updateNodeRunningStatus(hidePageOneStepFinishedStatus)
|
||||
resetHidePageStatus()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(oneStepRunRunningStatus!))
|
||||
setHidePageOneStepFinishedStatus(oneStepRunRunningStatus!)
|
||||
}, [oneStepRunRunningStatus])
|
||||
|
||||
useEffect(() => {
|
||||
resetHidePageStatus()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodeId])
|
||||
|
||||
const handlePageVisibilityChange = useCallback(() => {
|
||||
if (document.visibilityState === 'hidden')
|
||||
setPageHasHide(true)
|
||||
else
|
||||
setPageShowed(true)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
document.addEventListener('visibilitychange', handlePageVisibilityChange)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handlePageVisibilityChange)
|
||||
}
|
||||
}, [handlePageVisibilityChange])
|
||||
|
||||
if (isFetching && !isRunAfterSingleRun) {
|
||||
return (
|
||||
<div className='flex h-0 grow flex-col items-center justify-center'>
|
||||
<RiLoader2Line className='size-4 animate-spin text-text-tertiary' />
|
||||
</div>)
|
||||
}
|
||||
|
||||
if (isRunning)
|
||||
return <ResultPanel status='running' showSteps={false} />
|
||||
|
||||
if (!isPaused && (noLastRun || !runResult)) {
|
||||
return (
|
||||
<NoData canSingleRun={canSingleRun} onSingleRun={onSingleRunClicked} />
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<ResultPanel
|
||||
{...runResult as any}
|
||||
{...otherResultPanelProps}
|
||||
status={isPaused ? NodeRunningStatus.Stopped : ((runResult as any).status || otherResultPanelProps.status)}
|
||||
total_tokens={(runResult as any)?.execution_metadata?.total_tokens || otherResultPanelProps?.total_tokens}
|
||||
created_by={(runResult as any)?.created_by_account?.created_by || otherResultPanelProps?.created_by}
|
||||
nodeInfo={nodeInfo}
|
||||
showSteps={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(LastRun)
|
||||
@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiPlayLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
canSingleRun: boolean
|
||||
onSingleRun: () => void
|
||||
}
|
||||
|
||||
const NoData: FC<Props> = ({
|
||||
canSingleRun,
|
||||
onSingleRun,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex h-0 grow flex-col items-center justify-center'>
|
||||
<ClockPlay className='h-8 w-8 text-text-quaternary' />
|
||||
<div className='system-xs-regular my-2 text-text-tertiary'>{t('workflow.debug.noData.description')}</div>
|
||||
{canSingleRun && (
|
||||
<Button
|
||||
className='flex'
|
||||
size='small'
|
||||
onClick={onSingleRun}
|
||||
>
|
||||
<RiPlayLine className='mr-1 h-3.5 w-3.5' />
|
||||
<div>{t('workflow.debug.noData.runThisNode')}</div>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(NoData)
|
||||
@ -0,0 +1,330 @@
|
||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
import type { Params as OneStepRunParams } from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { TabType } from '../tab'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import useStartSingleRunFormParams from '@/app/components/workflow/nodes/start/use-single-run-form-params'
|
||||
import useLLMSingleRunFormParams from '@/app/components/workflow/nodes/llm/use-single-run-form-params'
|
||||
import useKnowledgeRetrievalSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params'
|
||||
import useCodeSingleRunFormParams from '@/app/components/workflow/nodes/code/use-single-run-form-params'
|
||||
import useTemplateTransformSingleRunFormParams from '@/app/components/workflow/nodes/template-transform/use-single-run-form-params'
|
||||
import useQuestionClassifierSingleRunFormParams from '@/app/components/workflow/nodes/question-classifier/use-single-run-form-params'
|
||||
import useParameterExtractorSingleRunFormParams from '@/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params'
|
||||
import useHttpRequestSingleRunFormParams from '@/app/components/workflow/nodes/http/use-single-run-form-params'
|
||||
import useToolSingleRunFormParams from '@/app/components/workflow/nodes/tool/use-single-run-form-params'
|
||||
import useIterationSingleRunFormParams from '@/app/components/workflow/nodes/iteration/use-single-run-form-params'
|
||||
import useAgentSingleRunFormParams from '@/app/components/workflow/nodes/agent/use-single-run-form-params'
|
||||
import useDocExtractorSingleRunFormParams from '@/app/components/workflow/nodes/document-extractor/use-single-run-form-params'
|
||||
import useLoopSingleRunFormParams from '@/app/components/workflow/nodes/loop/use-single-run-form-params'
|
||||
import useIfElseSingleRunFormParams from '@/app/components/workflow/nodes/if-else/use-single-run-form-params'
|
||||
import useVariableAggregatorSingleRunFormParams from '@/app/components/workflow/nodes/variable-assigner/use-single-run-form-params'
|
||||
import useVariableAssignerSingleRunFormParams from '@/app/components/workflow/nodes/assigner/use-single-run-form-params'
|
||||
|
||||
import useToolGetDataForCheckMore from '@/app/components/workflow/nodes/tool/use-get-data-for-check-more'
|
||||
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||
|
||||
// import
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import {
|
||||
useNodesSyncDraft,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||
import { useInvalidLastRun } from '@/service/use-workflow'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.LLM]: useLLMSingleRunFormParams,
|
||||
[BlockEnum.KnowledgeRetrieval]: useKnowledgeRetrievalSingleRunFormParams,
|
||||
[BlockEnum.Code]: useCodeSingleRunFormParams,
|
||||
[BlockEnum.TemplateTransform]: useTemplateTransformSingleRunFormParams,
|
||||
[BlockEnum.QuestionClassifier]: useQuestionClassifierSingleRunFormParams,
|
||||
[BlockEnum.HttpRequest]: useHttpRequestSingleRunFormParams,
|
||||
[BlockEnum.Tool]: useToolSingleRunFormParams,
|
||||
[BlockEnum.ParameterExtractor]: useParameterExtractorSingleRunFormParams,
|
||||
[BlockEnum.Iteration]: useIterationSingleRunFormParams,
|
||||
[BlockEnum.Agent]: useAgentSingleRunFormParams,
|
||||
[BlockEnum.DocExtractor]: useDocExtractorSingleRunFormParams,
|
||||
[BlockEnum.Loop]: useLoopSingleRunFormParams,
|
||||
[BlockEnum.Start]: useStartSingleRunFormParams,
|
||||
[BlockEnum.IfElse]: useIfElseSingleRunFormParams,
|
||||
[BlockEnum.VariableAggregator]: useVariableAggregatorSingleRunFormParams,
|
||||
[BlockEnum.Assigner]: useVariableAssignerSingleRunFormParams,
|
||||
[BlockEnum.VariableAssigner]: undefined,
|
||||
[BlockEnum.End]: undefined,
|
||||
[BlockEnum.Answer]: undefined,
|
||||
[BlockEnum.ListFilter]: undefined,
|
||||
[BlockEnum.IterationStart]: undefined,
|
||||
[BlockEnum.LoopStart]: undefined,
|
||||
[BlockEnum.LoopEnd]: undefined,
|
||||
}
|
||||
|
||||
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
|
||||
return (params: any) => {
|
||||
return singleRunFormParamsHooks[nodeType]?.(params) || {}
|
||||
}
|
||||
}
|
||||
|
||||
const getDataForCheckMoreHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.Tool]: useToolGetDataForCheckMore,
|
||||
[BlockEnum.LLM]: undefined,
|
||||
[BlockEnum.KnowledgeRetrieval]: undefined,
|
||||
[BlockEnum.Code]: undefined,
|
||||
[BlockEnum.TemplateTransform]: undefined,
|
||||
[BlockEnum.QuestionClassifier]: undefined,
|
||||
[BlockEnum.HttpRequest]: undefined,
|
||||
[BlockEnum.ParameterExtractor]: undefined,
|
||||
[BlockEnum.Iteration]: undefined,
|
||||
[BlockEnum.Agent]: undefined,
|
||||
[BlockEnum.DocExtractor]: undefined,
|
||||
[BlockEnum.Loop]: undefined,
|
||||
[BlockEnum.Start]: undefined,
|
||||
[BlockEnum.IfElse]: undefined,
|
||||
[BlockEnum.VariableAggregator]: undefined,
|
||||
[BlockEnum.End]: undefined,
|
||||
[BlockEnum.Answer]: undefined,
|
||||
[BlockEnum.VariableAssigner]: undefined,
|
||||
[BlockEnum.ListFilter]: undefined,
|
||||
[BlockEnum.IterationStart]: undefined,
|
||||
[BlockEnum.Assigner]: undefined,
|
||||
[BlockEnum.LoopStart]: undefined,
|
||||
[BlockEnum.LoopEnd]: undefined,
|
||||
}
|
||||
|
||||
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
|
||||
return (id: string, payload: CommonNodeType<T>) => {
|
||||
return getDataForCheckMoreHooks[nodeType]?.({ id, payload }) || {
|
||||
getData: () => {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Params<T> = Omit<OneStepRunParams<T>, 'isRunAfterSingleRun'>
|
||||
const useLastRun = <T>({
|
||||
...oneStepRunParams
|
||||
}: Params<T>) => {
|
||||
const { conversationVars, systemVars, hasSetInspectVar } = useInspectVarsCrud()
|
||||
const blockType = oneStepRunParams.data.type
|
||||
const isStartNode = blockType === BlockEnum.Start
|
||||
const isIterationNode = blockType === BlockEnum.Iteration
|
||||
const isLoopNode = blockType === BlockEnum.Loop
|
||||
const isAggregatorNode = blockType === BlockEnum.VariableAggregator
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
getData: getDataForCheckMore,
|
||||
} = useGetDataForCheckMoreHooks<T>(blockType)(oneStepRunParams.id, oneStepRunParams.data)
|
||||
const [isRunAfterSingleRun, setIsRunAfterSingleRun] = useState(false)
|
||||
|
||||
const {
|
||||
id,
|
||||
data,
|
||||
} = oneStepRunParams
|
||||
const oneStepRunRes = useOneStepRun({
|
||||
...oneStepRunParams,
|
||||
iteratorInputKey: blockType === BlockEnum.Iteration ? `${id}.input_selector` : '',
|
||||
moreDataForCheckValid: getDataForCheckMore(),
|
||||
isRunAfterSingleRun,
|
||||
})
|
||||
|
||||
const {
|
||||
appId,
|
||||
hideSingleRun,
|
||||
handleRun: doCallRunApi,
|
||||
getInputVars,
|
||||
toVarInputs,
|
||||
varSelectorsToVarInputs,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
setRunInputData,
|
||||
showSingleRun,
|
||||
runResult,
|
||||
iterationRunResult,
|
||||
loopRunResult,
|
||||
setNodeRunning,
|
||||
checkValid,
|
||||
} = oneStepRunRes
|
||||
|
||||
const {
|
||||
nodeInfo,
|
||||
...singleRunParams
|
||||
} = useSingleRunFormParamsHooks(blockType)({
|
||||
id,
|
||||
payload: data,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
toVarInputs,
|
||||
varSelectorsToVarInputs,
|
||||
runResult,
|
||||
iterationRunResult,
|
||||
loopRunResult,
|
||||
})
|
||||
|
||||
const toSubmitData = useCallback((data: Record<string, any>) => {
|
||||
if(!isIterationNode && !isLoopNode)
|
||||
return data
|
||||
|
||||
const allVarObject = singleRunParams?.allVarObject || {}
|
||||
const formattedData: Record<string, any> = {}
|
||||
Object.keys(allVarObject).forEach((key) => {
|
||||
const [varSectorStr, nodeId] = key.split(DELIMITER)
|
||||
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
|
||||
})
|
||||
if(isIterationNode) {
|
||||
const iteratorInputKey = `${id}.input_selector`
|
||||
formattedData[iteratorInputKey] = data[iteratorInputKey]
|
||||
}
|
||||
return formattedData
|
||||
}, [isIterationNode, isLoopNode, singleRunParams?.allVarObject, id])
|
||||
|
||||
const callRunApi = (data: Record<string, any>, cb?: () => void) => {
|
||||
handleSyncWorkflowDraft(true, true, {
|
||||
onSuccess() {
|
||||
doCallRunApi(toSubmitData(data))
|
||||
cb?.()
|
||||
},
|
||||
})
|
||||
}
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { setInitShowLastRunTab } = workflowStore.getState()
|
||||
const initShowLastRunTab = useStore(s => s.initShowLastRunTab)
|
||||
const [tabType, setTabType] = useState<TabType>(initShowLastRunTab ? TabType.lastRun : TabType.settings)
|
||||
useEffect(() => {
|
||||
if(initShowLastRunTab)
|
||||
setTabType(TabType.lastRun)
|
||||
|
||||
setInitShowLastRunTab(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initShowLastRunTab])
|
||||
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||
|
||||
const handleRunWithParams = async (data: Record<string, any>) => {
|
||||
const { isValid } = checkValid()
|
||||
if(!isValid)
|
||||
return
|
||||
setNodeRunning()
|
||||
setIsRunAfterSingleRun(true)
|
||||
setTabType(TabType.lastRun)
|
||||
callRunApi(data, () => {
|
||||
invalidLastRun()
|
||||
})
|
||||
hideSingleRun()
|
||||
}
|
||||
|
||||
const handleTabClicked = useCallback((type: TabType) => {
|
||||
setIsRunAfterSingleRun(false)
|
||||
setTabType(type)
|
||||
}, [])
|
||||
|
||||
const getExistVarValuesInForms = (forms: FormProps[]) => {
|
||||
if (!forms || forms.length === 0)
|
||||
return []
|
||||
|
||||
const valuesArr = forms.map((form) => {
|
||||
const values: Record<string, boolean> = {}
|
||||
form.inputs.forEach(({ variable, getVarValueFromDependent }) => {
|
||||
const isGetValueFromDependent = getVarValueFromDependent || !variable.includes('.')
|
||||
if(isGetValueFromDependent && !singleRunParams?.getDependentVar)
|
||||
return
|
||||
|
||||
const selector = isGetValueFromDependent ? (singleRunParams?.getDependentVar(variable) || []) : variable.slice(1, -1).split('.')
|
||||
if(!selector || selector.length === 0)
|
||||
return
|
||||
const [nodeId, varName] = selector.slice(0, 2)
|
||||
if(!isStartNode && nodeId === id) { // inner vars like loop vars
|
||||
values[variable] = true
|
||||
return
|
||||
}
|
||||
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||
if (inspectVarValue)
|
||||
values[variable] = true
|
||||
})
|
||||
return values
|
||||
})
|
||||
return valuesArr
|
||||
}
|
||||
|
||||
const isAllVarsHasValue = (vars?: ValueSelector[]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
return true
|
||||
return vars.every((varItem) => {
|
||||
const [nodeId, varName] = varItem.slice(0, 2)
|
||||
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||
return inspectVarValue
|
||||
})
|
||||
}
|
||||
|
||||
const isSomeVarsHasValue = (vars?: ValueSelector[]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
return true
|
||||
return vars.some((varItem) => {
|
||||
const [nodeId, varName] = varItem.slice(0, 2)
|
||||
const inspectVarValue = hasSetInspectVar(nodeId, varName, systemVars, conversationVars) // also detect system var , env and conversation var
|
||||
return inspectVarValue
|
||||
})
|
||||
}
|
||||
const getFilteredExistVarForms = (forms: FormProps[]) => {
|
||||
if (!forms || forms.length === 0)
|
||||
return []
|
||||
|
||||
const existVarValuesInForms = getExistVarValuesInForms(forms)
|
||||
|
||||
const res = forms.map((form, i) => {
|
||||
const existVarValuesInForm = existVarValuesInForms[i]
|
||||
const newForm = { ...form }
|
||||
const inputs = form.inputs.filter((input) => {
|
||||
return !(input.variable in existVarValuesInForm)
|
||||
})
|
||||
newForm.inputs = inputs
|
||||
return newForm
|
||||
}).filter(form => form.inputs.length > 0)
|
||||
return res
|
||||
}
|
||||
|
||||
const checkAggregatorVarsSet = (vars: ValueSelector[][]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
return true
|
||||
// in each group, at last one set is ok
|
||||
return vars.every((varItem) => {
|
||||
return isSomeVarsHasValue(varItem)
|
||||
})
|
||||
}
|
||||
|
||||
const handleSingleRun = () => {
|
||||
const { isValid } = checkValid()
|
||||
if(!isValid)
|
||||
return
|
||||
const vars = singleRunParams?.getDependentVars?.()
|
||||
// no need to input params
|
||||
if (isAggregatorNode ? checkAggregatorVarsSet(vars) : isAllVarsHasValue(vars)) {
|
||||
callRunApi({}, async () => {
|
||||
setIsRunAfterSingleRun(true)
|
||||
setNodeRunning()
|
||||
invalidLastRun()
|
||||
setTabType(TabType.lastRun)
|
||||
})
|
||||
}
|
||||
else {
|
||||
showSingleRun()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...oneStepRunRes,
|
||||
tabType,
|
||||
isRunAfterSingleRun,
|
||||
setTabType: handleTabClicked,
|
||||
singleRunParams,
|
||||
nodeInfo,
|
||||
setRunInputData,
|
||||
handleSingleRun,
|
||||
handleRunWithParams,
|
||||
getExistVarValuesInForms,
|
||||
getFilteredExistVarForms,
|
||||
}
|
||||
}
|
||||
|
||||
export default useLastRun
|
||||
@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import TabHeader from '@/app/components/base/tab-header'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export enum TabType {
|
||||
settings = 'settings',
|
||||
lastRun = 'lastRun',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
value: TabType,
|
||||
onChange: (value: TabType) => void
|
||||
}
|
||||
|
||||
const Tab: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<TabHeader
|
||||
items={[
|
||||
{ id: TabType.settings, name: t('workflow.debug.settingsTab').toLocaleUpperCase() },
|
||||
{ id: TabType.lastRun, name: t('workflow.debug.lastRunTab').toLocaleUpperCase() },
|
||||
]}
|
||||
itemClassName='ml-0'
|
||||
value={value}
|
||||
onChange={onChange as any}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(Tab)
|
||||
@ -1,214 +0,0 @@
|
||||
import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NextStep from './components/next-step'
|
||||
import PanelOperator from './components/panel-operator'
|
||||
import HelpLink from './components/help-link'
|
||||
import NodePosition from './components/node-position'
|
||||
import {
|
||||
DescriptionInput,
|
||||
TitleInput,
|
||||
} from './components/title-description-input'
|
||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||
import RetryOnPanel from './components/retry/retry-on-panel'
|
||||
import { useResizePanel } from './hooks/use-resize-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import {
|
||||
WorkflowHistoryEvent,
|
||||
useAvailableBlocks,
|
||||
useNodeDataUpdate,
|
||||
useNodesInteractions,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useToolIcon,
|
||||
useWorkflow,
|
||||
useWorkflowHistory,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
type BasePanelProps = {
|
||||
children: ReactNode
|
||||
} & Node
|
||||
|
||||
const BasePanel: FC<BasePanelProps> = ({
|
||||
id,
|
||||
data,
|
||||
children,
|
||||
position,
|
||||
width,
|
||||
height,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
})))
|
||||
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const {
|
||||
setPanelWidth,
|
||||
} = useWorkflow()
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
const toolIcon = useToolIcon(data)
|
||||
|
||||
const handleResize = useCallback((width: number) => {
|
||||
setPanelWidth(width)
|
||||
}, [setPanelWidth])
|
||||
|
||||
const {
|
||||
triggerRef,
|
||||
containerRef,
|
||||
} = useResizePanel({
|
||||
direction: 'horizontal',
|
||||
triggerDirection: 'left',
|
||||
minWidth: 420,
|
||||
maxWidth: 720,
|
||||
onResize: handleResize,
|
||||
})
|
||||
|
||||
const { saveStateToHistory } = useWorkflowHistory()
|
||||
|
||||
const {
|
||||
handleNodeDataUpdate,
|
||||
handleNodeDataUpdateWithSyncDraft,
|
||||
} = useNodeDataUpdate()
|
||||
|
||||
const handleTitleBlur = useCallback((title: string) => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||
const handleDescriptionChange = useCallback((desc: string) => {
|
||||
handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-2 h-full',
|
||||
showMessageLogModal && '!absolute -top-[5px] right-[416px] z-0 !mr-0 w-[384px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
||||
)}>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className='absolute -left-2 top-1/2 h-6 w-3 -translate-y-1/2 cursor-col-resize resize-x'>
|
||||
<div className='h-6 w-1 rounded-sm bg-divider-regular'></div>
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('h-full rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
style={{
|
||||
width: `${panelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<div className='sticky top-0 z-10 border-b-[0.5px] border-divider-regular bg-components-panel-bg'>
|
||||
<div className='flex items-center px-4 pb-1 pt-4'>
|
||||
<BlockIcon
|
||||
className='mr-1 shrink-0'
|
||||
type={data.type}
|
||||
toolIcon={toolIcon}
|
||||
size='md'
|
||||
/>
|
||||
<TitleInput
|
||||
value={data.title || ''}
|
||||
onBlur={handleTitleBlur}
|
||||
/>
|
||||
<div className='flex shrink-0 items-center text-text-tertiary'>
|
||||
{
|
||||
canRunBySingle(data.type) && !nodesReadOnly && (
|
||||
<Tooltip
|
||||
popupContent={t('workflow.panel.runThisStep')}
|
||||
popupClassName='mr-1'
|
||||
>
|
||||
<div
|
||||
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
handleNodeDataUpdate({ id, data: { _isSingleRun: true } })
|
||||
handleSyncWorkflowDraft(true)
|
||||
}}
|
||||
>
|
||||
<RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
<NodePosition nodePosition={position} nodeWidth={width} nodeHeight={height}></NodePosition>
|
||||
<HelpLink nodeType={data.type} />
|
||||
<PanelOperator id={id} data={data} showHelpLink={false} />
|
||||
<div className='mx-3 h-3.5 w-[1px] bg-divider-regular' />
|
||||
<div
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center'
|
||||
onClick={() => handleNodeSelect(id, true)}
|
||||
>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-2'>
|
||||
<DescriptionInput
|
||||
value={data.desc || ''}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{cloneElement(children as any, { id, data })}
|
||||
</div>
|
||||
<Split />
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!availableNextBlocks.length && (
|
||||
<div className='border-t-[0.5px] border-divider-regular p-4'>
|
||||
<div className='system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary'>
|
||||
{t('workflow.panel.nextStep').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='system-xs-regular mb-2 text-text-tertiary'>
|
||||
{t('workflow.panel.addNextStep')}
|
||||
</div>
|
||||
<NextStep selectedNode={{ id, data } as Node} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(BasePanel)
|
||||
@ -0,0 +1,90 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useMemo } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import type { AgentNodeType } from './types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import { useStrategyInfo } from './use-config'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: AgentNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
runResult: NodeTracing
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { inputs } = useNodeCrud<AgentNodeType>(id, payload)
|
||||
|
||||
const formData = useMemo(() => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(inputs.agent_parameters || {}).map(([key, value]) => {
|
||||
return [key, value.value]
|
||||
}),
|
||||
)
|
||||
}, [inputs.agent_parameters])
|
||||
|
||||
const {
|
||||
strategy: currentStrategy,
|
||||
} = useStrategyInfo(
|
||||
inputs.agent_strategy_provider_name,
|
||||
inputs.agent_strategy_name,
|
||||
)
|
||||
|
||||
const allVarStrArr = (() => {
|
||||
const arr = currentStrategy?.parameters.filter(item => item.type === 'string').map((item) => {
|
||||
return formData[item.name]
|
||||
}) || []
|
||||
return arr
|
||||
})()
|
||||
|
||||
const varInputs = getInputVars?.(allVarStrArr)
|
||||
|
||||
const forms = useMemo(() => {
|
||||
const forms: FormProps[] = []
|
||||
|
||||
if (varInputs!.length > 0) {
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||
inputs: varInputs!,
|
||||
values: runInputData,
|
||||
onChange: setRunInputData,
|
||||
},
|
||||
)
|
||||
}
|
||||
return forms
|
||||
}, [runInputData, setRunInputData, t, varInputs])
|
||||
|
||||
const nodeInfo = useMemo(() => {
|
||||
if (!runResult)
|
||||
return
|
||||
return formatTracing([runResult], t)[0]
|
||||
}, [runResult, t])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
nodeInfo,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,55 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
import { useMemo } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { type AssignerNodeType, WriteMode } from './types'
|
||||
import { writeModeTypesNum } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: AssignerNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
varSelectorsToVarInputs,
|
||||
}: Params) => {
|
||||
const { inputs } = useNodeCrud<AssignerNodeType>(id, payload)
|
||||
|
||||
const vars = inputs.items.filter((item) => {
|
||||
return item.operation !== WriteMode.clear && item.operation !== WriteMode.set
|
||||
&& item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast
|
||||
&& !writeModeTypesNum.includes(item.operation)
|
||||
}).map(item => item.value as ValueSelector)
|
||||
|
||||
const forms = useMemo(() => {
|
||||
const varInputs = varSelectorsToVarInputs(vars)
|
||||
|
||||
return [
|
||||
{
|
||||
inputs: varInputs,
|
||||
values: runInputData,
|
||||
onChange: setRunInputData,
|
||||
},
|
||||
]
|
||||
}, [runInputData, setRunInputData, varSelectorsToVarInputs, vars])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return vars
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,65 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import type { CodeNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: CodeNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
toVarInputs,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { inputs } = useNodeCrud<CodeNodeType>(id, payload)
|
||||
|
||||
const varInputs = toVarInputs(inputs.variables)
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: varInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
}, [inputVarValues, setInputVarValues, varInputs])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return payload.variables.map(v => v.value_selector)
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
const varItem = payload.variables.find(v => v.variable === variable)
|
||||
if (varItem)
|
||||
return varItem.value_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,64 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { DocExtractorNodeType } from './types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.docExtractor'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: DocExtractorNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const files = runInputData.files
|
||||
const setFiles = useCallback((newFiles: []) => {
|
||||
setRunInputData({
|
||||
...runInputData,
|
||||
files: newFiles,
|
||||
})
|
||||
}, [runInputData, setRunInputData])
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.inputVar`)!,
|
||||
variable: 'files',
|
||||
type: InputVarType.multiFiles,
|
||||
required: true,
|
||||
}],
|
||||
values: { files },
|
||||
onChange: (keyValue: Record<string, any>) => setFiles(keyValue.files),
|
||||
},
|
||||
]
|
||||
}, [files, setFiles, t])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return [payload.variable_selector]
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === 'files')
|
||||
return payload.variable_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,74 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import type { HttpNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: HttpNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { inputs } = useNodeCrud<HttpNodeType>(id, payload)
|
||||
|
||||
const fileVarInputs = useMemo(() => {
|
||||
if (!Array.isArray(inputs.body.data))
|
||||
return ''
|
||||
|
||||
const res = inputs.body.data
|
||||
.filter(item => item.file?.length)
|
||||
.map(item => item.file ? `{{#${item.file.join('.')}#}}` : '')
|
||||
.join(' ')
|
||||
return res
|
||||
}, [inputs.body.data])
|
||||
const varInputs = getInputVars([
|
||||
inputs.url,
|
||||
inputs.headers,
|
||||
inputs.params,
|
||||
typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data?.map(item => item.value).join(''),
|
||||
fileVarInputs,
|
||||
])
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: varInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
}, [inputVarValues, setInputVarValues, varInputs])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,166 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback } from 'react'
|
||||
import type { CaseItem, Condition, IfElseNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: IfElseNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
getInputVars,
|
||||
varSelectorsToVarInputs,
|
||||
}: Params) => {
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const getVarSelectorsFromCase = (caseItem: CaseItem): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (caseItem.conditions && caseItem.conditions.length) {
|
||||
caseItem.conditions.forEach((condition) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getVarSelectorsFromCondition = (condition: Condition) => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (condition.variable_selector)
|
||||
vars.push(condition.variable_selector)
|
||||
|
||||
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||
vars.push(...getVarSelectorsFromCase(condition.sub_variable_condition))
|
||||
return vars
|
||||
}
|
||||
|
||||
const getInputVarsFromCase = (caseItem: CaseItem): InputVar[] => {
|
||||
const vars: InputVar[] = []
|
||||
if (caseItem.conditions && caseItem.conditions.length) {
|
||||
caseItem.conditions.forEach((condition) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const conditionVars = getInputVarsFromConditionValue(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getInputVarsFromConditionValue = (condition: Condition): InputVar[] => {
|
||||
const vars: InputVar[] = []
|
||||
if (condition.value && typeof condition.value === 'string') {
|
||||
const inputVars = getInputVars([condition.value])
|
||||
vars.push(...inputVars)
|
||||
}
|
||||
|
||||
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||
vars.push(...getInputVarsFromCase(condition.sub_variable_condition))
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
const forms = (() => {
|
||||
const allInputs: ValueSelector[] = []
|
||||
const inputVarsFromValue: InputVar[] = []
|
||||
if (payload.cases && payload.cases.length) {
|
||||
payload.cases.forEach((caseItem) => {
|
||||
const caseVars = getVarSelectorsFromCase(caseItem)
|
||||
allInputs.push(...caseVars)
|
||||
inputVarsFromValue.push(...getInputVarsFromCase(caseItem))
|
||||
})
|
||||
}
|
||||
|
||||
if (payload.conditions && payload.conditions.length) {
|
||||
payload.conditions.forEach((condition) => {
|
||||
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||
allInputs.push(...conditionVars)
|
||||
inputVarsFromValue.push(...getInputVarsFromConditionValue(condition))
|
||||
})
|
||||
}
|
||||
|
||||
const varInputs = [...varSelectorsToVarInputs(allInputs), ...inputVarsFromValue]
|
||||
// remove duplicate inputs
|
||||
const existVarsKey: Record<string, boolean> = {}
|
||||
const uniqueVarInputs: InputVar[] = []
|
||||
varInputs.forEach((input) => {
|
||||
if(!input)
|
||||
return
|
||||
if (!existVarsKey[input.variable]) {
|
||||
existVarsKey[input.variable] = true
|
||||
uniqueVarInputs.push(input)
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
inputs: uniqueVarInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
})()
|
||||
|
||||
const getVarFromCaseItem = (caseItem: CaseItem): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (caseItem.conditions && caseItem.conditions.length) {
|
||||
caseItem.conditions.forEach((condition) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const conditionVars = getVarFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
const getVarFromCondition = (condition: Condition): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (condition.variable_selector)
|
||||
vars.push(condition.variable_selector)
|
||||
|
||||
if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||
vars.push(...getVarFromCaseItem(condition.sub_variable_condition))
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVars = () => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (payload.cases && payload.cases.length) {
|
||||
payload.cases.forEach((caseItem) => {
|
||||
const caseVars = getVarFromCaseItem(caseItem)
|
||||
vars.push(...caseVars)
|
||||
})
|
||||
}
|
||||
|
||||
if (payload.conditions && payload.conditions.length) {
|
||||
payload.conditions.forEach((condition) => {
|
||||
const conditionVars = getVarFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,154 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { IterationNodeType } from './types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useIsNodeInIteration, useWorkflow } from '../../hooks'
|
||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils'
|
||||
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: IterationNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
iterationRunResult: NodeTracing[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
toVarInputs,
|
||||
setRunInputData,
|
||||
iterationRunResult,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { isNodeInIteration } = useIsNodeInIteration(id)
|
||||
|
||||
const { getIterationNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const iterationChildrenNodes = getIterationNodeChildren(id)
|
||||
const beforeNodes = getBeforeNodesInSameBranch(id)
|
||||
const canChooseVarNodes = [...beforeNodes, ...iterationChildrenNodes]
|
||||
|
||||
const iteratorInputKey = `${id}.input_selector`
|
||||
const iterator = runInputData[iteratorInputKey]
|
||||
const setIterator = useCallback((newIterator: string[]) => {
|
||||
setRunInputData({
|
||||
...runInputData,
|
||||
[iteratorInputKey]: newIterator,
|
||||
})
|
||||
}, [iteratorInputKey, runInputData, setRunInputData])
|
||||
|
||||
const { usedOutVars, allVarObject } = (() => {
|
||||
const vars: ValueSelector[] = []
|
||||
const varObjs: Record<string, boolean> = {}
|
||||
const allVarObject: Record<string, {
|
||||
inSingleRunPassedKey: string
|
||||
}> = {}
|
||||
iterationChildrenNodes.forEach((node) => {
|
||||
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
||||
nodeVars.forEach((varSelector) => {
|
||||
if (varSelector[0] === id) { // skip iteration node itself variable: item, index
|
||||
return
|
||||
}
|
||||
const isInIteration = isNodeInIteration(varSelector[0])
|
||||
if (isInIteration) // not pass iteration inner variable
|
||||
return
|
||||
|
||||
const varSectorStr = varSelector.join('.')
|
||||
if (!varObjs[varSectorStr]) {
|
||||
varObjs[varSectorStr] = true
|
||||
vars.push(varSelector)
|
||||
}
|
||||
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
||||
if (typeof passToServerKeys === 'string')
|
||||
passToServerKeys = [passToServerKeys]
|
||||
|
||||
passToServerKeys.forEach((key: string, index: number) => {
|
||||
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
||||
inSingleRunPassedKey: key,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
const res = toVarInputs(vars.map((item) => {
|
||||
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
||||
return {
|
||||
label: {
|
||||
nodeType: varInfo?.data.type,
|
||||
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
},
|
||||
variable: `${item.join('.')}`,
|
||||
value_selector: item,
|
||||
}
|
||||
}))
|
||||
return {
|
||||
usedOutVars: res,
|
||||
allVarObject,
|
||||
}
|
||||
})()
|
||||
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: [...usedOutVars],
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
{
|
||||
label: t(`${i18nPrefix}.input`)!,
|
||||
inputs: [{
|
||||
label: '',
|
||||
variable: iteratorInputKey,
|
||||
type: InputVarType.iterator,
|
||||
required: false,
|
||||
getVarValueFromDependent: true,
|
||||
isFileItem: payload.iterator_input_type === VarType.arrayFile,
|
||||
}],
|
||||
values: { [iteratorInputKey]: iterator },
|
||||
onChange: (keyValue: Record<string, any>) => setIterator(keyValue[iteratorInputKey]),
|
||||
},
|
||||
]
|
||||
}, [inputVarValues, iterator, iteratorInputKey, payload.iterator_input_type, setInputVarValues, setIterator, t, usedOutVars])
|
||||
|
||||
const nodeInfo = formatTracing(iterationRunResult, t)[0]
|
||||
|
||||
const getDependentVars = () => {
|
||||
return [payload.iterator_selector]
|
||||
}
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === iteratorInputKey)
|
||||
return payload.iterator_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
nodeInfo,
|
||||
allVarObject,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,63 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { KnowledgeRetrievalNodeType } from './types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.knowledgeRetrieval'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: KnowledgeRetrievalNodeType
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const query = runInputData.query
|
||||
const setQuery = useCallback((newQuery: string) => {
|
||||
setRunInputData({
|
||||
...runInputData,
|
||||
query: newQuery,
|
||||
})
|
||||
}, [runInputData, setRunInputData])
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.queryVariable`)!,
|
||||
variable: 'query',
|
||||
type: InputVarType.paragraph,
|
||||
required: true,
|
||||
}],
|
||||
values: { query },
|
||||
onChange: (keyValue: Record<string, any>) => setQuery(keyValue.query),
|
||||
},
|
||||
]
|
||||
}, [query, setQuery, t])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return [payload.query_variable_selector]
|
||||
}
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === 'query')
|
||||
return payload.query_variable_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,198 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import type { InputVar, PromptItem, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { LLMNodeType } from './types'
|
||||
import { EditionType } from '../../types'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { useIsChatMode } from '../../hooks'
|
||||
import { useCallback } from 'react'
|
||||
import useConfigVision from '../../hooks/use-config-vision'
|
||||
import { noop } from 'lodash-es'
|
||||
import { findVariableWhenOnLLMVision } from '../utils'
|
||||
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.llm'
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: LLMNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
toVarInputs,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { inputs } = useNodeCrud<LLMNodeType>(id, payload)
|
||||
const getVarInputs = getInputVars
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const contexts = runInputData['#context#']
|
||||
const setContexts = useCallback((newContexts: string[]) => {
|
||||
setRunInputData?.({
|
||||
...runInputDataRef.current,
|
||||
'#context#': newContexts,
|
||||
})
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const visionFiles = runInputData['#files#']
|
||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||
setRunInputData?.({
|
||||
...runInputDataRef.current,
|
||||
'#files#': newFiles,
|
||||
})
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
// model
|
||||
const model = inputs.model
|
||||
const modelMode = inputs.model?.mode
|
||||
const isChatModel = modelMode === 'chat'
|
||||
const {
|
||||
isVisionModel,
|
||||
} = useConfigVision(model, {
|
||||
payload: inputs.vision,
|
||||
onChange: noop,
|
||||
})
|
||||
|
||||
const isShowVars = (() => {
|
||||
if (isChatModel)
|
||||
return (inputs.prompt_template as PromptItem[]).some(item => item.edition_type === EditionType.jinja2)
|
||||
|
||||
return (inputs.prompt_template as PromptItem).edition_type === EditionType.jinja2
|
||||
})()
|
||||
|
||||
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const {
|
||||
availableVars,
|
||||
} = useAvailableVarList(id, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: filterMemoryPromptVar,
|
||||
})
|
||||
|
||||
const allVarStrArr = (() => {
|
||||
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).filter(item => item.edition_type !== EditionType.jinja2).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
|
||||
if (isChatMode && isChatModel && !!inputs.memory) {
|
||||
arr.push('{{#sys.query#}}')
|
||||
arr.push(inputs.memory.query_prompt_template)
|
||||
}
|
||||
|
||||
return arr
|
||||
})()
|
||||
const varInputs = (() => {
|
||||
const vars = getVarInputs(allVarStrArr) || []
|
||||
if (isShowVars)
|
||||
return [...vars, ...(toVarInputs ? (toVarInputs(inputs.prompt_config?.jinja2_variables || [])) : [])]
|
||||
|
||||
return vars
|
||||
})()
|
||||
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.filter(key => !['#context#', '#files#'].includes(key))
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
const newVars = {
|
||||
...newPayload,
|
||||
'#context#': runInputDataRef.current['#context#'],
|
||||
'#files#': runInputDataRef.current['#files#'],
|
||||
}
|
||||
setRunInputData?.(newVars)
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const forms = (() => {
|
||||
const forms: FormProps[] = []
|
||||
|
||||
if (varInputs.length > 0) {
|
||||
forms.push(
|
||||
{
|
||||
label: t(`${i18nPrefix}.singleRun.variable`)!,
|
||||
inputs: varInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (inputs.context?.variable_selector && inputs.context?.variable_selector.length > 0) {
|
||||
forms.push(
|
||||
{
|
||||
label: t(`${i18nPrefix}.context`)!,
|
||||
inputs: [{
|
||||
label: '',
|
||||
variable: '#context#',
|
||||
type: InputVarType.contexts,
|
||||
required: false,
|
||||
}],
|
||||
values: { '#context#': contexts },
|
||||
onChange: keyValue => setContexts(keyValue['#context#']),
|
||||
},
|
||||
)
|
||||
}
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVars)
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t(`${i18nPrefix}.vision`)!,
|
||||
inputs: [{
|
||||
label: currentVariable?.variable as any,
|
||||
variable: '#files#',
|
||||
type: currentVariable?.formType as any,
|
||||
required: false,
|
||||
}],
|
||||
values: { '#files#': visionFiles },
|
||||
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
||||
},
|
||||
)
|
||||
}
|
||||
return forms
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
const contextVar = payload.context.variable_selector
|
||||
const vars = [...promptVars, contextVar]
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const visionVar = payload.vision.configs.variable_selector
|
||||
vars.push(visionVar)
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === '#context#')
|
||||
return payload.context.variable_selector
|
||||
|
||||
if(variable === '#files#')
|
||||
return payload.vision.configs?.variable_selector
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,221 @@
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import formatTracing from '@/app/components/workflow/run/utils/format-log'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useIsNodeInLoop, useWorkflow } from '../../hooks'
|
||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils'
|
||||
import type { InputVar, ValueSelector, Variable } from '../../types'
|
||||
import type { CaseItem, Condition, LoopNodeType } from './types'
|
||||
import { ValueType } from '@/app/components/workflow/types'
|
||||
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
payload: LoopNodeType
|
||||
runInputData: Record<string, any>
|
||||
runResult: NodeTracing
|
||||
loopRunResult: NodeTracing[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||
}
|
||||
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
runResult,
|
||||
loopRunResult,
|
||||
setRunInputData,
|
||||
toVarInputs,
|
||||
varSelectorsToVarInputs,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { isNodeInLoop } = useIsNodeInLoop(id)
|
||||
|
||||
const { getLoopNodeChildren, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const loopChildrenNodes = getLoopNodeChildren(id)
|
||||
const beforeNodes = getBeforeNodesInSameBranch(id)
|
||||
const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes]
|
||||
|
||||
const { usedOutVars, allVarObject } = (() => {
|
||||
const vars: ValueSelector[] = []
|
||||
const varObjs: Record<string, boolean> = {}
|
||||
const allVarObject: Record<string, {
|
||||
inSingleRunPassedKey: string
|
||||
}> = {}
|
||||
loopChildrenNodes.forEach((node) => {
|
||||
const nodeVars = getNodeUsedVars(node).filter(item => item && item.length > 0)
|
||||
nodeVars.forEach((varSelector) => {
|
||||
if (varSelector[0] === id) { // skip loop node itself variable: item, index
|
||||
return
|
||||
}
|
||||
const isInLoop = isNodeInLoop(varSelector[0])
|
||||
if (isInLoop) // not pass loop inner variable
|
||||
return
|
||||
|
||||
const varSectorStr = varSelector.join('.')
|
||||
if (!varObjs[varSectorStr]) {
|
||||
varObjs[varSectorStr] = true
|
||||
vars.push(varSelector)
|
||||
}
|
||||
let passToServerKeys = getNodeUsedVarPassToServerKey(node, varSelector)
|
||||
if (typeof passToServerKeys === 'string')
|
||||
passToServerKeys = [passToServerKeys]
|
||||
|
||||
passToServerKeys.forEach((key: string, index: number) => {
|
||||
allVarObject[[varSectorStr, node.id, index].join(DELIMITER)] = {
|
||||
inSingleRunPassedKey: key,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const res = toVarInputs(vars.map((item) => {
|
||||
const varInfo = getNodeInfoById(canChooseVarNodes, item[0])
|
||||
return {
|
||||
label: {
|
||||
nodeType: varInfo?.data.type,
|
||||
nodeName: varInfo?.data.title || canChooseVarNodes[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
},
|
||||
variable: `${item.join('.')}`,
|
||||
value_selector: item,
|
||||
}
|
||||
}))
|
||||
return {
|
||||
usedOutVars: res,
|
||||
allVarObject,
|
||||
}
|
||||
})()
|
||||
|
||||
const nodeInfo = useMemo(() => {
|
||||
const formattedNodeInfo = formatTracing(loopRunResult, t)[0]
|
||||
|
||||
if (runResult && formattedNodeInfo) {
|
||||
return {
|
||||
...formattedNodeInfo,
|
||||
execution_metadata: {
|
||||
...runResult.execution_metadata,
|
||||
...formattedNodeInfo.execution_metadata,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return formattedNodeInfo
|
||||
}, [runResult, loopRunResult, t])
|
||||
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const getVarSelectorsFromCase = (caseItem: CaseItem): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (caseItem.conditions && caseItem.conditions.length) {
|
||||
caseItem.conditions.forEach((condition) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const conditionVars = getVarSelectorsFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getVarSelectorsFromCondition = (condition: Condition) => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (condition.variable_selector)
|
||||
vars.push(condition.variable_selector)
|
||||
|
||||
if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||
vars.push(...getVarSelectorsFromCase(condition.sub_variable_condition))
|
||||
return vars
|
||||
}
|
||||
|
||||
const forms = (() => {
|
||||
const allInputs: ValueSelector[] = []
|
||||
payload.break_conditions?.forEach((condition) => {
|
||||
const vars = getVarSelectorsFromCondition(condition)
|
||||
allInputs.push(...vars)
|
||||
})
|
||||
|
||||
payload.loop_variables?.forEach((loopVariable) => {
|
||||
if(loopVariable.value_type === ValueType.variable)
|
||||
allInputs.push(loopVariable.value)
|
||||
})
|
||||
const inputVarsFromValue: InputVar[] = []
|
||||
const varInputs = [...varSelectorsToVarInputs(allInputs), ...inputVarsFromValue]
|
||||
|
||||
const existVarsKey: Record<string, boolean> = {}
|
||||
const uniqueVarInputs: InputVar[] = []
|
||||
varInputs.forEach((input) => {
|
||||
if(!input)
|
||||
return
|
||||
if (!existVarsKey[input.variable]) {
|
||||
existVarsKey[input.variable] = true
|
||||
uniqueVarInputs.push(input)
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
inputs: [...usedOutVars, ...uniqueVarInputs],
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
})()
|
||||
|
||||
const getVarFromCaseItem = (caseItem: CaseItem): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (caseItem.conditions && caseItem.conditions.length) {
|
||||
caseItem.conditions.forEach((condition) => {
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const conditionVars = getVarFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getVarFromCondition = (condition: Condition): ValueSelector[] => {
|
||||
const vars: ValueSelector[] = []
|
||||
if (condition.variable_selector)
|
||||
vars.push(condition.variable_selector)
|
||||
|
||||
if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length)
|
||||
vars.push(...getVarFromCaseItem(condition.sub_variable_condition))
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVars = () => {
|
||||
const vars: ValueSelector[] = usedOutVars.map(item => item.variable.split('.'))
|
||||
payload.break_conditions?.forEach((condition) => {
|
||||
const conditionVars = getVarFromCondition(condition)
|
||||
vars.push(...conditionVars)
|
||||
})
|
||||
payload.loop_variables?.forEach((loopVariable) => {
|
||||
if(loopVariable.value_type === ValueType.variable)
|
||||
vars.push(loopVariable.value)
|
||||
})
|
||||
const hasFilterLoopVars = vars.filter(item => item[0] !== id)
|
||||
return hasFilterLoopVars
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
nodeInfo,
|
||||
allVarObject,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,148 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { ParameterExtractorNodeType } from './types'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { useCallback } from 'react'
|
||||
import useConfigVision from '../../hooks/use-config-vision'
|
||||
import { noop } from 'lodash-es'
|
||||
import { findVariableWhenOnLLMVision } from '../utils'
|
||||
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: ParameterExtractorNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { inputs } = useNodeCrud<ParameterExtractorNodeType>(id, payload)
|
||||
|
||||
const model = inputs.model
|
||||
|
||||
const {
|
||||
isVisionModel,
|
||||
} = useConfigVision(model, {
|
||||
payload: inputs.vision,
|
||||
onChange: noop,
|
||||
})
|
||||
|
||||
const visionFiles = runInputData['#files#']
|
||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||
setRunInputData?.({
|
||||
...runInputDataRef.current,
|
||||
'#files#': newFiles,
|
||||
})
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const varInputs = getInputVars([inputs.instruction])
|
||||
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.filter(key => !['#context#', '#files#'].includes(key))
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
const newVars = {
|
||||
...newPayload,
|
||||
'#context#': runInputDataRef.current['#context#'],
|
||||
'#files#': runInputDataRef.current['#files#'],
|
||||
}
|
||||
setRunInputData?.(newVars)
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const filterVisionInputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
const {
|
||||
availableVars: availableVisionVars,
|
||||
} = useAvailableVarList(id, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: filterVisionInputVar,
|
||||
})
|
||||
|
||||
const forms = (() => {
|
||||
const forms: FormProps[] = []
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.inputVar`)!,
|
||||
variable: 'query',
|
||||
type: InputVarType.paragraph,
|
||||
required: true,
|
||||
}, ...varInputs],
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
)
|
||||
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVisionVars)
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.vision')!,
|
||||
inputs: [{
|
||||
label: currentVariable?.variable as any,
|
||||
variable: '#files#',
|
||||
type: currentVariable?.formType as any,
|
||||
required: false,
|
||||
}],
|
||||
values: { '#files#': visionFiles },
|
||||
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return forms
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
const vars = [payload.query, ...promptVars]
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const visionVar = payload.vision.configs.variable_selector
|
||||
vars.push(visionVar)
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === 'query')
|
||||
return payload.query
|
||||
if(variable === '#files#')
|
||||
return payload.vision.configs?.variable_selector
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,146 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { QuestionClassifierNodeType } from './types'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { useCallback } from 'react'
|
||||
import useConfigVision from '../../hooks/use-config-vision'
|
||||
import { noop } from 'lodash-es'
|
||||
import { findVariableWhenOnLLMVision } from '../utils'
|
||||
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: QuestionClassifierNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { inputs } = useNodeCrud<QuestionClassifierNodeType>(id, payload)
|
||||
|
||||
const model = inputs.model
|
||||
|
||||
const {
|
||||
isVisionModel,
|
||||
} = useConfigVision(model, {
|
||||
payload: inputs.vision,
|
||||
onChange: noop,
|
||||
})
|
||||
|
||||
const visionFiles = runInputData['#files#']
|
||||
const setVisionFiles = useCallback((newFiles: any[]) => {
|
||||
setRunInputData?.({
|
||||
...runInputDataRef.current,
|
||||
'#files#': newFiles,
|
||||
})
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const varInputs = getInputVars([inputs.instruction])
|
||||
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.filter(key => !['#files#'].includes(key))
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
const newVars = {
|
||||
...newPayload,
|
||||
'#files#': runInputDataRef.current['#files#'],
|
||||
}
|
||||
setRunInputData?.(newVars)
|
||||
}, [runInputDataRef, setRunInputData])
|
||||
|
||||
const filterVisionInputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
const {
|
||||
availableVars: availableVisionVars,
|
||||
} = useAvailableVarList(id, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: filterVisionInputVar,
|
||||
})
|
||||
|
||||
const forms = (() => {
|
||||
const forms: FormProps[] = []
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.inputVars`)!,
|
||||
variable: 'query',
|
||||
type: InputVarType.paragraph,
|
||||
required: true,
|
||||
}, ...varInputs],
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
)
|
||||
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const currentVariable = findVariableWhenOnLLMVision(payload.vision.configs.variable_selector, availableVisionVars)
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.vision')!,
|
||||
inputs: [{
|
||||
label: currentVariable?.variable as any,
|
||||
variable: '#files#',
|
||||
type: currentVariable?.formType as any,
|
||||
required: false,
|
||||
}],
|
||||
values: { '#files#': visionFiles },
|
||||
onChange: keyValue => setVisionFiles(keyValue['#files#']),
|
||||
},
|
||||
)
|
||||
}
|
||||
return forms
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
const promptVars = varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
const vars = [payload.query_variable_selector, ...promptVars]
|
||||
if (isVisionModel && payload.vision?.enabled && payload.vision?.configs?.variable_selector) {
|
||||
const visionVar = payload.vision.configs.variable_selector
|
||||
vars.push(visionVar)
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
if(variable === 'query')
|
||||
return payload.query_variable_selector
|
||||
if(variable === '#files#')
|
||||
return payload.vision.configs?.variable_selector
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,87 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { type InputVar, InputVarType, type Variable } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from './types'
|
||||
import { useIsChatMode } from '../../hooks'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: StartNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const forms = (() => {
|
||||
const forms: FormProps[] = []
|
||||
const inputs: InputVar[] = payload.variables.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
getVarValueFromDependent: true,
|
||||
}
|
||||
})
|
||||
|
||||
if (isChatMode) {
|
||||
inputs.push({
|
||||
label: 'sys.query',
|
||||
variable: '#sys.query#',
|
||||
type: InputVarType.textInput,
|
||||
required: true,
|
||||
})
|
||||
}
|
||||
|
||||
inputs.push({
|
||||
label: 'sys.files',
|
||||
variable: '#sys.files#',
|
||||
type: InputVarType.multiFiles,
|
||||
required: false,
|
||||
})
|
||||
|
||||
forms.push(
|
||||
{
|
||||
label: t('workflow.nodes.llm.singleRun.variable')!,
|
||||
inputs,
|
||||
values: runInputData,
|
||||
onChange: setRunInputData,
|
||||
},
|
||||
)
|
||||
|
||||
return forms
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
const inputVars = payload.variables.map((item) => {
|
||||
return [id, item.variable]
|
||||
})
|
||||
const vars: ValueSelector[] = [...inputVars, ['sys', 'files']]
|
||||
|
||||
if (isChatMode)
|
||||
vars.push(['sys', 'query'])
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
return [id, variable]
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,65 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import type { TemplateTransformNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: TemplateTransformNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
runInputData,
|
||||
toVarInputs,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { inputs } = useNodeCrud<TemplateTransformNodeType>(id, payload)
|
||||
|
||||
const varInputs = toVarInputs(inputs.variables)
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: varInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
}, [inputVarValues, setInputVarValues, varInputs])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return payload.variables.map(v => v.value_selector)
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
const varItem = payload.variables.find(v => v.variable === variable)
|
||||
if (varItem)
|
||||
return varItem.value_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,20 @@
|
||||
import type { ToolNodeType } from './types'
|
||||
import useConfig from './use-config'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
payload: ToolNodeType,
|
||||
}
|
||||
|
||||
const useGetDataForCheckMore = ({
|
||||
id,
|
||||
payload,
|
||||
}: Params) => {
|
||||
const { getMoreDataForCheckValid } = useConfig(id, payload)
|
||||
|
||||
return {
|
||||
getData: getMoreDataForCheckValid,
|
||||
}
|
||||
}
|
||||
|
||||
export default useGetDataForCheckMore
|
||||
@ -0,0 +1,94 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { type ToolNodeType, VarType } from './types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||
import produce from 'immer'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log'
|
||||
import { useToolIcon } from '../../hooks'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: ToolNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
runResult: NodeTracing
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
id,
|
||||
payload,
|
||||
getInputVars,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const { inputs } = useNodeCrud<ToolNodeType>(id, payload)
|
||||
|
||||
const hadVarParams = Object.keys(inputs.tool_parameters)
|
||||
.filter(key => inputs.tool_parameters[key].type !== VarType.constant)
|
||||
.map(k => inputs.tool_parameters[k])
|
||||
const varInputs = getInputVars(hadVarParams.map((p) => {
|
||||
if (p.type === VarType.variable) {
|
||||
// handle the old wrong value not crash the page
|
||||
if (!(p.value as any).join)
|
||||
return `{{#${p.value}#}}`
|
||||
|
||||
return `{{#${(p.value as ValueSelector).join('.')}#}}`
|
||||
}
|
||||
|
||||
return p.value as string
|
||||
}))
|
||||
const [inputVarValues, doSetInputVarValues] = useState<Record<string, any>>({})
|
||||
const setInputVarValues = useCallback((value: Record<string, any>) => {
|
||||
doSetInputVarValues(value)
|
||||
setRunInputData(value)
|
||||
}, [setRunInputData])
|
||||
|
||||
const inputVarValuesWithConstantValue = useCallback(() => {
|
||||
const res = produce(inputVarValues, (draft) => {
|
||||
Object.keys(inputs.tool_parameters).forEach((key: string) => {
|
||||
const { type, value } = inputs.tool_parameters[key]
|
||||
if (type === VarType.constant && (value === undefined || value === null))
|
||||
draft[key] = value
|
||||
})
|
||||
})
|
||||
return res
|
||||
}, [inputs.tool_parameters, inputVarValues])
|
||||
|
||||
const forms = useMemo(() => {
|
||||
const forms: FormProps[] = [{
|
||||
inputs: varInputs,
|
||||
values: inputVarValuesWithConstantValue(),
|
||||
onChange: setInputVarValues,
|
||||
}]
|
||||
return forms
|
||||
}, [inputVarValuesWithConstantValue, setInputVarValues, varInputs])
|
||||
|
||||
const nodeInfo = useMemo(() => {
|
||||
if (!runResult)
|
||||
return null
|
||||
return formatToTracingNodeList([runResult], t)[0]
|
||||
}, [runResult, t])
|
||||
|
||||
const toolIcon = useToolIcon(payload)
|
||||
|
||||
const getDependentVars = () => {
|
||||
return varInputs.map(item => item.variable.slice(1, -1).split('.'))
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
nodeInfo,
|
||||
toolIcon,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -0,0 +1,92 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback } from 'react'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: VariableAssignerNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: MutableRefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
varSelectorsToVarInputs,
|
||||
}: Params) => {
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = (() => {
|
||||
const allInputs: ValueSelector[] = []
|
||||
const isGroupEnabled = !!payload.advanced_settings?.group_enabled
|
||||
if (!isGroupEnabled && payload.variables && payload.variables.length) {
|
||||
payload.variables.forEach((varSelector) => {
|
||||
allInputs.push(varSelector)
|
||||
})
|
||||
}
|
||||
if (isGroupEnabled && payload.advanced_settings && payload.advanced_settings.groups && payload.advanced_settings.groups.length) {
|
||||
payload.advanced_settings.groups.forEach((group) => {
|
||||
group.variables?.forEach((varSelector) => {
|
||||
allInputs.push(varSelector)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const varInputs = varSelectorsToVarInputs(allInputs)
|
||||
// remove duplicate inputs
|
||||
const existVarsKey: Record<string, boolean> = {}
|
||||
const uniqueVarInputs: InputVar[] = []
|
||||
varInputs.forEach((input) => {
|
||||
if(!input)
|
||||
return
|
||||
if (!existVarsKey[input.variable]) {
|
||||
existVarsKey[input.variable] = true
|
||||
uniqueVarInputs.push({
|
||||
...input,
|
||||
required: false, // just one of the inputs is required
|
||||
})
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
inputs: uniqueVarInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
if(payload.advanced_settings?.group_enabled) {
|
||||
const vars: ValueSelector[][] = []
|
||||
payload.advanced_settings.groups.forEach((group) => {
|
||||
if(group.variables)
|
||||
vars.push([...group.variables])
|
||||
})
|
||||
return vars
|
||||
}
|
||||
return [payload.variables]
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@ -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,
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue