mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 17:29:44 +00:00
4.8.12 dev (#2928)
* perf: optimize global variables (#2863) * feat: add global variable types * add global variables to debug * fix select dnd * unify InputTypeConfig params * feat: http node url support variables (#2891) * feat: http node url support variables * change to prompt editor * fix: global variables (#2892) * fix global variables * fix type * perf: global variables * perf: workflow delete node error (#2905) * update lock * update 4812 doc * feat: add node course url config (#2897) * feat: add node course url config * change plugin course url * change default doc url * change url store * delete unused code * fix: global variable (#2915) * fix: global variable * add comment * fix: interactive check * locj * perf: debug switch to global tab when click run & global var default reset (#2925) * fix: tool course url * fix: global var default value & wrap variable form (#2926) * fix: add dataset tags not update render (#2927) * feat: tool will save histories * perf: global variables code * perf: FE_DOMAIN config --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -354,11 +354,11 @@ const RenderList = React.memo(function RenderList({
|
||||
<HStack mb={4} spacing={1} fontSize={'sm'}>
|
||||
<MyIcon name={'common/info'} w={'1.25rem'} />
|
||||
<Box flex={1}>{t('app:tool_input_param_tip')}</Box>
|
||||
{configTool.inputExplanationUrl && (
|
||||
{configTool.courseUrl && (
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
color={'primary.500'}
|
||||
onClick={() => window.open(configTool.inputExplanationUrl, '_blank')}
|
||||
onClick={() => window.open(configTool.courseUrl, '_blank')}
|
||||
>
|
||||
{t('app:workflow.Input guide')}
|
||||
</Box>
|
||||
|
@@ -1,29 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Box, StackProps, HStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const IOTitle = ({
|
||||
text,
|
||||
inputExplanationUrl,
|
||||
...props
|
||||
}: { text?: 'Input' | 'Output' | string; inputExplanationUrl?: string } & StackProps) => {
|
||||
const { t } = useTranslation();
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const IOTitle = ({ text, ...props }: { text?: 'Input' | 'Output' | string } & StackProps) => {
|
||||
return (
|
||||
<HStack fontSize={'md'} alignItems={'center'} fontWeight={'medium'} mb={3} {...props}>
|
||||
<Box w={'3px'} h={'14px'} borderRadius={'13px'} bg={'primary.600'} />
|
||||
<Box color={'myGray.900'}>{text}</Box>
|
||||
<Box flex={1} />
|
||||
|
||||
{inputExplanationUrl && (
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
color={'primary.500'}
|
||||
onClick={() => window.open(inputExplanationUrl, '_blank')}
|
||||
>
|
||||
{t('app:workflow.Input guide')}
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useState, useMemo, useEffect } from 'react';
|
||||
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -21,19 +21,30 @@ import {
|
||||
NumberInputStepper,
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FieldErrors, useForm } from 'react-hook-form';
|
||||
import {
|
||||
VariableInputEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '../../context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppContext } from '../../../context';
|
||||
import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
|
||||
const MyRightDrawer = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
|
||||
);
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
enum TabEnum {
|
||||
global = 'global',
|
||||
node = 'node'
|
||||
}
|
||||
|
||||
export const useDebug = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@@ -43,6 +54,23 @@ export const useDebug = () => {
|
||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||
const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug);
|
||||
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
|
||||
const filteredVar = useMemo(() => {
|
||||
const variables = appDetail.chatConfig.variables;
|
||||
return variables?.filter((item) => item.type !== VariableInputEnum.custom) || [];
|
||||
}, [appDetail.chatConfig.variables]);
|
||||
|
||||
const [defaultGlobalVariables, setDefaultGlobalVariables] = useState<Record<string, any>>(
|
||||
filteredVar.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.key] = item.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
);
|
||||
|
||||
const [runtimeNodeId, setRuntimeNodeId] = useState<string>();
|
||||
const [runtimeNodes, setRuntimeNodes] = useState<RuntimeNodeItemType[]>();
|
||||
const [runtimeEdges, setRuntimeEdges] = useState<RuntimeEdgeItemType[]>();
|
||||
@@ -108,6 +136,8 @@ export const useDebug = () => {
|
||||
const DebugInputModal = useCallback(() => {
|
||||
if (!runtimeNodes || !runtimeEdges) return <></>;
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.node);
|
||||
|
||||
const runtimeNode = runtimeNodes.find((node) => node.nodeId === runtimeNodeId);
|
||||
|
||||
if (!runtimeNode) return <></>;
|
||||
@@ -117,20 +147,24 @@ export const useDebug = () => {
|
||||
if (input.required && !input.value) return true;
|
||||
});
|
||||
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<Record<string, any>>({
|
||||
defaultValues: renderInputs.reduce((acc: Record<string, any>, input) => {
|
||||
const isReference = checkInputIsReference(input);
|
||||
if (isReference) {
|
||||
acc[input.key] = undefined;
|
||||
} else if (typeof input.value === 'object') {
|
||||
acc[input.key] = JSON.stringify(input.value, null, 2);
|
||||
} else {
|
||||
acc[input.key] = input.value;
|
||||
}
|
||||
const variablesForm = useForm<Record<string, any>>({
|
||||
defaultValues: {
|
||||
nodeVariables: renderInputs.reduce((acc: Record<string, any>, input) => {
|
||||
const isReference = checkInputIsReference(input);
|
||||
if (isReference) {
|
||||
acc[input.key] = undefined;
|
||||
} else if (typeof input.value === 'object') {
|
||||
acc[input.key] = JSON.stringify(input.value, null, 2);
|
||||
} else {
|
||||
acc[input.key] = input.value;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
return acc;
|
||||
}, {}),
|
||||
globalVariables: defaultGlobalVariables
|
||||
}
|
||||
});
|
||||
const { register, getValues, setValue, handleSubmit } = variablesForm;
|
||||
|
||||
const onClose = () => {
|
||||
setRuntimeNodeId(undefined);
|
||||
@@ -152,12 +186,13 @@ export const useDebug = () => {
|
||||
input.valueType === WorkflowIOValueTypeEnum.string ||
|
||||
input.valueType === WorkflowIOValueTypeEnum.number ||
|
||||
input.valueType === WorkflowIOValueTypeEnum.boolean
|
||||
)
|
||||
return data[input.key];
|
||||
) {
|
||||
return data.nodeVariables[input.key];
|
||||
}
|
||||
|
||||
return JSON.parse(data[input.key]);
|
||||
return JSON.parse(data.nodeVariables[input.key]);
|
||||
} catch (e) {
|
||||
return data[input.key];
|
||||
return data.nodeVariables[input.key];
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -169,11 +204,33 @@ export const useDebug = () => {
|
||||
}
|
||||
: node
|
||||
),
|
||||
runtimeEdges: runtimeEdges
|
||||
runtimeEdges: runtimeEdges,
|
||||
variables: data.globalVariables
|
||||
});
|
||||
|
||||
// Filter global variables and set them as default global variable values
|
||||
setDefaultGlobalVariables(data.globalVariables);
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
const onCheckRunError = useCallback((e: FieldErrors<Record<string, any>>) => {
|
||||
const hasRequiredNodeVar =
|
||||
e.nodeVariables && Object.values(e.nodeVariables).some((item) => item.type === 'required');
|
||||
|
||||
if (hasRequiredNodeVar) {
|
||||
return setCurrentTab(TabEnum.node);
|
||||
}
|
||||
|
||||
const hasRequiredGlobalVar =
|
||||
e.globalVariables &&
|
||||
Object.values(e.globalVariables).some((item) => item.type === 'required');
|
||||
|
||||
if (hasRequiredGlobalVar) {
|
||||
setCurrentTab(TabEnum.global);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyRightDrawer
|
||||
onClose={onClose}
|
||||
@@ -183,89 +240,124 @@ export const useDebug = () => {
|
||||
px={0}
|
||||
>
|
||||
<Box flex={'1 0 0'} overflow={'auto'} px={6}>
|
||||
{renderInputs.map((input) => {
|
||||
const required = input.required || false;
|
||||
{filteredVar.length > 0 && (
|
||||
<LightRowTabs<TabEnum>
|
||||
gap={3}
|
||||
ml={-2}
|
||||
mb={5}
|
||||
inlineStyles={{}}
|
||||
list={[
|
||||
{ label: t('workflow:Node_variables'), value: TabEnum.node },
|
||||
{ label: t('common:core.module.Variable'), value: TabEnum.global }
|
||||
]}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
)}
|
||||
<Box display={currentTab === TabEnum.global ? 'block' : 'none'}>
|
||||
{filteredVar.map((item) => (
|
||||
<VariableInputItem
|
||||
key={item.id}
|
||||
item={{ ...item, key: `globalVariables.${item.key}` }}
|
||||
variablesForm={variablesForm}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box display={currentTab === TabEnum.node ? 'block' : 'none'}>
|
||||
{renderInputs.map((input) => {
|
||||
const required = input.required || false;
|
||||
|
||||
const RenderInput = (() => {
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
||||
return (
|
||||
<Textarea
|
||||
{...register(`nodeVariables.${input.key}`, {
|
||||
required
|
||||
})}
|
||||
placeholder={t(input.placeholder || ('' as any))}
|
||||
bg={'myGray.50'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.number) {
|
||||
return (
|
||||
<NumberInput step={input.step} min={input.min} max={input.max} bg={'myGray.50'}>
|
||||
<NumberInputField
|
||||
{...register(`nodeVariables.${input.key}`, {
|
||||
required: input.required,
|
||||
min: input.min,
|
||||
max: input.max,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
}
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<Box>
|
||||
<Switch {...register(`nodeVariables.${input.key}`)} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
let value = getValues(input.key) || '';
|
||||
if (typeof value !== 'string') {
|
||||
value = JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
const RenderInput = (() => {
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
||||
return (
|
||||
<Textarea
|
||||
{...register(input.key, {
|
||||
required
|
||||
})}
|
||||
placeholder={t(input.placeholder || ('' as any))}
|
||||
<JsonEditor
|
||||
bg={'myGray.50'}
|
||||
placeholder={t(input.placeholder || ('' as any))}
|
||||
resize
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(`nodeVariables.${input.key}`, e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.number) {
|
||||
return (
|
||||
<NumberInput step={input.step} min={input.min} max={input.max} bg={'myGray.50'}>
|
||||
<NumberInputField
|
||||
{...register(input.key, {
|
||||
required: input.required,
|
||||
min: input.min,
|
||||
max: input.max,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
}
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<Box>
|
||||
<Switch {...register(input.key)} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
let value = getValues(input.key) || '';
|
||||
if (typeof value !== 'string') {
|
||||
value = JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
return (
|
||||
<JsonEditor
|
||||
bg={'myGray.50'}
|
||||
placeholder={t(input.placeholder || ('' as any))}
|
||||
resize
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(input.key, e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
||||
return !!RenderInput ? (
|
||||
<Box key={input.key} _notLast={{ mb: 4 }} px={1}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<Box position={'relative'}>
|
||||
{required && (
|
||||
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{t(input.debugLabel || (input.label as any))}
|
||||
</Box>
|
||||
{input.description && <QuestionTip ml={2} label={input.description} />}
|
||||
</Flex>
|
||||
{RenderInput}
|
||||
</Box>
|
||||
) : null;
|
||||
})}
|
||||
return !!RenderInput ? (
|
||||
<Box key={input.key} _notLast={{ mb: 4 }} px={1}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<Box position={'relative'}>
|
||||
{required && (
|
||||
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{t(input.debugLabel || (input.label as any))}
|
||||
</Box>
|
||||
{input.description && <QuestionTip ml={2} label={input.description} />}
|
||||
</Flex>
|
||||
{RenderInput}
|
||||
</Box>
|
||||
) : null;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex py={2} justifyContent={'flex-end'} px={6}>
|
||||
<Button onClick={handleSubmit(onClickRun)}>{t('common:common.Run')}</Button>
|
||||
<Button onClick={handleSubmit(onClickRun, onCheckRunError)}>
|
||||
{t('common:common.Run')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</MyRightDrawer>
|
||||
);
|
||||
}, [onStartNodeDebug, runtimeEdges, runtimeNodeId, runtimeNodes, t]);
|
||||
}, [
|
||||
defaultGlobalVariables,
|
||||
filteredVar,
|
||||
onStartNodeDebug,
|
||||
runtimeEdges,
|
||||
runtimeNodeId,
|
||||
runtimeNodes,
|
||||
t
|
||||
]);
|
||||
|
||||
return {
|
||||
DebugInputModal,
|
||||
|
@@ -412,41 +412,37 @@ export const useWorkflow = () => {
|
||||
});
|
||||
|
||||
/* node */
|
||||
// Remove change node and its child nodes and edges
|
||||
const handleRemoveNode = useMemoizedFn((change: NodeRemoveChange, nodeId: string) => {
|
||||
// If the node has child nodes, remove the child nodes
|
||||
const deletedNodeIdList = [nodeId];
|
||||
const deletedEdgeIdList = edges
|
||||
.filter((edge) => edge.source === nodeId || edge.target === nodeId)
|
||||
.map((edge) => edge.id);
|
||||
|
||||
const childNodes = nodes.filter((n) => n.data.parentNodeId === nodeId);
|
||||
if (childNodes.length > 0) {
|
||||
const childNodeIds = childNodes.map((node) => node.id);
|
||||
deletedNodeIdList.push(...childNodeIds);
|
||||
|
||||
const childEdges = edges.filter(
|
||||
(edge) => childNodeIds.includes(edge.source) || childNodeIds.includes(edge.target)
|
||||
);
|
||||
|
||||
onNodesChange(
|
||||
childNodes.map<NodeRemoveChange>((node) => ({
|
||||
type: 'remove',
|
||||
id: node.id
|
||||
}))
|
||||
);
|
||||
onEdgesChange(
|
||||
childEdges.map<EdgeRemoveChange>((edge) => ({
|
||||
type: 'remove',
|
||||
id: edge.id
|
||||
}))
|
||||
);
|
||||
deletedEdgeIdList.push(...childEdges.map((edge) => edge.id));
|
||||
}
|
||||
|
||||
onNodesChange([change]);
|
||||
|
||||
// Remove the edges connected to the node
|
||||
const nodeEdges = edges.filter((edge) => edge.source === nodeId || edge.target === nodeId);
|
||||
onEdgesChange(
|
||||
nodeEdges.map<EdgeRemoveChange>((edge) => ({
|
||||
onNodesChange(
|
||||
deletedNodeIdList.map<NodeRemoveChange>((id) => ({
|
||||
type: 'remove',
|
||||
id: edge.id
|
||||
id
|
||||
}))
|
||||
);
|
||||
onEdgesChange(
|
||||
deletedEdgeIdList.map<EdgeRemoveChange>((id) => ({
|
||||
type: 'remove',
|
||||
id
|
||||
}))
|
||||
);
|
||||
|
||||
return;
|
||||
});
|
||||
const handleSelectNode = useMemoizedFn((change: NodeSelectionChange) => {
|
||||
// If the node is not selected and the Ctrl key is pressed, select the node
|
||||
|
@@ -110,7 +110,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('common:core.module.variable.variable name')}
|
||||
{t('workflow:Variable_name')}
|
||||
</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
</Tr>
|
||||
|
@@ -80,12 +80,13 @@ const NodeComment = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
menuForbid={{
|
||||
debug: true
|
||||
}}
|
||||
border={'none'}
|
||||
rounded={'none'}
|
||||
bg={'#D8E9FF'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
|
||||
}
|
||||
customStyle={{
|
||||
border: 'none',
|
||||
rounded: 'none',
|
||||
bg: '#D8E9FF',
|
||||
boxShadow:
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
|
||||
}}
|
||||
>
|
||||
<Box w={'full'} h={'full'} position={'relative'}>
|
||||
<Box
|
||||
|
@@ -19,7 +19,8 @@ export const defaultFormInput: UserInputFormItemType = {
|
||||
maxLength: undefined,
|
||||
defaultValue: '',
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
required: false
|
||||
required: false,
|
||||
list: [{ label: '', value: '' }]
|
||||
};
|
||||
|
||||
// Modal for add or edit user input form items
|
||||
@@ -39,10 +40,7 @@ const InputFormEditModal = ({
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
...defaultValue,
|
||||
list: defaultValue.list?.length ? defaultValue.list : [{ label: '', value: '' }]
|
||||
}
|
||||
defaultValues: defaultValue
|
||||
});
|
||||
const { setValue, watch, reset } = form;
|
||||
|
||||
@@ -51,6 +49,7 @@ const InputFormEditModal = ({
|
||||
const maxLength = watch('maxLength');
|
||||
const max = watch('max');
|
||||
const min = watch('min');
|
||||
const defaultInputValue = watch('defaultValue');
|
||||
|
||||
const inputTypeList = [
|
||||
{
|
||||
@@ -111,7 +110,7 @@ const InputFormEditModal = ({
|
||||
reset(defaultFormInput);
|
||||
}
|
||||
},
|
||||
[toast, t, reset, onSubmit, onClose, defaultFormInput, defaultValueType]
|
||||
[defaultValue.key, keys, defaultValueType, isEdit, toast, t, onSubmit, onClose, reset]
|
||||
);
|
||||
|
||||
const onSubmitError = useCallback(
|
||||
@@ -197,6 +196,7 @@ const InputFormEditModal = ({
|
||||
maxLength={maxLength}
|
||||
max={max}
|
||||
min={min}
|
||||
defaultValue={defaultInputValue}
|
||||
onClose={onClose}
|
||||
onSubmitSuccess={onSubmitSuccess}
|
||||
onSubmitError={onSubmitError}
|
||||
|
@@ -81,6 +81,9 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
|
||||
|
||||
@@ -91,19 +94,18 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
(item) => item.key === NodeInputKeyEnum.httpReqUrl
|
||||
) as FlowNodeInputItemType;
|
||||
|
||||
const onChangeUrl = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const onChangeUrl = (value: string) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.httpReqUrl,
|
||||
value: {
|
||||
...requestUrl,
|
||||
value: e.target.value
|
||||
value
|
||||
}
|
||||
});
|
||||
};
|
||||
const onBlurUrl = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const val = e.target.value;
|
||||
const onBlurUrl = (val: string) => {
|
||||
// 拆分params和url
|
||||
const url = val.split('?')[0];
|
||||
const params = val.split('?')[1];
|
||||
@@ -154,6 +156,16 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
}
|
||||
};
|
||||
|
||||
const variables = useCreation(() => {
|
||||
return getEditorVariables({
|
||||
nodeId,
|
||||
nodeList,
|
||||
edges,
|
||||
appDetail,
|
||||
t
|
||||
});
|
||||
}, [nodeId, nodeList, edges, appDetail, t]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
|
||||
@@ -166,7 +178,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
</Box>
|
||||
<Flex alignItems={'center'} className="nodrag">
|
||||
<MySelect
|
||||
h={'34px'}
|
||||
h={'40px'}
|
||||
w={'88px'}
|
||||
bg={'white'}
|
||||
width={'100%'}
|
||||
@@ -205,17 +217,29 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
ml={2}
|
||||
h={'34px'}
|
||||
<Box
|
||||
w={'full'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
rounded={'md'}
|
||||
bg={'white'}
|
||||
value={requestUrl?.value || ''}
|
||||
placeholder={t('common:core.module.input.label.Http Request Url')}
|
||||
fontSize={'xs'}
|
||||
onChange={onChangeUrl}
|
||||
onBlur={onBlurUrl}
|
||||
/>
|
||||
ml={2}
|
||||
>
|
||||
<PromptEditor
|
||||
placeholder={
|
||||
t('common:core.module.input.label.Http Request Url') +
|
||||
', ' +
|
||||
t('common:textarea_variable_picker_tip')
|
||||
}
|
||||
value={requestUrl?.value || ''}
|
||||
variableLabels={variables}
|
||||
variables={variables}
|
||||
onBlur={onBlurUrl}
|
||||
onChange={onChangeUrl}
|
||||
minH={40}
|
||||
showOpenModal={false}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{isOpenCurl && <CurlImportModal nodeId={nodeId} inputs={inputs} onClose={onCloseCurl} />}
|
||||
|
@@ -22,7 +22,8 @@ export const defaultInput: FlowNodeInputItemType = {
|
||||
key: '',
|
||||
label: '',
|
||||
description: '',
|
||||
defaultValue: ''
|
||||
defaultValue: '',
|
||||
list: [{ label: '', value: '' }]
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
@@ -133,10 +134,7 @@ const FieldEditModal = ({
|
||||
|
||||
const isEdit = !!defaultValue.key;
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
...defaultValue,
|
||||
list: defaultValue.list?.length ? defaultValue.list : [{ label: '', value: '' }]
|
||||
}
|
||||
defaultValues: defaultValue
|
||||
});
|
||||
const { getValues, setValue, watch, reset } = form;
|
||||
|
||||
@@ -149,7 +147,7 @@ const FieldEditModal = ({
|
||||
const max = watch('max');
|
||||
const min = watch('min');
|
||||
const selectValueTypeList = watch('customInputConfig.selectValueTypeList');
|
||||
const defaultJsonValue = watch('defaultValue');
|
||||
const defaultInputValue = watch('defaultValue');
|
||||
|
||||
const defaultValueType =
|
||||
inputTypeList.flat().find((item) => item.value === inputType)?.defaultValueType ||
|
||||
@@ -157,9 +155,9 @@ const FieldEditModal = ({
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: FlowNodeInputItemType, action: 'confirm' | 'continue') => {
|
||||
data.key = data?.key?.trim();
|
||||
data.label = data?.label?.trim();
|
||||
|
||||
if (!data.key) {
|
||||
if (!data.label) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.module.edit.Field Name Cannot Be Empty')
|
||||
@@ -199,7 +197,7 @@ const FieldEditModal = ({
|
||||
data.toolDescription = undefined;
|
||||
}
|
||||
|
||||
data.label = data.key;
|
||||
data.key = data.label;
|
||||
|
||||
if (action === 'confirm') {
|
||||
onSubmit(data);
|
||||
@@ -327,7 +325,7 @@ const FieldEditModal = ({
|
||||
max={max}
|
||||
min={min}
|
||||
selectValueTypeList={selectValueTypeList}
|
||||
defaultJsonValue={defaultJsonValue}
|
||||
defaultValue={defaultInputValue}
|
||||
isToolInput={isToolInput}
|
||||
setIsToolInput={setIsToolInput}
|
||||
valueType={valueType}
|
||||
|
@@ -6,11 +6,19 @@ import {
|
||||
FormLabel,
|
||||
HStack,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Stack,
|
||||
Switch,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
VariableInputEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowValueTypeMap
|
||||
@@ -25,6 +33,9 @@ import React, { useMemo } from 'react';
|
||||
import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||
|
||||
type ListValueType = { id: string; value: string; label: string }[];
|
||||
|
||||
const InputTypeConfig = ({
|
||||
form,
|
||||
@@ -36,7 +47,7 @@ const InputTypeConfig = ({
|
||||
max,
|
||||
min,
|
||||
selectValueTypeList,
|
||||
defaultJsonValue,
|
||||
defaultValue,
|
||||
isToolInput,
|
||||
setIsToolInput,
|
||||
valueType,
|
||||
@@ -48,15 +59,15 @@ const InputTypeConfig = ({
|
||||
form: UseFormReturn<any>;
|
||||
isEdit: boolean;
|
||||
onClose: () => void;
|
||||
type: 'plugin' | 'formInput';
|
||||
inputType: FlowNodeInputTypeEnum;
|
||||
type: 'plugin' | 'formInput' | 'variable';
|
||||
inputType: FlowNodeInputTypeEnum | VariableInputEnum;
|
||||
|
||||
maxLength?: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
|
||||
selectValueTypeList?: WorkflowIOValueTypeEnum[];
|
||||
defaultJsonValue?: string;
|
||||
defaultValue?: string;
|
||||
|
||||
// Plugin-specific fields
|
||||
isToolInput?: boolean;
|
||||
@@ -69,8 +80,23 @@ const InputTypeConfig = ({
|
||||
onSubmitError: (e: Object) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultListValue = { label: t('common:None'), value: '' };
|
||||
|
||||
const { register, setValue, handleSubmit, control } = form;
|
||||
const { register, setValue, handleSubmit, control, watch } = form;
|
||||
const listValue: ListValueType = watch('list');
|
||||
|
||||
const typeLabels = {
|
||||
name: {
|
||||
formInput: t('common:core.module.input_name'),
|
||||
plugin: t('common:core.module.Field Name'),
|
||||
variable: t('workflow:Variable_name')
|
||||
},
|
||||
description: {
|
||||
formInput: t('common:core.module.input_description'),
|
||||
plugin: t('workflow:field_description'),
|
||||
variable: t('workflow:variable_description')
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
fields: selectEnums,
|
||||
@@ -81,6 +107,11 @@ const InputTypeConfig = ({
|
||||
name: 'list'
|
||||
});
|
||||
|
||||
const mergedSelectEnums = selectEnums.map((field, index) => ({
|
||||
...field,
|
||||
...listValue[index]
|
||||
}));
|
||||
|
||||
const valueTypeSelectList = Object.values(FlowValueTypeMap).map((item) => ({
|
||||
label: t(item.label as any),
|
||||
value: item.value
|
||||
@@ -88,21 +119,26 @@ const InputTypeConfig = ({
|
||||
|
||||
const showValueTypeSelect =
|
||||
inputType === FlowNodeInputTypeEnum.reference ||
|
||||
inputType === FlowNodeInputTypeEnum.customVariable;
|
||||
inputType === FlowNodeInputTypeEnum.customVariable ||
|
||||
inputType === VariableInputEnum.custom;
|
||||
|
||||
const showRequired = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.addInputParam, FlowNodeInputTypeEnum.customVariable];
|
||||
const list = [
|
||||
FlowNodeInputTypeEnum.addInputParam,
|
||||
FlowNodeInputTypeEnum.customVariable,
|
||||
VariableInputEnum.custom
|
||||
];
|
||||
return !list.includes(inputType);
|
||||
}, [inputType]);
|
||||
|
||||
const showMaxLenInput = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.textarea];
|
||||
return list.includes(inputType);
|
||||
return list.includes(inputType as FlowNodeInputTypeEnum);
|
||||
}, [inputType]);
|
||||
|
||||
const showMinMaxInput = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.numberInput];
|
||||
return list.includes(inputType);
|
||||
return list.includes(inputType as FlowNodeInputTypeEnum);
|
||||
}, [inputType]);
|
||||
|
||||
const showDefaultValue = useMemo(() => {
|
||||
@@ -111,34 +147,31 @@ const InputTypeConfig = ({
|
||||
FlowNodeInputTypeEnum.textarea,
|
||||
FlowNodeInputTypeEnum.JSONEditor,
|
||||
FlowNodeInputTypeEnum.numberInput,
|
||||
FlowNodeInputTypeEnum.switch
|
||||
FlowNodeInputTypeEnum.switch,
|
||||
FlowNodeInputTypeEnum.select
|
||||
];
|
||||
|
||||
return list.includes(inputType);
|
||||
return list.includes(inputType as FlowNodeInputTypeEnum);
|
||||
}, [inputType]);
|
||||
|
||||
return (
|
||||
<Stack flex={1} borderLeft={'1px solid #F0F1F6'} justifyContent={'space-between'}>
|
||||
<Flex flexDirection={'column'} p={8} gap={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<Flex flexDirection={'column'} p={8} pb={2} gap={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{type === 'formInput'
|
||||
? t('common:core.module.input_name')
|
||||
: t('common:core.module.Field Name')}
|
||||
{typeLabels.name[type] || typeLabels.name.formInput}
|
||||
</FormLabel>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register(type === 'formInput' ? 'label' : 'key', {
|
||||
{...register('label', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'flex-start'}>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{type === 'formInput'
|
||||
? t('common:core.module.input_description')
|
||||
: t('workflow:field_description')}
|
||||
{typeLabels.description[type] || typeLabels.description.plugin}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
@@ -149,7 +182,7 @@ const InputTypeConfig = ({
|
||||
</Flex>
|
||||
|
||||
{/* value type */}
|
||||
{type === 'plugin' && (
|
||||
{type !== 'formInput' && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{t('common:core.module.Data Type')}
|
||||
@@ -167,13 +200,15 @@ const InputTypeConfig = ({
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box fontSize={'14px'}>{defaultValueType}</Box>
|
||||
<Box fontSize={'14px'} mb={2}>
|
||||
{defaultValueType}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} minH={'40px'}>
|
||||
<FormLabel flex={'1'} fontWeight={'medium'}>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{t('workflow:field_required')}
|
||||
</FormLabel>
|
||||
<Switch {...register('required')} />
|
||||
@@ -191,7 +226,6 @@ const InputTypeConfig = ({
|
||||
isChecked={isToolInput}
|
||||
onChange={(e) => {
|
||||
setIsToolInput && setIsToolInput();
|
||||
console.log(isToolInput);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -208,6 +242,7 @@ const InputTypeConfig = ({
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('common:core.module.Max Length placeholder')}
|
||||
value={maxLength}
|
||||
max={50000}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('maxLength', e || '');
|
||||
@@ -258,13 +293,18 @@ const InputTypeConfig = ({
|
||||
{t('common:core.module.Default Value')}
|
||||
</FormLabel>
|
||||
{inputType === FlowNodeInputTypeEnum.numberInput && (
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
max={max}
|
||||
min={min}
|
||||
type={'number'}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
<NumberInput flex={1} step={1} min={min} max={max} position={'relative'}>
|
||||
<NumberInputField
|
||||
{...register('defaultValue', {
|
||||
min: min,
|
||||
max: max
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.input && (
|
||||
<Input bg={'myGray.50'} maxLength={maxLength} {...register('defaultValue')} />
|
||||
@@ -280,10 +320,29 @@ const InputTypeConfig = ({
|
||||
onChange={(e) => {
|
||||
setValue('defaultValue', e);
|
||||
}}
|
||||
defaultValue={String(defaultJsonValue)}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.switch && <Switch {...register('defaultValue')} />}
|
||||
{inputType === FlowNodeInputTypeEnum.select && (
|
||||
<MySelect<string>
|
||||
list={[defaultListValue, ...listValue]
|
||||
.filter((item) => item.label !== '')
|
||||
.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
}))}
|
||||
value={
|
||||
defaultValue && listValue.map((item) => item.value).includes(defaultValue)
|
||||
? defaultValue
|
||||
: ''
|
||||
}
|
||||
onchange={(e) => {
|
||||
setValue('defaultValue', e);
|
||||
}}
|
||||
w={'200px'}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -314,40 +373,116 @@ const InputTypeConfig = ({
|
||||
|
||||
{inputType === FlowNodeInputTypeEnum.select && (
|
||||
<>
|
||||
<Flex flexDirection={'column'} gap={4}>
|
||||
{selectEnums.map((item, i) => (
|
||||
<Flex key={item.id} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{`${t('common:core.module.variable.variable options')} ${i + 1}`}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
fontSize={'12px'}
|
||||
bg={'myGray.50'}
|
||||
placeholder={`${t('common:core.module.variable.variable options')} ${i + 1}`}
|
||||
{...register(`list.${i}.label`, {
|
||||
required: true,
|
||||
onChange: (e: any) => {
|
||||
setValue(`list.${i}.value`, e.target.value);
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
{selectEnums.length > 1 && (
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
<DndDrag<{ id: string; value: string }>
|
||||
onDragEndCb={(list) => {
|
||||
const newOrder = list.map((item) => item.id);
|
||||
const newSelectEnums = newOrder
|
||||
.map((id) => mergedSelectEnums.find((item) => item.id === id))
|
||||
.filter(Boolean) as { id: string; value: string }[];
|
||||
removeEnums();
|
||||
newSelectEnums.forEach((item) => appendEnums(item));
|
||||
|
||||
// 防止最后一个元素被focus
|
||||
setTimeout(() => {
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
dataList={mergedSelectEnums}
|
||||
renderClone={(provided, snapshot, rubric) => {
|
||||
return (
|
||||
<Box
|
||||
bg={'myGray.50'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
p={2}
|
||||
borderRadius="md"
|
||||
boxShadow="md"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
{mergedSelectEnums[rubric.source.index].value}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(provided) => (
|
||||
<Box
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
gap={4}
|
||||
>
|
||||
{mergedSelectEnums.map((item, i) => (
|
||||
<Draggable key={i} draggableId={i.toString()} index={i}>
|
||||
{(provided, snapshot) => (
|
||||
<Box
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
opacity: snapshot.isDragging ? 0.8 : 1
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
transform={snapshot.isDragging ? `scale(0.5)` : ''}
|
||||
transformOrigin={'top left'}
|
||||
>
|
||||
<FormLabel flex={'0 0 100px'} fontWeight={'medium'}>
|
||||
{`${t('common:core.module.variable.variable options')} ${i + 1}`}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
fontSize={'12px'}
|
||||
bg={'myGray.50'}
|
||||
placeholder={`${t('common:core.module.variable.variable options')} ${i + 1}`}
|
||||
{...register(`list.${i}.label`, {
|
||||
required: true,
|
||||
onChange: (e: any) => {
|
||||
setValue(`list.${i}.value`, e.target.value);
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
{selectEnums.length > 1 && (
|
||||
<Flex>
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
<Box {...provided.dragHandleProps}>
|
||||
<MyIcon
|
||||
name={'drag'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
w={'16px'}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
<Box h="0" w="0">
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</DndDrag>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'16px'} />}
|
||||
|
@@ -21,9 +21,7 @@ const VariableTable = ({
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('common:core.module.variable.variable name')}
|
||||
</Th>
|
||||
<Th borderBottomLeftRadius={'none !important'}>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
|
||||
<Th borderBottomRightRadius={'none !important'}></Th>
|
||||
|
@@ -38,10 +38,7 @@ const NodeSimple = ({
|
||||
{filterHiddenInputs.length > 0 && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle
|
||||
text={t('common:common.Input')}
|
||||
inputExplanationUrl={data.inputExplanationUrl}
|
||||
/>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
<RenderInput nodeId={nodeId} flowInputList={commonInputs} />
|
||||
</Container>
|
||||
</>
|
||||
|
@@ -25,6 +25,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
|
||||
type Props = FlowNodeItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -39,7 +41,8 @@ type Props = FlowNodeItemType & {
|
||||
copy?: boolean;
|
||||
delete?: boolean;
|
||||
};
|
||||
} & Omit<FlexProps, 'children'>;
|
||||
customStyle?: FlexProps;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -63,9 +66,8 @@ const NodeCard = (props: Props) => {
|
||||
isError = false,
|
||||
debugResult,
|
||||
isFolded,
|
||||
...customStyle
|
||||
customStyle
|
||||
} = props;
|
||||
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId);
|
||||
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
|
||||
@@ -102,14 +104,6 @@ const NodeCard = (props: Props) => {
|
||||
if (!node?.pluginId) return;
|
||||
const template = await getPreviewPluginNode({ appId: node.pluginId });
|
||||
|
||||
// Focus update plugin latest inputExplanationUrl
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'inputExplanationUrl',
|
||||
value: template.inputExplanationUrl
|
||||
});
|
||||
|
||||
return template;
|
||||
} else {
|
||||
const template = moduleTemplatesFlat.find(
|
||||
@@ -275,6 +269,24 @@ const NodeCard = (props: Props) => {
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!nodeTemplate?.diagram && node?.courseUrl && (
|
||||
<Box bg={'myGray.300'} w={'1px'} h={'12px'} mx={1} />
|
||||
)}
|
||||
{node?.courseUrl && !hasNewVersion && (
|
||||
<MyTooltip label={t('workflow:Node.Open_Node_Course')}>
|
||||
<MyIcon
|
||||
cursor={'pointer'}
|
||||
name="book"
|
||||
color={'primary.600'}
|
||||
w={'18px'}
|
||||
ml={1}
|
||||
_hover={{
|
||||
color: 'primary.800'
|
||||
}}
|
||||
onClick={() => window.open(getDocPath(node.courseUrl || ''), '_blank')}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
<NodeIntro nodeId={nodeId} intro={intro} />
|
||||
</Box>
|
||||
@@ -295,6 +307,7 @@ const NodeCard = (props: Props) => {
|
||||
onOpenConfirmSync,
|
||||
onClickSyncVersion,
|
||||
nodeTemplate?.diagram,
|
||||
node?.courseUrl,
|
||||
intro,
|
||||
menuForbid,
|
||||
nodeList,
|
||||
|
@@ -148,11 +148,13 @@ type WorkflowContextType = {
|
||||
onStartNodeDebug: ({
|
||||
entryNodeId,
|
||||
runtimeNodes,
|
||||
runtimeEdges
|
||||
runtimeEdges,
|
||||
variables
|
||||
}: {
|
||||
entryNodeId: string;
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
variables: Record<string, any>;
|
||||
}) => Promise<void>;
|
||||
onStopNodeDebug: () => void;
|
||||
|
||||
@@ -749,17 +751,19 @@ const WorkflowContextProvider = ({
|
||||
async ({
|
||||
entryNodeId,
|
||||
runtimeNodes,
|
||||
runtimeEdges
|
||||
runtimeEdges,
|
||||
variables
|
||||
}: {
|
||||
entryNodeId: string;
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
variables: Record<string, any>;
|
||||
}) => {
|
||||
const data = {
|
||||
runtimeNodes,
|
||||
runtimeEdges,
|
||||
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
|
||||
variables: {}
|
||||
variables
|
||||
};
|
||||
onStopNodeDebug();
|
||||
setWorkflowDebugData(data);
|
||||
|
Reference in New Issue
Block a user