diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index 94a8b45129..d2b9a519b5 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -21,7 +21,7 @@ import type { BlockEnum } from '@/app/components/workflow/types' import type { Emoji } from '@/app/components/tools/types' import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel' - +// import { useWhyDidYouUpdate } from 'ahooks' const i18nPrefix = 'workflow.singleRun' export type BeforeRunFormProps = { @@ -77,9 +77,13 @@ const BeforeRunForm: FC = ({ }) => { const { t } = useTranslation() + // useWhyDidYouUpdate('BeforeRunForm', { nodeName, nodeType, toolIcon, onHide, onRun, onStop, runningStatus, result, forms, showSpecialResultPanel, filteredExistVarForms, existVarValuesInForms, ...restResultPanelParams }) + const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed || runningStatus === NodeRunningStatus.Exception const isRunning = runningStatus === NodeRunningStatus.Running const isFileLoaded = (() => { + if (!forms || forms.length === 0) + return true // system files const filesForm = forms.find(item => !!item.values['#files#']) if (!filesForm) diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index b6083ae68c..6b57da3d6d 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -147,11 +147,9 @@ const BasePanel: FC = ({ runResult, getInputVars, toVarInputs, - childPanelRef, tabType, setTabType, singleRunParams, - setSingleRunParams, setRunInputData, hasLastRunData, handleRun, @@ -163,6 +161,35 @@ const BasePanel: FC = ({ defaultRunInputData: NODES_EXTRA_DATA[data.type]?.defaultRunInputData || {}, }) + if (isShowSingleRun) { + return ( +
+
+ } + /> +
+
+ ) + } + return (
= ({
{ - if (!childPanelRef.current?.singleRunParams) { - // handleNodeDataUpdate({ id, data: { _isSingleRun: true } }) - console.error('childPanelRef is not set') - return - } - const filteredExistVarForms = getFilteredExistVarForms(childPanelRef.current?.singleRunParams.forms) + const filteredExistVarForms = getFilteredExistVarForms(singleRunParams.forms) if (filteredExistVarForms.length > 0) { - setSingleRunParams(childPanelRef.current?.singleRunParams) showSingleRun() } else { @@ -266,7 +287,6 @@ const BasePanel: FC = ({ runResult, runInputDataRef, }, - ref: childPanelRef, })}
@@ -305,23 +325,6 @@ const BasePanel: FC = ({ {tabType === TabType.lastRun && ( )} - - { - isShowSingleRun && ( - } - /> - ) - }
) diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts index cb69155a9e..1c8a7a3d36 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts @@ -1,33 +1,72 @@ 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, useRef, useState } from 'react' -import type { PanelExposedType } from '@/types/workflow' +import { useCallback, useState } from 'react' import { TabType } from '../tab' -import { sleep } from '@/utils' import { useWorkflowStore } from '@/app/components/workflow/store' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' +import useLLMSingleRunFormParams from '@/app/components/workflow/nodes/llm/use-single-run-form-params' +import { BlockEnum } from '@/app/components/workflow/types' + +const singleRunFormParamsHooks: Record = { + [BlockEnum.LLM]: useLLMSingleRunFormParams, + [BlockEnum.Start]: undefined, + [BlockEnum.End]: undefined, + [BlockEnum.Answer]: undefined, + [BlockEnum.KnowledgeRetrieval]: undefined, + [BlockEnum.QuestionClassifier]: undefined, + [BlockEnum.IfElse]: undefined, + [BlockEnum.Code]: undefined, + [BlockEnum.TemplateTransform]: undefined, + [BlockEnum.HttpRequest]: undefined, + [BlockEnum.VariableAssigner]: undefined, + [BlockEnum.VariableAggregator]: undefined, + [BlockEnum.Tool]: undefined, + [BlockEnum.ParameterExtractor]: undefined, + [BlockEnum.Iteration]: undefined, + [BlockEnum.DocExtractor]: undefined, + [BlockEnum.ListFilter]: undefined, + [BlockEnum.IterationStart]: undefined, + [BlockEnum.Assigner]: undefined, + [BlockEnum.Agent]: undefined, + [BlockEnum.Loop]: undefined, + [BlockEnum.LoopStart]: undefined, + [BlockEnum.LoopEnd]: undefined, +} + +const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => { + return (params: any) => { + return singleRunFormParamsHooks[nodeType]?.(params) || {} + } +} type Params = OneStepRunParams const useLastRun = ({ ...oneStepRunParams }: Params) => { - const childPanelRef = useRef(null) - + const { + id, + data, + } = oneStepRunParams const oneStepRunRes = useOneStepRun(oneStepRunParams) const { hideSingleRun, handleRun: callRunApi, - setRunInputData: doSetRunInputData, + getInputVars, + toVarInputs, + runInputData, + runInputDataRef, + setRunInputData, } = oneStepRunRes - const [singleRunParams, setSingleRunParams] = useState(undefined) - - const setRunInputData = useCallback(async (data: Record) => { - doSetRunInputData(data) - // console.log(childPanelRef.current?.singleRunParams) - await sleep(0) // wait for childPanelRef.current?.singleRunParams refresh - setSingleRunParams(childPanelRef.current?.singleRunParams) - }, [doSetRunInputData]) + const singleRunParams = useSingleRunFormParamsHooks(data.type)({ + id, + payload: data, + runInputData, + runInputDataRef, + getInputVars, + setRunInputData, + toVarInputs, + }) const [tabType, setTabType] = useState(TabType.settings) const handleRun = async (data: Record) => { @@ -46,6 +85,9 @@ const useLastRun = ({ getInspectVar, } = workflowStore.getState() const getExistVarValuesInForms = (forms: FormProps[]) => { + if (!forms || forms.length === 0) + return [] + // if (!singleRunParams) const valuesArr = forms.map((form) => { const values: Record = {} @@ -83,6 +125,8 @@ const useLastRun = ({ } const getFilteredExistVarForms = (forms: FormProps[]) => { + if (!forms || forms.length === 0) + return [] const existVarValuesInForms = getExistVarValuesInForms(forms) const res = forms.map((form, i) => { @@ -99,11 +143,9 @@ const useLastRun = ({ return { ...oneStepRunRes, - childPanelRef, tabType, setTabType: handleTabClicked, singleRunParams, - setSingleRunParams, setRunInputData, hasLastRunData, handleRun, diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index b27b8cc200..c8be3d0ab7 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -1,10 +1,10 @@ -import React, { forwardRef, useCallback, useImperativeHandle } from 'react' +import type { FC } from 'react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import MemoryConfig from '../_base/components/memory-config' import VarReferencePicker from '../_base/components/variable/var-reference-picker' import ConfigVision from '../_base/components/config-vision' import useConfig from './use-config' -import { findVariableWhenOnLLMVision } from '../utils' import type { LLMNodeType } from './types' import ConfigPrompt from './components/config-prompt' import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' @@ -13,22 +13,19 @@ import Field from '@/app/components/workflow/nodes/_base/components/field' import Split from '@/app/components/workflow/nodes/_base/components/split' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types' -import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' +import type { NodePanelProps } from '@/app/components/workflow/types' import Tooltip from '@/app/components/base/tooltip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import StructureOutput from './components/structure-output' import Switch from '@/app/components/base/switch' import { RiAlertFill, RiQuestionLine } from '@remixicon/react' -import type { PanelExposedType } from '@/types/workflow' const i18nPrefix = 'workflow.nodes.llm' -const Panel = forwardRef>(({ +const Panel: FC> = ({ id, data, - panelProps, -}, ref) => { +}) => { const { t } = useTranslation() const { readOnly, @@ -56,74 +53,16 @@ const Panel = forwardRef>(({ handleMemoryChange, handleVisionResolutionEnabledChange, handleVisionResolutionChange, - inputVarValues, - setInputVarValues, - visionFiles, - setVisionFiles, - contexts, - setContexts, isModelSupportStructuredOutput, structuredOutputCollapsed, setStructuredOutputCollapsed, handleStructureOutputEnableChange, handleStructureOutputChange, - varInputs, filterJinjia2InputVar, - } = useConfig(id, data, panelProps) + } = useConfig(id, data) const model = inputs.model - const singleRunForms = (() => { - 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 && data.vision?.enabled && data.vision?.configs?.variable_selector) { - const currentVariable = findVariableWhenOnLLMVision(data.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 handleModelChange = useCallback((model: { provider: string modelId: string @@ -134,12 +73,6 @@ const Panel = forwardRef>(({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - useImperativeHandle(ref, () => ({ - singleRunParams: { - forms: singleRunForms, - }, - })) - return (
@@ -343,6 +276,6 @@ const Panel = forwardRef>(({
) -}) +} export default React.memo(Panel) diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index f6288b18d7..3bb9b29437 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -17,16 +17,9 @@ import { } from '@/app/components/header/account-setting/model-provider-page/declarations' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' -import type { PanelProps } from '@/types/workflow' import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' -const useConfig = (id: string, payload: LLMNodeType, panelProps?: PanelProps) => { - const getVarInputs = panelProps?.getInputVars - const toVarInputs = panelProps?.toVarInputs - const runInputData = panelProps?.runInputData || {} - const runInputDataRef = panelProps?.runInputDataRef || { current: {} } - const setRunInputData = panelProps?.setRunInputData - +const useConfig = (id: string, payload: LLMNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() const isChatMode = useIsChatMode() @@ -332,59 +325,6 @@ const useConfig = (id: string, payload: LLMNodeType, panelProps?: PanelProps) => filterVar: filterMemoryPromptVar, }) - const inputVarValues = (() => { - const vars: Record = {} - Object.keys(runInputData) - .filter(key => !['#context#', '#files#'].includes(key)) - .forEach((key) => { - vars[key] = runInputData[key] - }) - return vars - })() - - const setInputVarValues = useCallback((newPayload: Record) => { - const newVars = { - ...newPayload, - '#context#': runInputDataRef.current['#context#'], - '#files#': runInputDataRef.current['#files#'], - } - setRunInputData?.(newVars) - }, [runInputDataRef, setRunInputData]) - - 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]) - - 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 - })() - return { readOnly, isChatMode, @@ -411,13 +351,6 @@ const useConfig = (id: string, payload: LLMNodeType, panelProps?: PanelProps) => handleSyeQueryChange, handleVisionResolutionEnabledChange, handleVisionResolutionChange, - inputVarValues, - setInputVarValues, - visionFiles, - setVisionFiles, - contexts, - setContexts, - varInputs, isModelSupportStructuredOutput, handleStructureOutputChange, structuredOutputCollapsed, diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts new file mode 100644 index 0000000000..8e6aa005da --- /dev/null +++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts @@ -0,0 +1,175 @@ +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 + runInputDataRef: MutableRefObject> + getInputVars: (textList: string[]) => InputVar[] + setRunInputData: (data: Record) => void + toVarInputs: (variables: Variable[]) => InputVar[] +} +const useSingleRunFormParams = ({ + id, + payload, + runInputData, + runInputDataRef, + getInputVars, + setRunInputData, + toVarInputs, +}: Params) => { + const { t } = useTranslation() + const { inputs } = useNodeCrud(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 = {} + Object.keys(runInputData) + .filter(key => !['#context#', '#files#'].includes(key)) + .forEach((key) => { + vars[key] = runInputData[key] + }) + return vars + })() + + const setInputVarValues = useCallback((newPayload: Record) => { + 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 + })() + + return { + forms, + } +} + +export default useSingleRunFormParams