diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index 32f1021e5..6ce5be234 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -5,8 +5,8 @@ import { StoreNodeItemType } from '../type/node'; import { StoreEdgeItemType } from '../type/edge'; import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type'; import { VARIABLE_NODE_ID } from '../constants'; -import { isReferenceValue } from '../utils'; -import { FlowNodeOutputItemType, ReferenceValueProps } from '../type/io'; +import { isReferenceValue, isReferenceValueArray } from '../utils'; +import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io'; import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants'; @@ -225,15 +225,22 @@ export const checkNodeRunStatus = ({ return 'wait'; }; +/* + Get the value of the reference variable/node output + 1. [string,string] + 2. [string,string][] +*/ export const getReferenceVariableValue = ({ value, nodes, variables }: { - value: ReferenceValueProps; + value: ReferenceValueType; nodes: RuntimeNodeItemType[]; variables: Record; }) => { + if (!value) return undefined; + const nodeIds = nodes.map((node) => node.nodeId); // handle single reference value @@ -254,32 +261,13 @@ export const getReferenceVariableValue = ({ } // handle reference array - if ( - Array.isArray(value) && - value.every( - (val) => val?.length === 2 && typeof val[0] === 'string' && typeof val[1] === 'string' - ) - ) { - const result = value.map((val) => { - if (!isReferenceValue(val, nodeIds)) { - return [val]; - } - - const sourceNodeId = val?.[0]; - const outputId = val?.[1]; - - if (sourceNodeId === VARIABLE_NODE_ID && outputId) { - const variableValue = variables[outputId]; - return Array.isArray(variableValue) ? variableValue : [variableValue]; - } - - const node = nodes.find((node) => node.nodeId === sourceNodeId); - if (!node) { - return undefined; - } - - const outputValue = node.outputs.find((output) => output.id === outputId)?.value; - return Array.isArray(outputValue) ? outputValue : [outputValue]; + if (isReferenceValueArray(value, nodeIds)) { + const result = value.map((val) => { + return getReferenceVariableValue({ + value: val, + nodes, + variables + }); }); return result.flat(); @@ -288,6 +276,70 @@ export const getReferenceVariableValue = ({ return value; }; +// replace {{$xx.xx$}} variables for text +export function replaceEditorVariable({ + text, + nodes, + variables, + runningNode +}: { + text: any; + nodes: RuntimeNodeItemType[]; + variables: Record; // global variables + runningNode: RuntimeNodeItemType; +}) { + if (typeof text !== 'string') return text; + + const globalVariables = Object.keys(variables).map((key) => { + return { + nodeId: VARIABLE_NODE_ID, + id: key, + value: variables[key] + }; + }); + + // Upstream node outputs + const nodeVariables = nodes + .map((node) => { + return node.outputs.map((output) => { + return { + nodeId: node.nodeId, + id: output.id, + value: output.value + }; + }); + }) + .flat(); + + // Get runningNode inputs(Will be replaced with reference) + const customInputs = runningNode.inputs.flatMap((item) => { + return [ + { + id: item.key, + value: getReferenceVariableValue({ + value: item.value, + nodes, + variables + }), + nodeId: runningNode.nodeId + } + ]; + }); + + const allVariables = [...globalVariables, ...nodeVariables, ...customInputs]; + + // Replace {{$xxx.xxx$}} to value + for (const key in allVariables) { + const variable = allVariables[key]; + const val = variable.value; + const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val); + + const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g'); + text = text.replace(regex, formatVal); + } + return text || ''; +} + export const textAdaptGptResponse = ({ text, model = '', diff --git a/packages/global/core/workflow/template/system/ifElse/type.d.ts b/packages/global/core/workflow/template/system/ifElse/type.d.ts index 1f55adcaa..6092ac0e1 100644 --- a/packages/global/core/workflow/template/system/ifElse/type.d.ts +++ b/packages/global/core/workflow/template/system/ifElse/type.d.ts @@ -1,9 +1,9 @@ -import { ReferenceValueProps } from 'core/workflow/type/io'; +import { ReferenceItemValueType } from '../../../type/io'; import { VariableConditionEnum } from './constant'; export type IfElseConditionType = 'AND' | 'OR'; export type ConditionListItemType = { - variable?: ReferenceValueProps; + variable?: ReferenceItemValueType; condition?: VariableConditionEnum; value?: string; }; diff --git a/packages/global/core/workflow/template/system/variableUpdate/type.d.ts b/packages/global/core/workflow/template/system/variableUpdate/type.d.ts index b46d730ee..512353d0e 100644 --- a/packages/global/core/workflow/template/system/variableUpdate/type.d.ts +++ b/packages/global/core/workflow/template/system/variableUpdate/type.d.ts @@ -1,10 +1,10 @@ import { FlowNodeInputTypeEnum } from '../../../node/constant'; -import { ReferenceValueProps } from '../../..//type/io'; +import { ReferenceItemValueType, ReferenceValueType } from '../../..//type/io'; import { WorkflowIOValueTypeEnum } from '../../../constants'; export type TUpdateListItem = { - variable?: ReferenceValueProps; - value: ReferenceValueProps; + variable?: ReferenceItemValueType; + value: ReferenceValueType; // input: ['',value], reference: [nodeId,outputId] valueType?: WorkflowIOValueTypeEnum; renderType: FlowNodeInputTypeEnum.input | FlowNodeInputTypeEnum.reference; }; diff --git a/packages/global/core/workflow/template/system/workflowStart.ts b/packages/global/core/workflow/template/system/workflowStart.ts index 930ced6ca..c2da4a372 100644 --- a/packages/global/core/workflow/template/system/workflowStart.ts +++ b/packages/global/core/workflow/template/system/workflowStart.ts @@ -43,6 +43,3 @@ export const WorkflowStart: FlowNodeTemplateType = { } ] }; - -export const isWorkflowStartOutput = (key?: string) => - !!WorkflowStart.outputs.find((output) => output.key === key); diff --git a/packages/global/core/workflow/type/io.d.ts b/packages/global/core/workflow/type/io.d.ts index 9e5699416..7844bb3c5 100644 --- a/packages/global/core/workflow/type/io.d.ts +++ b/packages/global/core/workflow/type/io.d.ts @@ -80,4 +80,6 @@ export type FlowNodeOutputItemType = { customFieldConfig?: CustomFieldConfigType; }; -export type ReferenceValueProps = [string, string | undefined]; +export type ReferenceItemValueType = undefined | [string, string]; +export type ReferenceArrayValueType = undefined | [string, string][]; +export type ReferenceValueType = ReferenceItemValueType | ReferenceArrayValueType; diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts index 4b5cc0e43..4cbdd5f70 100644 --- a/packages/global/core/workflow/utils.ts +++ b/packages/global/core/workflow/utils.ts @@ -12,7 +12,7 @@ import { VARIABLE_NODE_ID, NodeOutputKeyEnum } from './constants'; -import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d'; +import { FlowNodeInputItemType, FlowNodeOutputItemType } from './type/io.d'; import { StoreNodeItemType } from './type/node'; import type { VariableItemType, @@ -30,7 +30,6 @@ import { } from '../app/constants'; import { IfElseResultEnum } from './template/system/ifElse/constant'; import { RuntimeNodeItemType } from './runtime/type'; -import { getReferenceVariableValue } from './runtime/utils'; import { Input_Template_File_Link, Input_Template_History, @@ -301,9 +300,19 @@ export const formatEditorVariablePickerIcon = ( })); }; -export const isReferenceValue = (value: any, nodeIds: string[]): boolean => { - const validIdList = [VARIABLE_NODE_ID, ...nodeIds]; - return Array.isArray(value) && value.length === 2 && validIdList.includes(value[0]); +export const isReferenceValue = (value: any, nodeIds: string[]): value is [string, string] => { + if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string') return false; + + const validIdSet = new Set([VARIABLE_NODE_ID, ...nodeIds]); + return validIdSet.has(value[0]); +}; +export const isReferenceValueArray = ( + value: any, + nodeIds: string[] +): value is [string, string][] => { + if (!Array.isArray(value) || value.length === 0) return false; + + return value.every((item) => isReferenceValue(item, nodeIds)); }; export const getElseIFLabel = (i: number) => { @@ -345,79 +354,6 @@ export const updatePluginInputByVariables = ( ); }; -// replace {{$xx.xx$}} variables for text -export function replaceEditorVariable({ - text, - nodes, - variables, - runningNode -}: { - text: any; - nodes: RuntimeNodeItemType[]; - variables: Record; // global variables - runningNode: RuntimeNodeItemType; -}) { - if (typeof text !== 'string') return text; - - const globalVariables = Object.keys(variables).map((key) => { - return { - nodeId: VARIABLE_NODE_ID, - id: key, - value: variables[key] - }; - }); - - // Upstream node outputs - const nodeVariables = nodes - .map((node) => { - return node.outputs.map((output) => { - return { - nodeId: node.nodeId, - id: output.id, - value: output.value - }; - }); - }) - .flat(); - - // Get runningNode inputs(Will be replaced with reference) - const customInputs = runningNode.inputs.flatMap((item) => { - if (Array.isArray(item.value)) { - return [ - { - id: item.key, - value: getReferenceVariableValue({ - value: item.value as ReferenceValueProps, - nodes, - variables - }), - nodeId: runningNode.nodeId - } - ]; - } - return [ - { - id: item.key, - value: item.value, - nodeId: runningNode.nodeId - } - ]; - }); - - const allVariables = [...globalVariables, ...nodeVariables, ...customInputs]; - - // Replace {{$xxx.xxx$}} to value - for (const key in allVariables) { - const variable = allVariables[key]; - const val = variable.value; - const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val); - - const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g'); - text = text.replace(regex, formatVal); - } - return text || ''; -} - /* Get plugin runtime input user query */ export const getPluginRunUserQuery = ({ pluginInputs, diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 9d1f9fa51..cc686752e 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -23,7 +23,6 @@ import { } from '@fastgpt/global/core/workflow/node/constant'; import { getNanoid, replaceVariable } from '@fastgpt/global/common/string/tools'; import { getSystemTime } from '@fastgpt/global/common/time/timezone'; -import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils'; import { dispatchWorkflowStart } from './init/workflowStart'; import { dispatchChatCompletion } from './chat/oneapi'; @@ -42,7 +41,8 @@ import { removeSystemVariable, valueTypeFormat } from './utils'; import { filterWorkflowEdges, checkNodeRunStatus, - textAdaptGptResponse + textAdaptGptResponse, + replaceEditorVariable } from '@fastgpt/global/core/workflow/runtime/utils'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { dispatchRunTools } from './agent/runTool/index'; diff --git a/packages/service/core/workflow/dispatch/tools/http468.ts b/packages/service/core/workflow/dispatch/tools/http468.ts index 145ded177..ab7e019e9 100644 --- a/packages/service/core/workflow/dispatch/tools/http468.ts +++ b/packages/service/core/workflow/dispatch/tools/http468.ts @@ -14,10 +14,12 @@ import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; import { addLog } from '../../../../common/system/log'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; +import { + textAdaptGptResponse, + replaceEditorVariable +} from '@fastgpt/global/core/workflow/runtime/utils'; import { getSystemPluginCb } from '../../../../../plugins/register'; import { ContentTypes } from '@fastgpt/global/core/workflow/constants'; -import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils'; import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller'; import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants'; import { createFileToken } from '../../../../support/permission/controller'; diff --git a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts index b380a3911..c32ba0307 100644 --- a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts +++ b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts @@ -4,11 +4,14 @@ import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; -import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils'; +import { + getReferenceVariableValue, + replaceEditorVariable +} from '@fastgpt/global/core/workflow/runtime/utils'; import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type'; import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import { removeSystemVariable, valueTypeFormat } from '../utils'; -import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils'; +import { isReferenceValue } from '@fastgpt/global/core/workflow/utils'; type Props = ModuleDispatchProps<{ [NodeInputKeyEnum.updateList]: TUpdateListItem[]; @@ -19,15 +22,20 @@ export const dispatchUpdateVariable = async (props: Props): Promise => const { params, variables, runtimeNodes, workflowStreamResponse, node } = props; const { updateList } = params; - const result = updateList.map((item) => { - const varNodeId = item.variable?.[0]; - const varKey = item.variable?.[1]; + const nodeIds = runtimeNodes.map((node) => node.nodeId); - if (!varNodeId || !varKey) { + const result = updateList.map((item) => { + const variable = item.variable; + + if (!isReferenceValue(variable, nodeIds)) { return null; } + const varNodeId = variable[0]; + const varKey = variable[1]; + const value = (() => { + // If first item is empty, it means it is a input value if (!item.value?.[0]) { const formatValue = valueTypeFormat(item.value?.[1], item.valueType); @@ -40,16 +48,15 @@ export const dispatchUpdateVariable = async (props: Props): Promise => }) : formatValue; } else { - const value = getReferenceVariableValue({ + return getReferenceVariableValue({ value: item.value, variables, nodes: runtimeNodes }); - - return value; } })(); + // Update node output // Global variable if (varNodeId === VARIABLE_NODE_ID) { variables[varKey] = value; @@ -74,6 +81,7 @@ export const dispatchUpdateVariable = async (props: Props): Promise => }); return { + [DispatchNodeResponseKeyEnum.newVariables]: variables, [DispatchNodeResponseKeyEnum.nodeResponse]: { updateVarResult: result } diff --git a/packages/web/components/common/Icon/icons/core/workflow/template/loopEnd.svg b/packages/web/components/common/Icon/icons/core/workflow/template/loopEnd.svg index ca8fc3c91..1d56992b1 100644 --- a/packages/web/components/common/Icon/icons/core/workflow/template/loopEnd.svg +++ b/packages/web/components/common/Icon/icons/core/workflow/template/loopEnd.svg @@ -1,11 +1,10 @@ - - - + + - - - + + + diff --git a/packages/web/components/common/Icon/icons/core/workflow/template/loopStart.svg b/packages/web/components/common/Icon/icons/core/workflow/template/loopStart.svg index 546a3ecd5..c71bdc9a7 100644 --- a/packages/web/components/common/Icon/icons/core/workflow/template/loopStart.svg +++ b/packages/web/components/common/Icon/icons/core/workflow/template/loopStart.svg @@ -1,11 +1,10 @@ - - - - + + + - - - + + + diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx index 5292de07d..a2a6ae97c 100644 --- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx @@ -1,12 +1,11 @@ import React, { useRef, useCallback, useState } from 'react'; import { Button, useDisclosure, Box, Flex, useOutsideClick, Checkbox } from '@chakra-ui/react'; -import { MultipleSelectProps } from './type'; +import { ListItemType, MultipleArraySelectProps, MultipleSelectProps } from './type'; import EmptyTip from '../EmptyTip'; import { useTranslation } from 'next-i18next'; import MyIcon from '../../common/Icon'; -import { ChevronDownIcon } from '@chakra-ui/icons'; -const MultipleRowSelect = ({ +export const MultipleRowSelect = ({ placeholder, label, value = [], @@ -15,14 +14,12 @@ const MultipleRowSelect = ({ maxH = 300, onSelect, popDirection = 'bottom', - styles, - isArray = false + styles }: MultipleSelectProps) => { const { t } = useTranslation(); const ref = useRef(null); const { isOpen, onOpen, onClose } = useDisclosure(); - - const [navigationPath, setNavigationPath] = useState([]); + const [cloneValue, setCloneValue] = useState(value); useOutsideClick({ ref: ref, @@ -31,33 +28,183 @@ const MultipleRowSelect = ({ const RenderList = useCallback( ({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => { - const currentNav = navigationPath[index]; - const selectedIndex = list.findIndex((item) => item.value === currentNav); + const selectedValue = cloneValue[index]; + const selectedIndex = list.findIndex((item) => item.value === selectedValue); const children = list[selectedIndex]?.children || []; const hasChildren = list.some((item) => item.children && item.children?.length > 0); - const handleSelect = (item: any) => { + return ( + <> + + {list.map((item) => ( + { + const newValue = [...cloneValue]; + + if (item.value === selectedValue) { + newValue[index] = undefined; + setCloneValue(newValue); + onSelect(newValue); + } else { + newValue[index] = item.value; + setCloneValue(newValue); + if (!hasChildren) { + onSelect(newValue); + onClose(); + } + } + }} + {...(item.value === selectedValue + ? { + color: 'primary.600' + } + : {})} + > + {item.label} + + ))} + {list.length === 0 && ( + + )} + + {children.length > 0 && } + + ); + }, + [cloneValue] + ); + + const onOpenSelect = useCallback(() => { + setCloneValue(value); + onOpen(); + }, [value, onOpen]); + + return ( + + } + _active={{ + transform: 'none' + }} + {...(isOpen + ? { + borderColor: 'primary.600', + color: 'primary.700', + boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' + } + : { + borderColor: 'myGray.200', + boxShadow: 'none' + })} + {...styles} + onClick={() => (isOpen ? onClose() : onOpenSelect())} + > + {label ?? placeholder} + + {isOpen && ( + + + + + + )} + + ); +}; + +export const MultipleRowArraySelect = ({ + placeholder, + label, + value = [], + list, + emptyTip, + maxH = 300, + onSelect, + popDirection = 'bottom', + styles +}: MultipleArraySelectProps) => { + const { t } = useTranslation(); + const ref = useRef(null); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const [navigationPath, setNavigationPath] = useState([]); + + // Close when clicking outside + useOutsideClick({ + ref: ref, + handler: onClose + }); + + const RenderList = useCallback( + ({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => { + const currentNavValue = navigationPath[index]; + const selectedIndex = list.findIndex((item) => item.value === currentNavValue); + const children = list[selectedIndex]?.children || []; + const hasChildren = list.some((item) => item.children && item.children?.length > 0); + + const handleSelect = (item: ListItemType) => { + // Has children, set parent value if (hasChildren) { - // Update parent menu path const newPath = [...navigationPath]; newPath[index] = item.value; - // Clear sub paths - newPath.splice(index + 1); setNavigationPath(newPath); } else { - if (!isArray) { - onSelect([navigationPath[0], item.value]); - onClose(); - } else { - const parentValue = navigationPath[0]; - const newValues = [...value]; - const newValue = [parentValue, item.value]; + const parentValue = navigationPath[0]; + const newValues = [...value]; + const newValue = [parentValue, item.value]; - if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) { - onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value))); - } else { - onSelect([...newValues, newValue]); - } + if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) { + onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value))); + } else { + onSelect([...newValues, newValue]); } } }; @@ -74,8 +221,8 @@ const MultipleRowSelect = ({ whiteSpace={'nowrap'} > {list.map((item) => { - const isSelected = item.value === currentNav; - const showCheckbox = isArray && index !== 0; + const isSelected = item.value === currentNavValue; + const showCheckbox = !hasChildren; const isChecked = showCheckbox && value.some((v) => v[1] === item.value && v[0] === navigationPath[0]); @@ -101,7 +248,7 @@ const MultipleRowSelect = ({ mr={1} /> )} - {item.label} + {item.label} ); })} @@ -117,33 +264,26 @@ const MultipleRowSelect = ({ ); }, - [navigationPath, value, isArray, onSelect] + [navigationPath, value, onSelect] ); const onOpenSelect = useCallback(() => { - setNavigationPath(isArray ? [] : [value[0]?.[0], value[0]?.[1]]); + setNavigationPath([]); onOpen(); - }, [value, isArray, onOpen]); + }, [value, onOpen]); return ( - } - border={'1px solid'} - borderRadius={'md'} - bg={'white'} + rightIcon={} + iconSpacing={2} + h={'auto'} _active={{ transform: 'none' }} @@ -164,20 +304,21 @@ const MultipleRowSelect = ({ onClick={() => (isOpen ? onClose() : onOpenSelect())} className="nowheel" > - {label ?? placeholder} - - - - + + {label ?? placeholder} + + {isOpen && ( = { +export type MultipleSelectProps = { label?: string | React.ReactNode; - value: any[]; + value?: any[]; placeholder?: string; list: ListItemType[]; emptyTip?: string; @@ -14,5 +14,8 @@ export type MultipleSelectProps = { onSelect: (val: any[]) => void; styles?: ButtonProps; popDirection?: 'top' | 'bottom'; - isArray?: boolean; +}; +export type MultipleArraySelectProps = Omit & { + value?: any[][]; + onSelect: (val: any[][]) => void; }; diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index ff105e192..0e6258c02 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -46,6 +46,7 @@ const Button = defineStyleConfig({ px: '2', py: '0', h: '24px', + minH: '24px', fontWeight: 'medium', borderRadius: 'sm' }, @@ -54,6 +55,7 @@ const Button = defineStyleConfig({ px: '0', py: '0', h: '24px', + minH: '24px', w: '24px', fontWeight: 'medium', borderRadius: 'sm' @@ -64,6 +66,7 @@ const Button = defineStyleConfig({ py: 0, fontWeight: 'medium', h: '30px', + minH: '30px', borderRadius: 'sm' }, smSquare: { @@ -72,6 +75,7 @@ const Button = defineStyleConfig({ py: 0, fontWeight: 'medium', h: '30px', + minH: '30px', w: '30px', borderRadius: 'sm' }, @@ -80,6 +84,7 @@ const Button = defineStyleConfig({ px: '4', py: 0, h: '34px', + minH: '34px', fontWeight: 'medium', borderRadius: 'sm' }, @@ -88,6 +93,7 @@ const Button = defineStyleConfig({ px: '0', py: 0, h: '34px', + minH: '34px', w: '34px', fontWeight: 'medium', borderRadius: 'sm' @@ -97,6 +103,7 @@ const Button = defineStyleConfig({ px: '4', py: 0, h: '40px', + minH: '40px', fontWeight: 'medium', borderRadius: 'md' }, @@ -105,6 +112,7 @@ const Button = defineStyleConfig({ px: '0', py: 0, h: '40px', + minH: '40px', w: '40px', fontWeight: 'medium', borderRadius: 'md' @@ -346,14 +354,14 @@ const NumberInput = numInputMultiStyle({ sm: defineStyle({ field: { h: '32px', - borderRadius: 'md', + borderRadius: 'sm', fontsize: 'sm' } }), lg: defineStyle({ field: { h: '40px', - borderRadius: 'md', + borderRadius: 'sm', fontsize: 'sm' } }) diff --git a/projects/app/src/components/Markdown/codeBlock/Iframe.tsx b/projects/app/src/components/Markdown/codeBlock/Iframe.tsx new file mode 100644 index 000000000..50aff3f61 --- /dev/null +++ b/projects/app/src/components/Markdown/codeBlock/Iframe.tsx @@ -0,0 +1,21 @@ +import React, { useEffect, useRef, useCallback, useState } from 'react'; +import { Box } from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; + +const MermaidBlock = ({ code }: { code: string }) => { + return ( + + + + ); +}; + +export default MermaidBlock; diff --git a/projects/app/src/components/Markdown/index.tsx b/projects/app/src/components/Markdown/index.tsx index 5a6d2d25d..703ebbf90 100644 --- a/projects/app/src/components/Markdown/index.tsx +++ b/projects/app/src/components/Markdown/index.tsx @@ -22,6 +22,7 @@ const CodeLight = dynamic(() => import('./CodeLight'), { ssr: false }); const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false }); const MdImage = dynamic(() => import('./img/Image'), { ssr: false }); const EChartsCodeBlock = dynamic(() => import('./img/EChartsCodeBlock'), { ssr: false }); +const IframeCodeBlock = dynamic(() => import('./codeBlock/Iframe'), { ssr: false }); const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false }); const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false }); @@ -76,7 +77,7 @@ const Markdown = ({ ); return finalText; - }, [showAnimation, source]); + }, [forbidZhFormat, showAnimation, source]); const urlTransform = useCallback((val: string) => { return val; @@ -123,6 +124,9 @@ function Code(e: any) { if (codeType === CodeClassNameEnum.echarts) { return ; } + if (codeType === CodeClassNameEnum.iframe) { + return ; + } return ( diff --git a/projects/app/src/components/Markdown/utils.ts b/projects/app/src/components/Markdown/utils.ts index b0b907542..50d0b5c40 100644 --- a/projects/app/src/components/Markdown/utils.ts +++ b/projects/app/src/components/Markdown/utils.ts @@ -5,7 +5,8 @@ export enum CodeClassNameEnum { echarts = 'echarts', quote = 'quote', files = 'files', - latex = 'latex' + latex = 'latex', + iframe = 'iframe' } function htmlTableToLatex(html: string) { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx index 03cab16aa..8a92cb29a 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx @@ -27,6 +27,8 @@ import { WorkflowContext } from '../../../context'; import { cloneDeep } from 'lodash'; import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { AppContext } from '../../../../context'; +import { isReferenceValue, isReferenceValueArray } from '@fastgpt/global/core/workflow/utils'; +import { ReferenceItemValueType } from '@fastgpt/global/core/workflow/type/io'; const NodeLoop = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); @@ -34,7 +36,7 @@ const NodeLoop = ({ data, selected }: NodeProps) => { const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v); const { appDetail } = useContextSelector(AppContext, (v) => v); - const arrayValue = inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray)?.value; + const loopInputArray = inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray); const { nodeWidth, nodeHeight } = useMemo(() => { return { @@ -49,41 +51,53 @@ const NodeLoop = ({ data, selected }: NodeProps) => { }, [nodeId, nodeList]); // Detect and update array input type - useEffect(() => { - const inputsClone = cloneDeep(inputs); + const newValueType = useMemo(() => { + if (!loopInputArray) return WorkflowIOValueTypeEnum.arrayAny; + + const nodeIds = nodeList.map((node) => node.nodeId); const globalVariables = getWorkflowGlobalVariables({ nodes: nodeList, chatConfig: appDetail.chatConfig }); - let arrayType: WorkflowIOValueTypeEnum | undefined; - if (arrayValue[0]?.[0] === VARIABLE_NODE_ID) { - arrayType = globalVariables.find((item) => item.key === arrayValue[0]?.[1])?.valueType; - } else { - const node = nodeList.find((node) => node.nodeId === arrayValue[0]?.[0]); - const output = node?.outputs.find((output) => output.id === arrayValue[0]?.[1]); - arrayType = output?.valueType; - } + const getValueType = (value: ReferenceItemValueType) => { + if (value?.[0] === VARIABLE_NODE_ID) { + return globalVariables.find((item) => item.key === value[1])?.valueType; + } else { + const node = nodeList.find((node) => node.nodeId === value?.[0]); + const output = node?.outputs.find((output) => output.id === value?.[1]); + return output?.valueType; + } + }; - const arrayInput = inputsClone.find((input) => input.key === NodeInputKeyEnum.loopInputArray); + const valueType = (() => { + if (isReferenceValue(loopInputArray?.value, nodeIds)) { + return getValueType(loopInputArray?.value); + } else if (isReferenceValueArray(loopInputArray?.value, nodeIds)) { + return getValueType(loopInputArray?.value?.[0]); + } else { + return WorkflowIOValueTypeEnum.arrayAny; + } + })(); - if (!arrayInput) { - return; - } + const type = ArrayTypeMap[valueType as keyof typeof ArrayTypeMap]; + return type ?? WorkflowIOValueTypeEnum.arrayAny; + }, [appDetail.chatConfig, loopInputArray, nodeList]); + useEffect(() => { + if (!loopInputArray) return; onChangeNode({ nodeId, type: 'updateInput', key: NodeInputKeyEnum.loopInputArray, value: { - ...arrayInput, - valueType: arrayType - ? ArrayTypeMap[arrayType as keyof typeof ArrayTypeMap] - : WorkflowIOValueTypeEnum.arrayAny + ...loopInputArray, + valueType: newValueType } }); - }, [appDetail.chatConfig, arrayValue, inputs, nodeId, nodeList, onChangeNode]); + }, [newValueType]); + // Update childrenNodeIdList useEffect(() => { onChangeNode({ nodeId, @@ -112,7 +126,7 @@ const NodeLoop = ({ data, selected }: NodeProps) => { > - + diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx index be26b3a52..b979d499a 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx @@ -7,14 +7,13 @@ import RenderInput from './render/RenderInput'; import { Box, Button, Flex, HStack } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { SmallAddIcon } from '@chakra-ui/icons'; -import { NodeInputKeyEnum, VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; +import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; import MySlider from '@/components/Slider'; import { FlowNodeInputItemType, - ReferenceValueProps + ReferenceItemValueType } from '@fastgpt/global/core/workflow/type/io.d'; import RenderOutput from './render/RenderOutput'; import IOTitle from '../components/IOTitle'; @@ -24,94 +23,13 @@ import { ReferSelector, useReference } from './render/RenderInput/templates/Refe import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import ValueTypeLabel from './render/ValueTypeLabel'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart'; import { getWebLLMModel } from '@/web/common/system/utils'; -import { useMemoizedFn } from 'ahooks'; const NodeDatasetConcat = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { llmModelList } = useSystemStore(); const { nodeId, inputs, outputs } = data; const { nodeList, onChangeNode } = useContextSelector(WorkflowContext, (v) => v); - const Reference = useMemoizedFn( - ({ nodeId, inputChildren }: { nodeId: string; inputChildren: FlowNodeInputItemType }) => { - const { t } = useTranslation(); - const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); - - const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); - - const { referenceList, formatValue } = useReference({ - nodeId, - valueType: inputChildren.valueType, - value: inputChildren.value - }); - - const onSelect = useCallback( - (e: ReferenceValueProps | ReferenceValueProps[]) => { - const workflowStartNode = nodeList.find( - (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart - ); - - onChangeNode({ - nodeId, - type: 'replaceInput', - key: inputChildren.key, - value: { - ...inputChildren, - value: - e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string) - ? [VARIABLE_NODE_ID, e[1]] - : e - } - }); - }, - [inputChildren, nodeId, nodeList, onChangeNode] - ); - - const onDel = useCallback(() => { - onChangeNode({ - nodeId, - type: 'delInput', - key: inputChildren.key - }); - }, [inputChildren.key, nodeId, onChangeNode]); - - return ( - <> - - {t(inputChildren.label as any)} - {/* value */} - - - - - - - ); - } - ); - const CustomComponent = useMemo(() => { const quoteList = inputs.filter((item) => item.canEdit); const tokenLimit = (() => { @@ -184,7 +102,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps) => { {quoteList.map((children) => ( - + ))} @@ -192,7 +110,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps) => { ); } }; - }, [Reference, inputs, nodeId, nodeList, onChangeNode, t, llmModelList]); + }, [inputs, nodeId, nodeList, onChangeNode, t]); const Render = useMemo(() => { return ( @@ -212,3 +130,75 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps) => { return Render; }; export default React.memo(NodeDatasetConcat); + +const VariableSelector = ({ + nodeId, + inputChildren +}: { + nodeId: string; + inputChildren: FlowNodeInputItemType; +}) => { + const { t } = useTranslation(); + const { onChangeNode } = useContextSelector(WorkflowContext, (v) => v); + + const { referenceList } = useReference({ + nodeId, + valueType: inputChildren.valueType + }); + + const onSelect = useCallback( + (e: ReferenceItemValueType) => { + if (!e) return; + + onChangeNode({ + nodeId, + type: 'replaceInput', + key: inputChildren.key, + value: { + ...inputChildren, + value: e + } + }); + }, + [inputChildren, nodeId, onChangeNode] + ); + + const onDel = useCallback(() => { + onChangeNode({ + nodeId, + type: 'delInput', + key: inputChildren.key + }); + }, [inputChildren.key, nodeId, onChangeNode]); + + return ( + <> + + {t(inputChildren.label as any)} + {/* value */} + + + + + + + ); +}; diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx index ae41365c8..d7bd2c673 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx @@ -7,7 +7,7 @@ import Container from '../../components/Container'; import { MinusIcon, SmallAddIcon } from '@chakra-ui/icons'; import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; +import { ReferenceItemValueType } from '@fastgpt/global/core/workflow/type/io'; import { useTranslation } from 'next-i18next'; import { ReferSelector, useReference } from '../render/RenderInput/templates/Reference'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; @@ -122,7 +122,7 @@ const ListItem = ({ {/* variable reference */} - { @@ -302,29 +302,29 @@ const ListItem = ({ export default React.memo(ListItem); -const Reference = ({ +const VariableSelector = ({ nodeId, variable, onSelect }: { nodeId: string; - variable?: ReferenceValueProps; - onSelect: (e: ReferenceValueProps) => void; + variable?: ReferenceItemValueType; + onSelect: (e: ReferenceItemValueType) => void; }) => { const { t } = useTranslation(); - const { referenceList, formatValue } = useReference({ + const { referenceList } = useReference({ nodeId, - valueType: WorkflowIOValueTypeEnum.any, - value: variable + valueType: WorkflowIOValueTypeEnum.any }); return ( ); }; @@ -336,7 +336,7 @@ const ConditionSelect = ({ onSelect }: { condition?: VariableConditionEnum; - variable?: ReferenceValueProps; + variable?: ReferenceItemValueType; onSelect: (e: VariableConditionEnum) => void; }) => { const { t } = useTranslation(); @@ -414,7 +414,7 @@ const ConditionValueInput = ({ onChange }: { value?: string; - variable?: ReferenceValueProps; + variable?: ReferenceItemValueType; condition?: VariableConditionEnum; onChange: (e: string) => void; }) => { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx index 1f9f9e8da..0448c575c 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx @@ -2,18 +2,12 @@ import React, { useCallback, useMemo, useState } from 'react'; import { NodeProps } from 'reactflow'; import NodeCard from '../render/NodeCard'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d'; -import dynamic from 'next/dynamic'; import { Box, Button, Flex } from '@chakra-ui/react'; import { SmallAddIcon } from '@chakra-ui/icons'; -import { - FlowNodeInputTypeEnum, - FlowNodeTypeEnum -} from '@fastgpt/global/core/workflow/node/constant'; import Container from '../../components/Container'; -import { FlowNodeInputItemType, ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; -import { VARIABLE_NODE_ID, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; +import { FlowNodeInputItemType, ReferenceValueType } from '@fastgpt/global/core/workflow/type/io'; +import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { useTranslation } from 'next-i18next'; -import RenderInput from '../render/RenderInput'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../../context'; import IOTitle from '../../components/IOTitle'; @@ -24,7 +18,6 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import { useI18n } from '@/web/context/I18n'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart'; import PluginOutputEditModal, { defaultOutput } from './PluginOutputEditModal'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; @@ -113,38 +106,27 @@ function Reference({ content: workflowT('confirm_delete_field_tip') }); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); - const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const [editField, setEditField] = useState(); const onSelect = useCallback( - (e: ReferenceValueProps | ReferenceValueProps[]) => { - const workflowStartNode = nodeList.find( - (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart - ); - - const value = - e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string) - ? [VARIABLE_NODE_ID, e[1]] - : e; - + (e: ReferenceValueType) => { onChangeNode({ nodeId, type: 'updateInput', key: input.key, value: { ...input, - value + value: e } }); }, - [input, nodeId, nodeList, onChangeNode] + [input, nodeId, onChangeNode] ); - const { referenceList, formatValue } = useReference({ + const { referenceList } = useReference({ nodeId, - valueType: input.valueType, - value: input.value + valueType: input.valueType }); const onUpdateField = useCallback( @@ -217,7 +199,7 @@ function Reference({ diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx index 39182cc1b..1707c8298 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx @@ -26,14 +26,14 @@ import Container from '../components/Container'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { SmallAddIcon } from '@chakra-ui/icons'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; +import { ReferenceItemValueType, ReferenceValueType } from '@fastgpt/global/core/workflow/type/io'; import { ReferSelector, useReference } from './render/RenderInput/templates/Reference'; import { getRefData } from '@/web/core/workflow/utils'; -import { isReferenceValue } from '@fastgpt/global/core/workflow/utils'; import { AppContext } from '@/pages/app/detail/components/context'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import { useCreation, useMemoizedFn } from 'ahooks'; import { getEditorVariables } from '../../utils'; +import { isArray } from 'lodash'; const NodeVariableUpdate = ({ data, selected }: NodeProps) => { const { inputs = [], nodeId } = data; @@ -103,18 +103,17 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => (item) => item.renderType === updateItem.renderType ); - const nodeIds = nodeList.map((node) => node.nodeId); - const handleUpdate = (newValue: ReferenceValueProps | string) => { - if (isReferenceValue(newValue, nodeIds)) { + const handleUpdate = (newValue: ReferenceValueType | string) => { + if (typeof newValue === 'string') { onUpdateList( updateList.map((update, i) => - i === index ? { ...update, value: newValue as ReferenceValueProps } : update + i === index ? { ...update, value: ['', newValue] } : update ) ); } else { onUpdateList( updateList.map((update, i) => - i === index ? { ...update, value: ['', newValue as string] } : update + i === index ? { ...update, value: newValue as ReferenceItemValueType } : update ) ); } @@ -124,7 +123,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => {t('common:core.workflow.variable')} - { @@ -135,7 +134,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => ...update, value: ['', ''], valueType, - variable: value + variable: value as ReferenceItemValueType }; } return update; @@ -202,7 +201,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => {(() => { if (updateItem.renderType === FlowNodeInputTypeEnum.reference) { return ( - ) => /> ); } + + const inputValue = isArray(updateItem.value?.[1]) ? '' : updateItem.value?.[1]; + if (valueType === WorkflowIOValueTypeEnum.string) { return ( ) => } if (valueType === WorkflowIOValueTypeEnum.number) { return ( - + handleUpdate(e.target.value)} /> @@ -237,7 +239,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => if (valueType === WorkflowIOValueTypeEnum.boolean) { return ( handleUpdate(String(e.target.checked))} /> ); @@ -246,7 +248,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => return ( ) => }; export default React.memo(NodeVariableUpdate); -const Reference = ({ +const VariableSelector = ({ nodeId, variable, valueType, onSelect }: { nodeId: string; - variable?: ReferenceValueProps; + variable?: ReferenceValueType; valueType?: WorkflowIOValueTypeEnum; - onSelect: (e: ReferenceValueProps) => void; + onSelect: (e: ReferenceValueType) => void; }) => { const { t } = useTranslation(); - const { referenceList, formatValue } = useReference({ + const { referenceList } = useReference({ nodeId, - valueType, - value: variable + valueType }); return ( ); }; diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx index 86985bacd..8a133ce30 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx @@ -5,20 +5,17 @@ import { SmallAddIcon } from '@chakra-ui/icons'; import { useTranslation } from 'next-i18next'; import dynamic from 'next/dynamic'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { FlowNodeInputItemType, ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; +import { FlowNodeInputItemType, ReferenceValueType } from '@fastgpt/global/core/workflow/type/io'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { defaultInput } from '../../FieldEditModal'; import { getInputComponentProps } from '@fastgpt/global/core/workflow/node/io/utils'; -import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; -import { isReference, ReferSelector, useReference } from '../Reference'; +import { ReferSelector, useReference } from '../Reference'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import ValueTypeLabel from '../../../ValueTypeLabel'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useI18n } from '@/web/context/I18n'; -import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart'; const FieldEditModal = dynamic(() => import('../../FieldEditModal')); @@ -126,71 +123,40 @@ function Reference({ const [editField, setEditField] = useState(); const onSelect = useCallback( - (e: ReferenceValueProps | ReferenceValueProps[]) => { - const workflowStartNode = nodeList.find( - (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart - ); - - const newValue = - e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1] as string) - ? [VARIABLE_NODE_ID, e[1]] - : e; - + (e: ReferenceValueType) => { onChangeNode({ nodeId, type: 'replaceInput', key: inputChildren.key, value: { ...inputChildren, - value: newValue + value: e } }); }, - [inputChildren, nodeId, nodeList, onChangeNode] + [inputChildren, nodeId, onChangeNode] ); - const { referenceList, formatValue } = useReference({ + const { referenceList } = useReference({ nodeId, - valueType: inputChildren.valueType, - value: inputChildren.value + valueType: inputChildren.valueType }); - // handle array and non-array type conversion - const getValueTypeChange = useCallback( - (data: FlowNodeInputItemType, oldType: string | undefined) => { - const newType = data.valueType; - if (oldType === newType) return data.value; - - if (!oldType?.includes('array') && newType?.includes('array')) { - return Array.isArray(data.value) && data.value.every((item) => isReference(item)) - ? data.value - : [data.value]; - } - if (oldType?.includes('array') && !newType?.includes('array')) { - return Array.isArray(data.value) ? data.value[0] : data.value; - } - return data.value; - }, - [] - ); - const onUpdateField = useCallback( ({ data }: { data: FlowNodeInputItemType }) => { if (!data.key) return; - const updatedValue = getValueTypeChange(data, inputChildren.valueType); - onChangeNode({ nodeId, type: 'replaceInput', key: inputChildren.key, value: { ...data, - value: updatedValue + value: data } }); }, - [inputChildren, nodeId, onChangeNode, getValueTypeChange] + [inputChildren, nodeId, onChangeNode] ); const onDel = useCallback(() => { onChangeNode({ @@ -234,7 +200,7 @@ function Reference({ diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx index 372049b1f..9c1235155 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -1,28 +1,37 @@ import React, { useCallback, useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { Flex, Box, ButtonProps } from '@chakra-ui/react'; +import { Flex, Box, ButtonProps, Grid } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { computedNodeInputReference } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { NodeOutputKeyEnum, - VARIABLE_NODE_ID, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; -import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; +import type { + ReferenceArrayValueType, + ReferenceItemValueType, + ReferenceValueType +} from '@fastgpt/global/core/workflow/type/io'; import dynamic from 'next/dynamic'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { AppContext } from '@/pages/app/detail/components/context'; -const MultipleRowSelect = dynamic( - () => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect') +const MultipleRowSelect = dynamic(() => + import('@fastgpt/web/components/common/MySelect/MultipleRowSelect').then( + (v) => v.MultipleRowSelect + ) +); +const MultipleRowArraySelect = dynamic(() => + import('@fastgpt/web/components/common/MySelect/MultipleRowSelect').then( + (v) => v.MultipleRowArraySelect + ) ); const Avatar = dynamic(() => import('@fastgpt/web/components/common/Avatar')); -type SelectProps = { - value?: ReferenceValueProps[]; +type CommonSelectProps = { placeholder?: string; list: { label: string | React.ReactNode; @@ -33,95 +42,27 @@ type SelectProps = { valueType?: WorkflowIOValueTypeEnum; }[]; }[]; - onSelect: (val: ReferenceValueProps | ReferenceValueProps[]) => void; popDirection?: 'top' | 'bottom'; styles?: ButtonProps; - isArray?: boolean; }; - -export const isReference = (val: any) => - Array.isArray(val) && - val.length === 2 && - typeof val[0] === 'string' && - typeof val[1] === 'string'; - -const Reference = ({ item, nodeId }: RenderInputProps) => { - const { t } = useTranslation(); - const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); - const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); - - const onSelect = useCallback( - (e: ReferenceValueProps | ReferenceValueProps[]) => { - const workflowStartNode = nodeList.find( - (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart - ); - if (e[0] === workflowStartNode?.id && e[1] !== NodeOutputKeyEnum.userChatInput) { - onChangeNode({ - nodeId, - type: 'updateInput', - key: item.key, - value: { - ...item, - value: [VARIABLE_NODE_ID, e[1]] - } - }); - } else { - onChangeNode({ - nodeId, - type: 'updateInput', - key: item.key, - value: { - ...item, - value: e - } - }); - } - }, - [item, nodeId, nodeList, onChangeNode] - ); - - const { referenceList, formatValue } = useReference({ - nodeId, - valueType: item.valueType, - value: item.value - }); - - const popDirection = useMemo(() => { - const node = nodeList.find((node) => node.nodeId === nodeId); - if (!node) return 'bottom'; - return node.flowNodeType === FlowNodeTypeEnum.loop ? 'top' : 'bottom'; - }, [nodeId, nodeList]); - - return ( - - ); +type SelectProps = CommonSelectProps & { + isArray?: T; + value?: T extends true ? ReferenceArrayValueType : ReferenceItemValueType; + onSelect: (val?: T extends true ? ReferenceArrayValueType : ReferenceItemValueType) => void; }; -export default React.memo(Reference); - export const useReference = ({ nodeId, - valueType = WorkflowIOValueTypeEnum.any, - value + valueType = WorkflowIOValueTypeEnum.any }: { nodeId: string; valueType?: WorkflowIOValueTypeEnum; - value?: any; }) => { const { t } = useTranslation(); const { appDetail } = useContextSelector(AppContext, (v) => v); - const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); - const edges = useContextSelector(WorkflowContext, (v) => v.edges); - const isArray = valueType?.includes('array'); - const currentType = isArray ? valueType.replace('array', '').toLowerCase() : valueType; + const { nodeList, edges } = useContextSelector(WorkflowContext, (v) => v); + // 获取可选的变量列表 const referenceList = useMemo(() => { const sourceNodes = computedNodeInputReference({ nodeId, @@ -133,8 +74,11 @@ export const useReference = ({ if (!sourceNodes) return []; + const isArray = valueType?.includes('array'); + const arrayItemType = isArray ? valueType.replace('array', '').toLowerCase() : valueType; + // 转换为 select 的数据结构 - const list: SelectProps['list'] = sourceNodes + const list: CommonSelectProps['list'] = sourceNodes .map((node) => { return { label: ( @@ -148,26 +92,16 @@ export const useReference = ({ .filter( (output) => valueType === WorkflowIOValueTypeEnum.any || + valueType === WorkflowIOValueTypeEnum.arrayAny || output.valueType === WorkflowIOValueTypeEnum.any || - currentType === output.valueType || - // array output.valueType === valueType || - (valueType === WorkflowIOValueTypeEnum.arrayAny && - [ - WorkflowIOValueTypeEnum.arrayString, - WorkflowIOValueTypeEnum.arrayNumber, - WorkflowIOValueTypeEnum.arrayBoolean, - WorkflowIOValueTypeEnum.arrayObject, - WorkflowIOValueTypeEnum.string, - WorkflowIOValueTypeEnum.number, - WorkflowIOValueTypeEnum.boolean, - WorkflowIOValueTypeEnum.object - ].includes(output.valueType as WorkflowIOValueTypeEnum)) + // Array can select string + arrayItemType === output.valueType ) .filter((output) => output.id !== NodeOutputKeyEnum.addOutputParam) .map((output) => { return { - label: t((output.label as any) || ''), + label: t(output.label as any), value: output.id, valueType: output.valueType }; @@ -177,148 +111,216 @@ export const useReference = ({ .filter((item) => item.children.length > 0); return list; - }, [appDetail.chatConfig, currentType, edges, isArray, nodeId, nodeList, t, valueType]); - - const formatValue = useMemo(() => { - // convert origin reference [variableId, outputId] to new reference [[variableId, outputId], ...] - if (isReference(value)) { - return [value] as ReferenceValueProps[]; - } else if (Array.isArray(value) && value.every((item) => isReference(item))) { - return value as ReferenceValueProps[]; - } - return undefined; - }, [value]); + }, [appDetail.chatConfig, edges, nodeId, nodeList, t, valueType]); return { - referenceList, - formatValue + referenceList }; }; -const ReferSelectorComponent = ({ +const Reference = ({ item, nodeId }: RenderInputProps) => { + const { t } = useTranslation(); + const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v); + const isArray = item.valueType?.includes('array') ?? false; + + const onSelect = useCallback( + (e: ReferenceValueType) => { + onChangeNode({ + nodeId, + type: 'updateInput', + key: item.key, + value: { + ...item, + value: e + } + }); + }, + [item, nodeId, onChangeNode] + ); + + const { referenceList } = useReference({ + nodeId, + valueType: item.valueType + }); + + const popDirection = useMemo(() => { + const node = nodeList.find((node) => node.nodeId === nodeId); + if (!node) return 'bottom'; + return node.flowNodeType === FlowNodeTypeEnum.loop ? 'top' : 'bottom'; + }, [nodeId, nodeList]); + + return ( + + ); +}; + +export default React.memo(Reference); + +const SingleReferenceSelector = ({ placeholder, value, list = [], onSelect, - popDirection, - isArray -}: SelectProps) => { - const { t } = useTranslation(); + popDirection +}: SelectProps) => { + const getSelectValue = useCallback( + (value: ReferenceValueType) => { + if (!value) return []; - const selectValue = useMemo(() => { - if (!value || value.every((item) => !item || item.every((subItem) => !subItem))) { - return; - } - return value.map((valueItem) => { - const firstColumn = list.find((item) => item.value === valueItem[0]); + const firstColumn = list.find((item) => item.value === value[0]); if (!firstColumn) { - return; + return []; } - const secondColumn = firstColumn.children.find((item) => item.value === valueItem[1]); + const secondColumn = firstColumn.children.find((item) => item.value === value[1]); if (!secondColumn) { - return; + return []; } - return [firstColumn, secondColumn]; - }); - }, [list, value]); + return [firstColumn.label, secondColumn.label]; + }, + [list] + ); + + const ItemSelector = useMemo(() => { + const selectorVal = value as ReferenceItemValueType; + const [nodeName, outputName] = getSelectValue(selectorVal); + const isValidSelect = nodeName && outputName; - const Render = useMemo(() => { return ( 0 ? ( - - {isArray ? ( - // [[variableId, outputId], ...] - selectValue.map((item, index) => { - const isInvalidItem = item === undefined; - return ( - + isValidSelect ? ( + + + {nodeName} + + {outputName} + + + ) : ( + + {placeholder} + + ) + } + value={selectorVal} + list={list} + onSelect={onSelect as any} + popDirection={popDirection} + /> + ); + }, [getSelectValue, list, onSelect, placeholder, popDirection, value]); + + return ItemSelector; +}; +const MultipleReferenceSelector = ({ + placeholder, + value, + list = [], + onSelect, + popDirection +}: SelectProps) => { + const { t } = useTranslation(); + + const getSelectValue = useCallback( + (value: ReferenceValueType) => { + if (!value) return []; + + const firstColumn = list.find((item) => item.value === value[0]); + if (!firstColumn) { + return []; + } + const secondColumn = firstColumn.children.find((item) => item.value === value[1]); + if (!secondColumn) { + return []; + } + return [firstColumn.label, secondColumn.label]; + }, + [list] + ); + + const ArraySelector = useMemo(() => { + const selectorVal = value as ReferenceItemValueType[]; + + return ( + 0 ? ( + + {selectorVal.map((item, index) => { + const [nodeName, outputName] = getSelectValue(item); + const isInvalidItem = !nodeName || !outputName; + + return ( + + {isInvalidItem ? ( t('common:invalid_variable') ) : ( <> - {item?.[0].label} + {nodeName} - {item?.[1].label} + {outputName} )} - { - e.stopPropagation(); - if (isInvalidItem) { - const filteredValue = value?.filter((_, i) => i !== index); - onSelect(filteredValue as any); - return; - } - const filteredValue = value?.filter( - (val) => val[0] !== item?.[0].value || val[1] !== item?.[1].value - ); - filteredValue && onSelect(filteredValue); - }} - /> - ); - }) - ) : // [variableId, outputId] - selectValue[0] ? ( - - {selectValue[0][0].label} - - {selectValue[0][1].label} - - ) : ( - - {placeholder} - - )} - + { + e.stopPropagation(); + onSelect(value?.filter((_, i) => i !== index)); + }} + /> + + ); + })} + ) : ( - + {placeholder} ) } - value={value as any[]} + value={selectorVal as any} list={list} - onSelect={(e) => { - onSelect(e as ReferenceValueProps); - }} + onSelect={onSelect as any} popDirection={popDirection} - isArray={isArray} /> ); - }, [isArray, list, onSelect, placeholder, popDirection, selectValue, t, value]); + }, [getSelectValue, list, onSelect, placeholder, popDirection, t, value]); - return Render; + return ArraySelector; +}; +export const ReferSelector = (props: SelectProps) => { + return props.isArray ? ( + )} /> + ) : ( + )} /> + ); }; - -ReferSelectorComponent.displayName = 'ReferSelector'; - -export const ReferSelector = React.memo(ReferSelectorComponent); diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 7c121433c..ebfb83a89 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -28,7 +28,7 @@ import { TFunction } from 'next-i18next'; import { FlowNodeInputItemType, FlowNodeOutputItemType, - ReferenceValueProps + ReferenceItemValueType } from '@fastgpt/global/core/workflow/type/io'; import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type'; import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant'; @@ -227,7 +227,7 @@ export const getRefData = ({ nodeList, chatConfig }: { - variable?: ReferenceValueProps; + variable?: ReferenceItemValueType; nodeList: FlowNodeItemType[]; chatConfig: AppChatConfigType; }) => { @@ -352,7 +352,7 @@ export const checkWorkflowNodeAndConnection = ({ } // New format - return input.value.some((inputItem: ReferenceValueProps) => { + return input.value.some((inputItem: ReferenceItemValueType) => { if (!Array.isArray(inputItem) || inputItem.length !== 2) { return true; }