4.7-production (#1053)

* 4.7-alpha3 (#62)

* doc

* Optimize possible null Pointers and parts of Ux

* fix: mulity index training error

* feat: doc and rename question guide

* fix ios speech input (#59)

* fix: prompt editor variables nowrap (#61)

* change openapi import in http module with curl import (#60)

* chore(ui): dataset import modal ui (#58)

* chore(ui): dataset import modal ui

* use component

* fix height

* 4.7 (#63)

* fix: claude3 image type verification failed (#1038) (#1040)

* perf: curl import modal

* doc img

* perf: adapt cohere rerank

* perf: code

* perf: input style

* doc

---------

Co-authored-by: xiaotian <dimsky@163.com>

* fix: ts

* docker deploy

* perf: prompt call

* doc

* ts

* finish ui

* perf: outlink detail ux

* perf: user schema

* fix: plugin update

* feat: get current time plugin

* fix: ts

* perf: fetch anamation

* perf: mark ux

* doc

* perf: select app ux

* fix: split text custom string conflict

* peref: inform readed

* doc

* memo flow component

* perf: version

* faq

* feat: flow max runtimes

* feat: similarity tip

* feat: auto detect file encoding

* Supports asymmetric vector model

* fix: ts

* perf: max w

* move code

* perf: hide whisper

* fix: ts

* feat: system msg modal

* perf: catch error

* perf: inform tip

* fix: inform

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
Co-authored-by: xiaotian <dimsky@163.com>
This commit is contained in:
Archer
2024-03-26 12:09:31 +08:00
committed by GitHub
parent ef15ca894e
commit 911512b36d
180 changed files with 2179 additions and 1361 deletions

View File

@@ -30,7 +30,8 @@ const RawSourceBox = ({ sourceId, sourceName = '', canView = true, ...props }: P
shouldWrapChildren={false}
>
<Box
color={'myGray.600'}
color={'myGray.900'}
fontWeight={'medium'}
display={'inline-flex'}
whiteSpace={'nowrap'}
{...(canPreview

View File

@@ -5,6 +5,7 @@ import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { Box } from '@chakra-ui/react';
import ParentPaths from '@/components/common/ParentPaths';
import MyBox from '@/components/common/MyBox';
type PathItemType = {
parentId: string;
@@ -17,6 +18,7 @@ const DatasetSelectContainer = ({
paths,
onClose,
tips,
isLoading,
children
}: {
isOpen: boolean;
@@ -24,6 +26,7 @@ const DatasetSelectContainer = ({
paths: PathItemType[];
onClose: () => void;
tips?: string | null;
isLoading?: boolean;
children: React.ReactNode;
}) => {
const { t } = useTranslation();
@@ -57,7 +60,9 @@ const DatasetSelectContainer = ({
maxW={['90vw', '900px']}
isCentered
>
{children}
<MyBox isLoading={isLoading} h={'100%'}>
{children}
</MyBox>
</MyModal>
);
};

View File

@@ -81,6 +81,8 @@ const DatasetParamsModal = ({
const datasetSearchUsingCfrForm = watch('datasetSearchUsingExtensionQuery');
const queryExtensionModel = watch('datasetSearchExtensionModel');
const cfbBgDesc = watch('datasetSearchExtensionBg');
const usingReRankWatch = watch('usingReRank');
const searchModeWatch = watch('searchMode');
const chatModelSelectList = (() =>
llmModelList
@@ -97,16 +99,10 @@ const DatasetParamsModal = ({
const showSimilarity = useMemo(() => {
if (similarity === undefined) return false;
if (
getValues('searchMode') === DatasetSearchModeEnum.fullTextRecall &&
!getValues('usingReRank')
)
return false;
if (getValues('searchMode') === DatasetSearchModeEnum.mixedRecall && !getValues('usingReRank'))
return false;
return true;
}, [getValues, similarity]);
if (usingReRankWatch) return true;
if (searchModeWatch === DatasetSearchModeEnum.embedding) return true;
return false;
}, [searchModeWatch, similarity, usingReRankWatch]);
const showReRank = useMemo(() => {
return usingReRank !== undefined && reRankModelList.length > 0;
@@ -155,64 +151,57 @@ const DatasetParamsModal = ({
setRefresh(!refresh);
}}
/>
{showReRank && (
<>
<Divider my={4} />
<Flex
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
py={3}
pl={'14px'}
pr={'16px'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
position={'relative'}
{...(getValues('usingReRank')
? {
borderColor: 'primary.400'
}
: {})}
onClick={(e) => {
if (
teamPlanStatus?.standardConstants &&
!teamPlanStatus?.standardConstants?.permissionReRank
) {
return toast({
status: 'warning',
title: t('support.team.limit.No permission rerank')
});
<>
<Divider my={4} />
<Flex
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
py={3}
pl={'14px'}
pr={'16px'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
position={'relative'}
{...(getValues('usingReRank')
? {
borderColor: 'primary.400'
}
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
}}
>
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
<Box>{t('core.dataset.search.ReRank')}</Box>
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
{t('core.dataset.search.ReRank desc')}
</Box>
: {})}
onClick={(e) => {
if (!showReRank) {
return toast({
status: 'warning',
title: t('core.ai.Not deploy rerank model')
});
}
if (
teamPlanStatus?.standardConstants &&
!teamPlanStatus?.standardConstants?.permissionReRank
) {
return toast({
status: 'warning',
title: t('support.team.limit.No permission rerank')
});
}
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
}}
>
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
<Box>{t('core.dataset.search.ReRank')}</Box>
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
{t('core.dataset.search.ReRank desc')}
</Box>
<Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox
colorScheme="primary"
isChecked={getValues('usingReRank')}
size="lg"
/>
<Box
position={'absolute'}
top={0}
right={0}
bottom={0}
left={0}
zIndex={1}
></Box>
</Box>
</Flex>
</>
)}
</Box>
<Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" />
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
</Box>
</Flex>
</>
</>
)}
{currentTabType === SearchSettingTabEnum.limit && (
@@ -243,15 +232,15 @@ const DatasetParamsModal = ({
</Box>
</Box>
)}
{showSimilarity && (
<Box display={['block', 'flex']} mt={10}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<Box display={['block', 'flex']} mt={10}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
{showSimilarity ? (
<MySlider
markList={[
{ label: '0', value: 0 },
@@ -266,9 +255,11 @@ const DatasetParamsModal = ({
setRefresh(!refresh);
}}
/>
</Box>
) : (
<Box color={'myGray.500'}>{t('core.dataset.search.No support similarity')}</Box>
)}
</Box>
)}
</Box>
</Box>
)}
{currentTabType === SearchSettingTabEnum.queryExtension && (

View File

@@ -20,7 +20,7 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { moduleTemplatesList } from '@fastgpt/global/core/module/template/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import ParentPaths from '@/components/common/ParentPaths';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
@@ -70,7 +70,7 @@ const ModuleTemplateList = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchKey ? item.pluginType !== PluginTypeEnum.folder : true
)
};
return map[templateType];
return JSON.stringify(map[templateType]);
}, [basicNodeTemplates, searchKey, systemNodeTemplates, teamPluginNodeTemplates, templateType]);
const { mutate: onChangeTab } = useRequest({
@@ -96,120 +96,125 @@ const ModuleTemplateList = ({ isOpen, onClose }: ModuleTemplateListProps) => {
})
);
return (
<>
<Box
zIndex={2}
display={isOpen ? 'block' : 'none'}
position={'absolute'}
top={0}
left={0}
bottom={0}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'10px'}
left={0}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
/>
</Flex>
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
const Render = useMemo(() => {
const parseTemplates = JSON.parse(templates) as FlowNodeTemplateType[];
return (
<>
<Box
zIndex={2}
display={isOpen ? 'block' : 'none'}
position={'absolute'}
top={0}
left={0}
bottom={0}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'10px'}
left={0}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
/>
</Flex>
)}
</Box>
<RenderList
templates={templates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
)}
</Box>
<RenderList
templates={parseTemplates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
}, [currentParent, isOpen, onChangeTab, onClose, router, searchKey, t, templateType, templates]);
return Render;
};
export default React.memo(ModuleTemplateList);

View File

@@ -40,11 +40,10 @@ const SelectAppModal = ({
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
iconSrc="/imgs/module/ai.svg"
onClose={onClose}
minW={'700px'}
position={'relative'}
w={'600px'}
>
<ModalBody
minH={'300px'}
display={'grid'}
gridTemplateColumns={['1fr', 'repeat(3, minmax(0, 1fr))']}
gridGap={4}

View File

@@ -5,12 +5,13 @@ import { Box, Flex, Switch, type SwitchProps } from '@chakra-ui/react';
import React from 'react';
import { useTranslation } from 'next-i18next';
// question generator switch
const QGSwitch = (props: SwitchProps) => {
const { t } = useTranslation();
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/questionGuide'} mr={2} w={'20px'} />
<Box>{t('core.app.Next Step Guide')}</Box>
<MyIcon name={'core/chat/QGFill'} mr={2} w={'20px'} />
<Box>{t('core.app.Question Guide')}</Box>
<MyTooltip label={t('core.app.Question Guide Tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>

View File

@@ -124,12 +124,12 @@ const VariableEdit = ({
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th w={'18px !important'} p={0} bg={'myGray.50'} />
<Th bg={'myGray.50'}>{t('core.module.variable.variable name')}</Th>
<Th bg={'myGray.50'}>{t('core.module.variable.key')}</Th>
<Th bg={'myGray.50'}>{t('common.Require Input')}</Th>
<Th bg={'myGray.50'}></Th>
<Tr bg={'myGray.50'}>
<Th w={'18px !important'} p={0} />
<Th>{t('core.module.variable.variable name')}</Th>
<Th>{t('core.module.variable.key')}</Th>
<Th>{t('common.Require Input')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
import NodeCard from '../render/NodeCard';
@@ -16,93 +16,69 @@ import { useTranslation } from 'next-i18next';
import SourceHandle from '../render/SourceHandle';
import MyTooltip from '@/components/MyTooltip';
import { onChangeNode } from '../../FlowProvider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
const NodeCQNode = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const { t } = useTranslation();
const { moduleId, inputs } = data;
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
<Divider text={t('common.Input')} />
<Container>
<RenderInput
moduleId={moduleId}
flowInputList={inputs}
CustomComponent={{
[ModuleInputKeyEnum.agents]: ({ key: agentKey, value = [], ...props }) => {
const agents = value as ClassifyQuestionAgentItemType[];
return (
<Box>
{agents.map((item, i) => (
<Box key={item.key} mb={4}>
<Flex alignItems={'center'}>
<MyTooltip label={t('common.Delete')}>
<MyIcon
mt={1}
mr={2}
name={'minus'}
w={'14px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: agentKey,
value: {
...props,
key: agentKey,
value: agents.filter((input) => input.key !== item.key)
}
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</MyTooltip>
<Box flex={1}>{i + 1}</Box>
</Flex>
<Box position={'relative'}>
<Textarea
rows={2}
mt={1}
defaultValue={item.value}
onChange={(e) => {
const newVal = agents.map((val) =>
val.key === item.key
? {
...val,
value: e.target.value
}
: val
);
onChangeNode({
moduleId,
type: 'updateInput',
key: agentKey,
value: {
...props,
key: agentKey,
value: newVal
}
});
}}
/>
<SourceHandle
handleKey={item.key}
valueType={ModuleIOValueTypeEnum.boolean}
/>
</Box>
</Box>
))}
<Button
onClick={() => {
const key = nanoid();
const CustomComponent = useMemo(
() => ({
[ModuleInputKeyEnum.agents]: ({
key: agentKey,
value = [],
...props
}: FlowNodeInputItemType) => {
const agents = value as ClassifyQuestionAgentItemType[];
return (
<Box>
{agents.map((item, i) => (
<Box key={item.key} mb={4}>
<Flex alignItems={'center'}>
<MyTooltip label={t('common.Delete')}>
<MyIcon
mt={1}
mr={2}
name={'minus'}
w={'14px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: agentKey,
value: {
...props,
key: agentKey,
value: agents.filter((input) => input.key !== item.key)
}
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</MyTooltip>
<Box flex={1}>{i + 1}</Box>
</Flex>
<Box position={'relative'}>
<Textarea
rows={2}
mt={1}
defaultValue={item.value}
onChange={(e) => {
const newVal = agents.map((val) =>
val.key === item.key
? {
...val,
value: e.target.value
}
: val
);
onChangeNode({
moduleId,
type: 'updateInput',
@@ -110,29 +86,56 @@ const NodeCQNode = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
value: {
...props,
key: agentKey,
value: agents.concat({ value: '', key })
}
});
onChangeNode({
moduleId,
type: 'addOutput',
value: {
key,
label: '',
type: FlowNodeOutputTypeEnum.hidden,
targets: []
value: newVal
}
});
}}
>
{t('core.module.Add question type')}
</Button>
/>
<SourceHandle handleKey={item.key} valueType={ModuleIOValueTypeEnum.boolean} />
</Box>
);
}
}}
/>
</Box>
))}
<Button
onClick={() => {
const key = nanoid();
onChangeNode({
moduleId,
type: 'updateInput',
key: agentKey,
value: {
...props,
key: agentKey,
value: agents.concat({ value: '', key })
}
});
onChangeNode({
moduleId,
type: 'addOutput',
value: {
key,
label: '',
type: FlowNodeOutputTypeEnum.hidden,
targets: []
}
});
}}
>
{t('core.module.Add question type')}
</Button>
</Box>
);
}
}),
[moduleId, t]
);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
<Divider text={t('common.Input')} />
<Container>
<RenderInput moduleId={moduleId} flowInputList={inputs} CustomComponent={CustomComponent} />
</Container>
</NodeCard>
);

View File

@@ -20,6 +20,7 @@ import SourceHandle from '../render/SourceHandle';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const { t } = useTranslation();
@@ -27,7 +28,10 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) =>
const { nodes } = useFlowProviderStore();
const { moduleId, inputs, outputs } = data;
const quotes = inputs.filter((item) => item.valueType === ModuleIOValueTypeEnum.datasetQuote);
const quotes = useMemo(
() => inputs.filter((item) => item.valueType === ModuleIOValueTypeEnum.datasetQuote),
[inputs]
);
const tokenLimit = useMemo(() => {
let maxTokens = 3000;
@@ -46,8 +50,8 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) =>
return maxTokens;
}, [llmModelList, nodes]);
const RenderQuoteList = useMemo(
() => (
const RenderQuoteList = useMemo(() => {
return (
<Box>
<Box>
{quotes.map((quote, i) => (
@@ -88,45 +92,45 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) =>
{t('core.module.Dataset quote.Add quote')}
</Button>
</Box>
),
[moduleId, quotes, t]
);
);
}, [moduleId, quotes, t]);
const CustomComponent = useMemo(() => {
console.log(111);
return {
[ModuleInputKeyEnum.datasetMaxTokens]: (item: FlowNodeInputItemType) => (
<Box px={2}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: tokenLimit, value: tokenLimit }
]}
width={'100%'}
min={100}
max={tokenLimit}
step={50}
value={item.value}
onChange={(e) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}}
/>
</Box>
)
};
}, [moduleId, tokenLimit]);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} position={'relative'}>
<RenderInput
moduleId={moduleId}
flowInputList={inputs}
CustomComponent={{
[ModuleInputKeyEnum.datasetMaxTokens]: (item) => (
<Box px={2}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: tokenLimit, value: tokenLimit }
]}
width={'100%'}
min={100}
max={tokenLimit}
step={50}
value={item.value}
onChange={(e) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}}
/>
</Box>
)
}}
/>
<RenderInput moduleId={moduleId} flowInputList={inputs} CustomComponent={CustomComponent} />
{/* render dataset select */}
{RenderQuoteList}
<Flex position={'absolute'} right={4} top={'60%'}>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import {
Box,
Button,
@@ -28,6 +28,7 @@ import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constan
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
import { onChangeNode, useFlowProviderStore } from '../../../FlowProvider';
import RenderToolInput from '../../render/RenderToolInput';
import { FlowNodeInputItemType } from '../../../../../../../../../../packages/global/core/module/node/type';
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, outputs, moduleId } = data;
@@ -36,6 +37,99 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
const { t } = useTranslation();
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
const CustomComponent = useMemo(
() => ({
[ModuleInputKeyEnum.extractKeys]: ({
value: extractKeys = [],
...props
}: Omit<FlowNodeInputItemType, 'value'> & {
value?: ContextExtractAgentItemType[];
}) => (
<Box>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'}>{t('core.module.extract.Target field')}</Box>
<Button
size={'sm'}
variant={'whitePrimary'}
leftIcon={<AddIcon fontSize={'10px'} />}
onClick={() => setEditExtractField(defaultField)}
>
{t('core.module.extract.Add field')}
</Button>
</Flex>
<Box
mt={2}
borderRadius={'md'}
overflow={'hidden'}
borderWidth={'1px'}
borderBottom="none"
>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th bg={'myGray.50'}> key</Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
</Tr>
</Thead>
<Tbody>
{extractKeys.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
>
<Td>{item.key}</Td>
<Td>{item.desc}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
setEditExtractField(item);
}}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.extractKeys,
value: {
...props,
value: extractKeys.filter((extract) => item.key !== extract.key)
}
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
)
}),
[moduleId, t]
);
return (
<NodeCard minW={'400px'} {...data}>
{toolInputs.length > 0 && (
@@ -52,97 +146,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
<RenderInput
moduleId={moduleId}
flowInputList={commonInputs}
CustomComponent={{
[ModuleInputKeyEnum.extractKeys]: ({
value: extractKeys = [],
...props
}: {
value?: ContextExtractAgentItemType[];
}) => (
<Box>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'}>{t('core.module.extract.Target field')}</Box>
<Button
size={'sm'}
variant={'whitePrimary'}
leftIcon={<AddIcon fontSize={'10px'} />}
onClick={() => setEditExtractField(defaultField)}
>
{t('core.module.extract.Add field')}
</Button>
</Flex>
<Box
mt={2}
borderRadius={'md'}
overflow={'hidden'}
borderWidth={'1px'}
borderBottom="none"
>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th bg={'myGray.50'}> key</Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
</Tr>
</Thead>
<Tbody>
{extractKeys.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
>
<Td>{item.key}</Td>
<Td>{item.desc}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
setEditExtractField(item);
}}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.extractKeys,
value: {
...props,
value: extractKeys.filter(
(extract) => item.key !== extract.key
)
}
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
)
}}
CustomComponent={CustomComponent}
/>
</Container>
</>

View File

@@ -6,8 +6,8 @@ import { onChangeNode } from '../../../FlowProvider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import yaml from 'js-yaml';
import { useForm } from 'react-hook-form';
import parse from '@bany/curl-to-json';
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = {
@@ -18,20 +18,19 @@ const methodMap: { [K in RequestMethod]: string } = {
patch: 'PATCH'
};
const OpenApiImportModal = ({
children,
const CurlImportModal = ({
moduleId,
inputs
inputs,
onClose
}: {
children: React.ReactElement;
moduleId: string;
inputs: FlowNodeInputItemType[];
onClose: () => void;
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { t } = useTranslation();
const { register, handleSubmit } = useForm({
defaultValues: {
openapiContent: ''
curlContent: ''
}
});
@@ -39,60 +38,36 @@ const OpenApiImportModal = ({
const handleFileProcessing = async (content: string) => {
try {
let data;
try {
data = JSON.parse(content);
} catch (jsonError) {
try {
data = yaml.load(content, { schema: yaml.FAILSAFE_SCHEMA });
} catch (yamlError) {
console.error(yamlError);
throw new Error();
}
}
const firstPathName = Object.keys(data.paths)[0];
const firstPathData = data.paths[firstPathName];
const firstRequestMethod = Object.keys(firstPathData)[0];
const firstRequestMethodData = firstPathData[firstRequestMethod];
const firstRequestParameters = firstRequestMethodData.parameters || [];
const pathParams = [];
const headerParams = [];
for (const parameter of firstRequestParameters) {
if (parameter.in === 'path') {
pathParams.push({
key: parameter.name,
type: parameter.schema.type
});
} else {
headerParams.push({
key: parameter.name,
type: parameter.schema.type
});
}
}
const requestBodySchema =
firstRequestMethodData.requestBody?.content?.['application/json']?.schema;
let requestBodyValue = '';
if (requestBodySchema) {
requestBodyValue = JSON.stringify(requestBodySchema, null, 2);
}
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
const requestMethod = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod);
const params = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
const headers = inputs.find((item) => item.key === ModuleInputKeyEnum.httpHeaders);
const jsonBody = inputs.find((item) => item.key === ModuleInputKeyEnum.httpJsonBody);
const parsed = parse(content);
if (!parsed.url) {
throw new Error('url not found');
}
const newParams = Object.keys(parsed.params || {}).map((key) => ({
key,
value: parsed.params?.[key],
type: 'string'
}));
const newHeaders = Object.keys(parsed.header || {}).map((key) => ({
key,
value: parsed.header?.[key],
type: 'string'
}));
const newBody = JSON.stringify(parsed.data, null, 2);
onChangeNode({
moduleId,
type: 'updateInput',
key: ModuleInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
value: firstPathName
value: parsed.url
}
});
@@ -102,7 +77,7 @@ const OpenApiImportModal = ({
key: ModuleInputKeyEnum.httpMethod,
value: {
...requestMethod,
value: methodMap[firstRequestMethod.toLowerCase() as RequestMethod] || 'GET'
value: methodMap[parsed.method?.toLowerCase() as RequestMethod] || 'GET'
}
});
@@ -112,7 +87,7 @@ const OpenApiImportModal = ({
key: ModuleInputKeyEnum.httpParams,
value: {
...params,
value: pathParams
value: newParams
}
});
@@ -122,7 +97,7 @@ const OpenApiImportModal = ({
key: ModuleInputKeyEnum.httpHeaders,
value: {
...headers,
value: headerParams
value: newHeaders
}
});
@@ -132,7 +107,7 @@ const OpenApiImportModal = ({
key: ModuleInputKeyEnum.httpJsonBody,
value: {
...jsonBody,
value: requestBodyValue
value: newBody
}
});
@@ -153,33 +128,28 @@ const OpenApiImportModal = ({
};
return (
<>
{children && <Box onClick={onOpen}>{children}</Box>}
<MyModal
isOpen={isOpen}
onClose={onClose}
iconSrc="modal/edit"
title={t('common.Import')}
m={'auto'}
w={500}
>
<ModalBody>
<Textarea
height={400}
maxH={500}
mt={2}
{...register('openapiContent')}
placeholder={t('core.module.http.OpenAPI import placeholder')}
/>
</ModalBody>
<ModalFooter>
<Button onClick={handleSubmit((data) => handleFileProcessing(data.openapiContent))}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
</>
<MyModal
isOpen
onClose={onClose}
iconSrc="modal/edit"
title={t('core.module.http.curl import')}
w={600}
>
<ModalBody>
<Textarea
rows={20}
mt={2}
{...register('curlContent')}
placeholder={t('core.module.http.curl import placeholder')}
/>
</ModalBody>
<ModalFooter>
<Button onClick={handleSubmit((data) => handleFileProcessing(data.curlContent))}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(OpenApiImportModal);
export default React.memo(CurlImportModal);

View File

@@ -17,7 +17,8 @@ import {
Th,
Td,
TableContainer,
Button
Button,
useDisclosure
} from '@chakra-ui/react';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { onChangeNode, useFlowProviderStore } from '../../../FlowProvider';
@@ -39,7 +40,7 @@ import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import dynamic from 'next/dynamic';
import MySelect from '@fastgpt/web/components/common/MySelect';
import RenderToolInput from '../../render/RenderToolInput';
const OpenApiImportModal = dynamic(() => import('./OpenApiImportModal'));
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
export const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
@@ -104,6 +105,8 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
const { toast } = useToast();
const [_, startSts] = useTransition();
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
const requestMethods = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod);
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
@@ -180,11 +183,9 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
<Box>
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
<Box>{t('core.module.Http request settings')}</Box>
<Box>
<OpenApiImportModal moduleId={moduleId} inputs={inputs}>
<Button variant={'link'}>{t('core.module.http.OpenAPI import')}</Button>
</OpenApiImportModal>
</Box>
<Button variant={'link'} onClick={onOpenCurl}>
{t('core.module.http.curl import')}
</Button>
</Box>
<Flex alignItems={'center'} className="nodrag">
<MySelect
@@ -228,16 +229,18 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
}}
/>
<Input
flex={'1 0 0'}
ml={2}
h={'34px'}
value={requestUrl?.value}
placeholder={t('core.module.input.label.Http Request Url')}
fontSize={'xs'}
w={'350px'}
onChange={onChangeUrl}
onBlur={onBlurUrl}
/>
</Flex>
{isOpenCurl && <CurlImportModal moduleId={moduleId} inputs={inputs} onClose={onCloseCurl} />}
</Box>
);
});

View File

@@ -1,27 +0,0 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
import { useTranslation } from 'next-i18next';
const NodeRunAPP = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const { t } = useTranslation();
const { moduleId, inputs, outputs } = data;
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} flowInputList={inputs} />
</Container>
<Divider text={t('common.Output')} />
<Container>
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);
};
export default React.memo(NodeRunAPP);

View File

@@ -9,6 +9,7 @@ import RenderOutput from '../render/RenderOutput';
import RenderToolInput from '../render/RenderToolInput';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../FlowProvider';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
const NodeSimple = ({
data,
@@ -44,7 +45,7 @@ const NodeSimple = ({
</Container>
</>
)}
{outputs.length > 0 && (
{outputs.filter((output) => output.type !== FlowNodeOutputTypeEnum.hidden).length > 0 && (
<>
<Divider text={t('common.Output')} />
<Container>

View File

@@ -21,10 +21,6 @@ const NodeTools = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
<RenderInput moduleId={moduleId} flowInputList={inputs} />
</Container>
<Divider text={t('common.Output')} />
<Container>
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
<Box position={'relative'}>
<Box borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
<Divider showBorderBottom={false} text={t('core.module.template.Tool module')} />

View File

@@ -22,7 +22,7 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const theme = useTheme();
return (
<>
<NodeCard minW={'300px'} selected={selected} {...data}>
<NodeCard minW={'300px'} selected={selected} forbidMenu {...data}>
<Container className="nodrag" borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<WelcomeText data={data} />
<Box pt={4} pb={2}>

View File

@@ -12,7 +12,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { ToolTargetHandle } from './ToolHandle';
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';

View File

@@ -13,6 +13,10 @@ const RenderList: {
types: `${FlowNodeInputTypeEnum}`[];
Component: React.ComponentType<RenderInputProps>;
}[] = [
{
types: [FlowNodeInputTypeEnum.triggerAndFinish],
Component: dynamic(() => import('./templates/TriggerAndFinish'))
},
{
types: [FlowNodeInputTypeEnum.input],
Component: dynamic(() => import('./templates/TextInput'))

View File

@@ -1,14 +1,16 @@
import React, { useEffect, useState } from 'react';
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { getFlowStore, onChangeNode, useFlowProviderStoreType } from '../../../../FlowProvider';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { SelectAppItemType } from '@fastgpt/global/core/module/type';
import Avatar from '@/components/Avatar';
import SelectAppModal from '../../../../SelectAppModal';
import { useTranslation } from 'next-i18next';
const SelectAppRender = ({ item, moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const theme = useTheme();
const [filterAppIds, setFilterAppIds] = useState<useFlowProviderStoreType['filterAppIds']>([]);
const { filterAppIds } = useFlowProviderStore();
const {
isOpen: isOpenSelectApp,
@@ -18,50 +20,65 @@ const SelectAppRender = ({ item, moduleId }: RenderInputProps) => {
const value = item.value as SelectAppItemType | undefined;
useEffect(() => {
async () => {
const { filterAppIds } = await getFlowStore();
setFilterAppIds(filterAppIds);
};
}, []);
const filterAppString = useMemo(() => filterAppIds.join(','), [filterAppIds]);
return (
<>
<Box onClick={onOpenSelectApp}>
{!value ? (
<Button variant={'whitePrimary'} w={'100%'}>
</Button>
) : (
<Flex alignItems={'center'} border={theme.borders.base} borderRadius={'md'} px={3} py={2}>
<Avatar src={value?.logo} />
<Box fontWeight={'bold'} ml={1}>
{value?.name}
</Box>
</Flex>
const Render = useMemo(() => {
return (
<>
<Box onClick={onOpenSelectApp}>
{!value ? (
<Button variant={'whitePrimary'} w={'100%'}>
{t('core.module.Select app')}
</Button>
) : (
<Flex
alignItems={'center'}
border={theme.borders.base}
borderRadius={'md'}
px={3}
py={2}
>
<Avatar src={value?.logo} />
<Box fontWeight={'bold'} ml={1}>
{value?.name}
</Box>
</Flex>
)}
</Box>
{isOpenSelectApp && (
<SelectAppModal
defaultApps={item.value?.id ? [item.value.id] : []}
filterAppIds={filterAppString.split(',')}
onClose={onCloseSelectApp}
onSuccess={(e) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: 'app',
value: {
...item,
value: e[0]
}
});
}}
/>
)}
</Box>
</>
);
}, [
filterAppString,
isOpenSelectApp,
item,
moduleId,
onCloseSelectApp,
onOpenSelectApp,
t,
theme.borders.base,
value
]);
{isOpenSelectApp && (
<SelectAppModal
defaultApps={item.value?.id ? [item.value.id] : []}
filterAppIds={filterAppIds}
onClose={onCloseSelectApp}
onSuccess={(e) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: 'app',
value: {
...item,
value: e[0]
}
});
}}
/>
)}
</>
);
return Render;
};
export default React.memo(SelectAppRender);

View File

@@ -0,0 +1,50 @@
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import TargetHandle from '../../TargetHandle';
import SourceHandle from '../../SourceHandle';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useFlowProviderStore } from '../../../../FlowProvider';
const TriggerAndFinish = ({ moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const { nodes } = useFlowProviderStore();
const outputs = useMemo(
() => nodes.find((node) => node.data.moduleId === moduleId)?.data?.outputs || [],
[moduleId, nodes]
);
const hasFinishOutput = useMemo(
() => outputs.some((output) => output.key === ModuleOutputKeyEnum.finish),
[outputs]
);
const Render = useMemo(
() => (
<Flex
className="nodrag"
cursor={'default'}
alignItems={'center'}
justifyContent={'space-between'}
position={'relative'}
>
<Box position={'relative'}>
<TargetHandle handleKey={ModuleInputKeyEnum.switch} valueType={'any'} />
{t('core.module.input.label.switch')}
</Box>
{hasFinishOutput && (
<Box position={'relative'}>
{t('core.module.output.label.running done')}
<SourceHandle handleKey={ModuleOutputKeyEnum.finish} valueType={'boolean'} />
</Box>
)}
</Flex>
),
[hasFinishOutput, t]
);
return Render;
};
export default React.memo(TriggerAndFinish);

View File

@@ -15,7 +15,7 @@ import {
import { useForm } from 'react-hook-form';
import { defaultEditFormData } from './constants';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { onChangeNode } from '../../../FlowProvider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';

View File

@@ -14,39 +14,40 @@ import { useCallback } from 'react';
type ToolHandleProps = BoxProps & {
moduleId: string;
};
export const ToolTargetHandle = ({ moduleId, ...props }: ToolHandleProps) => {
export const ToolTargetHandle = ({ moduleId }: ToolHandleProps) => {
const { t } = useTranslation();
const valueTypeMap = FlowValueTypeMap[ModuleIOValueTypeEnum.tools];
return (
<Box position={'absolute'} left={'50%'} transform={'translate(-17px,-10px)'} {...props}>
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<Handle
style={{
borderRadius: '0',
backgroundColor: 'transparent'
}}
type="target"
id={ModuleOutputKeyEnum.selectedTools}
position={Position.Top}
>
<Handle
style={{
width: '14px',
height: '14px',
border: '4px solid #5E8FFF',
borderRadius: '0',
backgroundColor: 'transparent',
transformOrigin: 'center',
transform: 'rotate(45deg)'
}}
type="target"
id={ModuleOutputKeyEnum.selectedTools}
position={Position.Top}
<Box
w={'14px'}
h={'14px'}
border={'4px solid #5E8FFF'}
transform={'translate(-40%,-30%) rotate(45deg)'}
/>
</MyTooltip>
</Box>
</Handle>
</MyTooltip>
);
};
export const ToolSourceHandle = ({ moduleId, ...props }: ToolHandleProps) => {
export const ToolSourceHandle = ({ moduleId }: ToolHandleProps) => {
const { t } = useTranslation();
const { setEdges, nodes } = useFlowProviderStore();
@@ -75,29 +76,30 @@ export const ToolSourceHandle = ({ moduleId, ...props }: ToolHandleProps) => {
);
return (
<Box position={'absolute'} left={'50%'} transform={'translate(-16px,-14px)'} {...props}>
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<Handle
style={{
borderRadius: '0',
backgroundColor: 'transparent'
}}
type="source"
id={ModuleOutputKeyEnum.selectedTools}
position={Position.Bottom}
onConnect={onConnect}
>
<Handle
style={{
width: '14px',
height: '14px',
border: '4px solid #5E8FFF',
borderRadius: '0',
backgroundColor: 'transparent',
transformOrigin: 'center',
transform: 'rotate(45deg)'
}}
type="source"
id={ModuleOutputKeyEnum.selectedTools}
position={Position.Bottom}
onConnect={onConnect}
<Box
w={'14px'}
h={'14px'}
border={'4px solid #5E8FFF'}
transform={'translate(-40%,-30%) rotate(45deg)'}
/>
</MyTooltip>
</Box>
</Handle>
</MyTooltip>
);
};