V4.6.5-alpha (#609)
This commit is contained in:
@@ -21,6 +21,7 @@ function Row({
|
||||
value?: string | number;
|
||||
rawDom?: React.ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const val = value || rawDom;
|
||||
const strValue = `${value}`;
|
||||
@@ -29,7 +30,7 @@ function Row({
|
||||
return val !== undefined && val !== '' && val !== 'undefined' ? (
|
||||
<Box mb={3}>
|
||||
<Box fontSize={['sm', 'md']} mb={isCodeBlock ? 0 : 1} flex={'0 0 90px'}>
|
||||
{label}:
|
||||
{t(label)}:
|
||||
</Box>
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
@@ -69,12 +70,12 @@ const WholeResponseModal = ({
|
||||
alt={''}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
{item.moduleName}
|
||||
{t(item.moduleName)}
|
||||
</Flex>
|
||||
),
|
||||
id: `${i}`
|
||||
})),
|
||||
[response]
|
||||
[response, t]
|
||||
);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(`0`);
|
||||
@@ -103,26 +104,33 @@ const WholeResponseModal = ({
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<Row label={t('chat.response.module name')} value={activeModule?.moduleName} />
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.price !== undefined && (
|
||||
<Row
|
||||
label={t('chat.response.module price')}
|
||||
label={t('core.chat.response.module price')}
|
||||
value={`¥${formatPrice(activeModule?.price)}`}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
label={t('chat.response.module time')}
|
||||
label={t('core.chat.response.module time')}
|
||||
value={`${activeModule?.runningTime || 0}s`}
|
||||
/>
|
||||
<Row label={t('chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row label={t('chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('chat.response.module query')} value={activeModule?.query} />
|
||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
value={activeModule?.contextTotalLen}
|
||||
/>
|
||||
|
||||
{/* ai chat */}
|
||||
<Row label={t('chat.response.module temperature')} value={activeModule?.temperature} />
|
||||
<Row label={t('chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('chat.response.module historyPreview')}
|
||||
label={t('core.chat.response.module temperature')}
|
||||
value={activeModule?.temperature}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('core.chat.response.module historyPreview')}
|
||||
rawDom={
|
||||
activeModule.historyPreview ? (
|
||||
<>
|
||||
@@ -148,7 +156,7 @@ const WholeResponseModal = ({
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('chat.response.module quoteList')}
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule.quoteList, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -161,27 +169,27 @@ const WholeResponseModal = ({
|
||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
/>
|
||||
)}
|
||||
<Row label={t('chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
<Row label={t('chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
|
||||
{/* classify question */}
|
||||
<Row
|
||||
label={t('chat.response.module cq')}
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
if (!activeModule?.cqList) return '';
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
<Row label={t('chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
|
||||
{/* extract */}
|
||||
<Row
|
||||
label={t('chat.response.module extract description')}
|
||||
label={t('core.chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
/>
|
||||
{activeModule?.extractResult && (
|
||||
<Row
|
||||
label={t('chat.response.module extract result')}
|
||||
label={t('core.chat.response.module extract result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -189,13 +197,13 @@ const WholeResponseModal = ({
|
||||
{/* http */}
|
||||
{activeModule?.body && (
|
||||
<Row
|
||||
label={t('chat.response.module http body')}
|
||||
label={t('core.chat.response.module http body')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
{activeModule?.httpResult && (
|
||||
<Row
|
||||
label={t('chat.response.module http result')}
|
||||
label={t('core.chat.response.module http result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -203,10 +211,13 @@ const WholeResponseModal = ({
|
||||
{/* plugin */}
|
||||
{activeModule?.pluginOutput && (
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
label={t('core.chat.response.plugin output')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* text editor */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
|
||||
@@ -500,7 +500,7 @@ const ChatBox = (
|
||||
|
||||
return {
|
||||
bg: colorMap[chatContent.status] || colorMap.loading,
|
||||
name: chatContent.moduleName || t('common.Loading')
|
||||
name: t(chatContent.moduleName || '') || t('common.Loading')
|
||||
};
|
||||
}, [chatHistory, isChatting, t]);
|
||||
/* style end */
|
||||
@@ -517,7 +517,7 @@ const ChatBox = (
|
||||
};
|
||||
}, [router.query]);
|
||||
|
||||
// add guide text listener
|
||||
// add listener
|
||||
useEffect(() => {
|
||||
const windowMessage = ({ data }: MessageEvent<{ type: 'sendPrompt'; text: string }>) => {
|
||||
if (data?.type === 'sendPrompt' && data?.text) {
|
||||
@@ -536,9 +536,9 @@ const ChatBox = (
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', windowMessage);
|
||||
eventBus.off(EventNameEnum.sendQuestion);
|
||||
eventBus.off(EventNameEnum.editQuestion);
|
||||
window.removeEventListener('message', windowMessage);
|
||||
};
|
||||
}, [handleSubmit, resetInputVal, sendPrompt]);
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Textarea,
|
||||
TextareaProps,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyModal from '@/components/MyModal';
|
||||
|
||||
type Props = TextareaProps & {
|
||||
title?: string;
|
||||
showSetModalModeIcon?: boolean;
|
||||
// variables: string[];
|
||||
};
|
||||
|
||||
const PromptTextarea = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { title = t('core.app.edit.Prompt Editor'), value, ...childProps } = props;
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Editor {...childProps} value={value} showSetModalModeIcon onSetModalMode={onOpen} />
|
||||
{isOpen && (
|
||||
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
|
||||
<ModalBody>
|
||||
<Editor
|
||||
{...childProps}
|
||||
value={value}
|
||||
minH={'300px'}
|
||||
maxH={'auto'}
|
||||
minW={['100%', '512px']}
|
||||
showSetModalModeIcon={false}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={onClose}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptTextarea;
|
||||
|
||||
const Editor = React.memo(function Editor({
|
||||
showSetModalModeIcon = true,
|
||||
onSetModalMode,
|
||||
...props
|
||||
}: Props & { onSetModalMode?: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box h={'100%'} w={'100%'} position={'relative'}>
|
||||
<Textarea wordBreak={'break-all'} maxW={'100%'} {...props} />
|
||||
{showSetModalModeIcon && (
|
||||
<Box
|
||||
zIndex={1}
|
||||
position={'absolute'}
|
||||
bottom={1}
|
||||
right={2}
|
||||
cursor={'pointer'}
|
||||
onClick={onSetModalMode}
|
||||
>
|
||||
<MyTooltip label={t('common.ui.textarea.Magnifying')}>
|
||||
<MyIcon name={'fullScreenLight'} w={'14px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
const VariableSelectBlock = React.memo(function VariableSelectBlock({
|
||||
variables
|
||||
}: {
|
||||
variables: string[];
|
||||
}) {
|
||||
return <></>;
|
||||
});
|
||||
|
||||
const Placeholder = React.memo(function Placeholder({
|
||||
placeholder = ''
|
||||
}: {
|
||||
placeholder?: string;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
zIndex={0}
|
||||
userSelect={'none'}
|
||||
color={'myGray.400'}
|
||||
px={3}
|
||||
py={2}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{placeholder}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@@ -26,6 +26,7 @@ import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d'
|
||||
import type { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
|
||||
|
||||
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
|
||||
|
||||
@@ -187,14 +188,18 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={
|
||||
t('template.Quote Content Tip', { default: Prompt_QuoteTemplateList[0].value }) ||
|
||||
''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuoteTemplate)}
|
||||
<PromptTextarea
|
||||
bg={'myWhite.400'}
|
||||
rows={8}
|
||||
placeholder={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
showSetModalModeIcon
|
||||
value={getValues(ModuleInputKeyEnum.aiChatQuoteTemplate)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e.target.value);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -209,13 +214,18 @@ const AIChatSettingsModal = ({
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
<PromptTextarea
|
||||
bg={'myWhite.400'}
|
||||
rows={11}
|
||||
placeholder={
|
||||
t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value }) || ''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuotePrompt)}
|
||||
placeholder={t('template.Quote Prompt Tip', {
|
||||
default: Prompt_QuotePromptList[0].value
|
||||
})}
|
||||
showSetModalModeIcon
|
||||
value={getValues(ModuleInputKeyEnum.aiChatQuotePrompt)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e.target.value);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -28,8 +28,8 @@ import React, {
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
@@ -174,7 +174,7 @@ export const FlowProvider = ({
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion) {
|
||||
return ModuleDataTypeEnum.boolean;
|
||||
return ModuleIOValueTypeEnum.string;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
@@ -193,8 +193,8 @@ export const FlowProvider = ({
|
||||
});
|
||||
}
|
||||
if (
|
||||
sourceType !== ModuleDataTypeEnum.any &&
|
||||
targetType !== ModuleDataTypeEnum.any &&
|
||||
sourceType !== ModuleIOValueTypeEnum.any &&
|
||||
targetType !== ModuleIOValueTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
@@ -207,8 +207,7 @@ export const FlowProvider = ({
|
||||
addEdge(
|
||||
{
|
||||
...connect,
|
||||
type: 'buttonedge',
|
||||
animated: true,
|
||||
type: EDGE_TYPE,
|
||||
data: {
|
||||
onDelete: onDelConnect
|
||||
}
|
||||
@@ -228,6 +227,7 @@ export const FlowProvider = ({
|
||||
[setEdges, setNodes]
|
||||
);
|
||||
|
||||
/* change */
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, type, key, value, index }: FlowNodeChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
@@ -434,51 +434,3 @@ export default React.memo(FlowProvider);
|
||||
export const onChangeNode = (e: FlowNodeChangeProps) => {
|
||||
eventBus.emit(EventNameEnum.updaterNode, e);
|
||||
};
|
||||
|
||||
export function flowNode2Modules({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((input) => ({
|
||||
...input,
|
||||
connected: false
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected = !!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,25 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from './FlowProvider';
|
||||
|
||||
const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ImportSettings = ({
|
||||
onClose,
|
||||
setNodes,
|
||||
setEdges,
|
||||
initData
|
||||
}: Props & {
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
setEdges: useFlowProviderStoreType['setEdges'];
|
||||
initData: useFlowProviderStoreType['initData'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [value, setValue] = useState('');
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
@@ -56,4 +68,8 @@ const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ImportSettings);
|
||||
export default React.memo(function (props: Props) {
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return <ImportSettings {...props} setNodes={setNodes} setEdges={setEdges} initData={initData} />;
|
||||
});
|
||||
|
||||
@@ -7,13 +7,12 @@ import type {
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from './FlowProvider';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
@@ -22,47 +21,32 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { moduleTemplatesList } from '@/web/core/modules/template/system';
|
||||
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
system = 'system',
|
||||
plugin = 'plugin'
|
||||
}
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
systemTemplates: FlowModuleTemplateType[];
|
||||
pluginTemplates: FlowModuleTemplateType[];
|
||||
templates: FlowModuleTemplateType[];
|
||||
};
|
||||
|
||||
type ModuleTemplateListProps = ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
type RenderListProps = {
|
||||
templates: FlowModuleTemplateType[];
|
||||
onClose: () => void;
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
reactFlowWrapper: useFlowProviderStoreType['reactFlowWrapper'];
|
||||
};
|
||||
|
||||
const ModuleTemplateList = ({
|
||||
systemTemplates,
|
||||
pluginTemplates,
|
||||
templates,
|
||||
isOpen,
|
||||
onClose
|
||||
}: ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onClose,
|
||||
setNodes,
|
||||
reactFlowWrapper
|
||||
}: ModuleTemplateListProps & {
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
reactFlowWrapper: useFlowProviderStoreType['reactFlowWrapper'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [templateType, setTemplateType] = React.useState(TemplateTypeEnum.system);
|
||||
|
||||
const typeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: TemplateTypeEnum.system,
|
||||
label: t('app.module.System Module'),
|
||||
child: <RenderList templates={systemTemplates} onClose={onClose} />
|
||||
},
|
||||
{
|
||||
type: TemplateTypeEnum.plugin,
|
||||
label: t('plugin.Plugin Module'),
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} isPlugin />
|
||||
}
|
||||
],
|
||||
[pluginTemplates, onClose, systemTemplates, t]
|
||||
);
|
||||
const TemplateItem = useMemo(
|
||||
() => typeList.find((item) => item.type === templateType)?.child,
|
||||
[templateType, typeList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -82,6 +66,7 @@ const ModuleTemplateList = ({
|
||||
position={'absolute'}
|
||||
top={'65px'}
|
||||
left={0}
|
||||
pt={2}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||
w={isOpen ? ['100%', '360px'] : '0'}
|
||||
@@ -92,47 +77,32 @@ const ModuleTemplateList = ({
|
||||
transition={'.2s ease'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<Flex pt={4} pb={1} px={5} gap={4} alignItems={'center'} fontSize={['md', 'xl']}>
|
||||
{typeList.map((item) => (
|
||||
<Box
|
||||
key={item.label}
|
||||
borderBottom={'2px solid transparent'}
|
||||
{...(item.type === templateType
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
borderBottomColor: 'myBlue.700',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
onClick: () => setTemplateType(item.type)
|
||||
})}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
{TemplateItem}
|
||||
<RenderList
|
||||
templates={templates}
|
||||
onClose={onClose}
|
||||
setNodes={setNodes}
|
||||
reactFlowWrapper={reactFlowWrapper}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
export default React.memo(function (props: ModuleTemplateListProps) {
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
|
||||
return <ModuleTemplateList {...props} setNodes={setNodes} reactFlowWrapper={reactFlowWrapper} />;
|
||||
});
|
||||
|
||||
const RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
isPlugin = false,
|
||||
onClose
|
||||
}: {
|
||||
templates: FlowModuleTemplateType[];
|
||||
isPlugin?: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
onClose,
|
||||
setNodes,
|
||||
reactFlowWrapper
|
||||
}: RenderListProps) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
@@ -199,9 +169,9 @@ const RenderList = React.memo(function RenderList({
|
||||
<Box key={item.type}>
|
||||
<Flex>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
{isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
|
||||
{/* {isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
@@ -213,7 +183,7 @@ const RenderList = React.memo(function RenderList({
|
||||
</Box>
|
||||
<MyIcon name={'common/rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
)} */}
|
||||
</Flex>
|
||||
<>
|
||||
{item.list.map((template) => (
|
||||
@@ -248,9 +218,9 @@ const RenderList = React.memo(function RenderList({
|
||||
borderRadius={'0'}
|
||||
/>
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{template.name}</Box>
|
||||
<Box color={'black'}>{t(template.name)}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{template.intro}
|
||||
{t(template.intro)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { useStoreApi, type ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
const CustomConnection = ({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) => {
|
||||
const store = useStoreApi();
|
||||
|
||||
const { connectionHandleId } = store.getState();
|
||||
console.log(fromX, fromY, toX, toY, connectionHandleId);
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle
|
||||
cx={toX}
|
||||
cy={toY}
|
||||
fill="#fff"
|
||||
r={3}
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomConnection;
|
||||
@@ -1,23 +1,33 @@
|
||||
import React from 'react';
|
||||
import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath } from 'reactflow';
|
||||
import {
|
||||
SmoothStepEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getSmoothStepPath,
|
||||
MarkerType
|
||||
} from 'reactflow';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
|
||||
const ButtonEdge = ({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style = {},
|
||||
markerEnd,
|
||||
data
|
||||
}: EdgeProps<{
|
||||
onDelete: (id: string) => void;
|
||||
}>) => {
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
const ButtonEdge = (
|
||||
props: EdgeProps<{
|
||||
onDelete: (id: string) => void;
|
||||
}>
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
data,
|
||||
selected,
|
||||
style = {}
|
||||
} = props;
|
||||
|
||||
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
@@ -26,9 +36,19 @@ const ButtonEdge = ({
|
||||
targetPosition
|
||||
});
|
||||
|
||||
const edgeStyle = {
|
||||
...style,
|
||||
...(selected
|
||||
? {
|
||||
strokeWidth: 4,
|
||||
stroke: '#3370ff'
|
||||
}
|
||||
: { strokeWidth: 2, stroke: '#BDC1C5' })
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
|
||||
<SmoothStepEdge {...props} style={edgeStyle} />
|
||||
<EdgeLabelRenderer>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -48,7 +68,11 @@ const ButtonEdge = ({
|
||||
}}
|
||||
onClick={() => data?.onDelete(id)}
|
||||
>
|
||||
<MyIcon name="closeSolid" w={'100%'} color={'myGray.600'}></MyIcon>
|
||||
<MyIcon
|
||||
name="closeSolid"
|
||||
w={'100%'}
|
||||
color={selected ? 'myBlue.800' : 'myGray.500'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
const ExtractFieldModal = ({
|
||||
defaultField = {
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField?: ContextExtractAgentItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||
}) => {
|
||||
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={'提取字段配置'}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段描述</Box>
|
||||
<Input
|
||||
placeholder="姓名/年龄/sql语句……"
|
||||
{...register('desc', { required: '字段描述不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="name/age/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtractFieldModal);
|
||||
@@ -1,195 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export type EditFieldModeType = 'input' | 'output' | 'pluginInput';
|
||||
export type EditFieldType = {
|
||||
type?: `${FlowNodeInputTypeEnum}`; // input type
|
||||
key: string;
|
||||
label?: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
createSign?: boolean;
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
mode,
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
mode: EditFieldModeType;
|
||||
defaultField: EditFieldType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: EditFieldType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleDataTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleDataTypeEnum.selectDataset
|
||||
}
|
||||
];
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<EditFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const title = ['input', 'pluginInput'].includes(mode)
|
||||
? t('app.Input Field Settings')
|
||||
: t('app.Output Field Settings');
|
||||
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
return getValues('type') === FlowNodeInputTypeEnum.target || mode === 'output';
|
||||
}, [getValues, mode, refresh]);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} iconSrc="/imgs/module/extract.png" title={title} onClose={onClose}>
|
||||
<ModalBody minH={'260px'} overflow={'visible'}>
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{mode === 'pluginInput' && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('type')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('type', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{['input', 'pluginInput'].includes(mode) && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showValueTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleDataTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === ModuleDataTypeEnum.chatHistory ||
|
||||
type === ModuleDataTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea placeholder="可选" rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
|
||||
export const defaultInputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
export const defaultOutputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
@@ -11,7 +11,7 @@ import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
@@ -19,7 +19,7 @@ import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { moduleId, inputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -29,28 +29,57 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ModuleInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = [],
|
||||
...props
|
||||
}: {
|
||||
key: string;
|
||||
value?: ClassifyQuestionAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
{agents.map((item, i) => (
|
||||
<Box key={item.key} mb={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTooltip label={t('common.Delete')}>
|
||||
<MyIcon
|
||||
[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}
|
||||
mr={2}
|
||||
name={'minus'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
defaultValue={item.value}
|
||||
onChange={(e) => {
|
||||
const newVal = agents.map((val) =>
|
||||
val.key === item.key
|
||||
? {
|
||||
...val,
|
||||
value: e.target.value
|
||||
}
|
||||
: val
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
@@ -58,80 +87,50 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: agents.filter((input) => input.key !== item.key)
|
||||
value: newVal
|
||||
}
|
||||
});
|
||||
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={ModuleDataTypeEnum.string} />
|
||||
<SourceHandle
|
||||
handleKey={item.key}
|
||||
valueType={ModuleIOValueTypeEnum.string}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加问题类型
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加问题类型
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
|
||||
export const defaultField = {
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true,
|
||||
enum: ''
|
||||
};
|
||||
|
||||
const ExtractFieldModal = ({
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField: ContextExtractAgentItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={t('core.module.extract.Field Setting Title')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input placeholder="name/age/sql" {...register('key', { required: true })} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Input
|
||||
placeholder={t('core.module.extract.Field Description Placeholder')}
|
||||
{...register('desc', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.module.extract.Enum Value')}({t('common.choosable')})
|
||||
<MyTooltip label={t('core.module.extract.Enum Description')} forceShow>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
|
||||
<Textarea rows={5} placeholder={'apple\npeach\nwatermelon'} {...register('enum')} />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtractFieldModal);
|
||||
@@ -3,25 +3,24 @@ import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@c
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import Container from '../../modules/Container';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import Divider from '../modules/Divider';
|
||||
import RenderInput from '../../render/RenderInput';
|
||||
import Divider from '../../modules/Divider';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import RenderOutput from '../../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
const { onDelEdge } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -42,13 +41,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() =>
|
||||
setEditExtractField({
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
})
|
||||
}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
>
|
||||
新增字段
|
||||
</Button>
|
||||
@@ -150,7 +143,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
@@ -25,54 +24,10 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
<NodeCard minW={'350px'} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
<Button
|
||||
variant={'base'}
|
||||
mt={5}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length - 1}`,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: nanoid(),
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加出参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import { defaultInputField, type EditFieldType } from '../modules/FieldEditModal';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import type {
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type.d';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true
|
||||
};
|
||||
|
||||
const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
@@ -42,10 +68,10 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
type: item.type,
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
@@ -71,14 +97,13 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
@@ -99,95 +124,126 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(defaultInputField);
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
mode={'pluginInput'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
// create field
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
const input = {
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
...e
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
...(data.inputType === FlowNodeInputTypeEnum.addInputParam
|
||||
? {
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
},
|
||||
defaultEditField: {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
: {})
|
||||
};
|
||||
const output = {
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
...e
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,52 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { EditFieldType, defaultOutputField } from '../modules/FieldEditModal';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import {
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: false,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
};
|
||||
|
||||
const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
@@ -36,7 +63,7 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
>
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
@@ -47,7 +74,11 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={2} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
@@ -56,10 +87,12 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -68,7 +101,7 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
@@ -84,12 +117,6 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'left'} mt={5}>
|
||||
@@ -97,92 +124,106 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(defaultOutputField);
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
添加出参
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
mode={'output'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
if (!memInput || !memOutput) return;
|
||||
const input = {
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
...e
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
const output = {
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
...e
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const QuestionInputNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { moduleId, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'240px'} {...data}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
@@ -24,7 +24,7 @@ import VariableEdit from '../modules/VariableEdit';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import NodeCard from '../../modules/NodeCard';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../../modules/Container';
|
||||
import { VariableInputEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { EditInputFieldMap, EditNodeFieldType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const FieldEditModal = ({
|
||||
editField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
dataType: true
|
||||
},
|
||||
defaultField,
|
||||
keys = [],
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
editField?: EditInputFieldMap;
|
||||
defaultField: EditNodeFieldType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const isCreate = useMemo(() => !defaultField.key, [defaultField.key]);
|
||||
const showDynamicInputSelect =
|
||||
!keys.includes(DYNAMIC_INPUT_KEY) || defaultField.key === DYNAMIC_INPUT_KEY;
|
||||
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleIOValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleIOValueTypeEnum.selectDataset
|
||||
},
|
||||
...(showDynamicInputSelect
|
||||
? [
|
||||
{
|
||||
label: t('core.module.inputType.dynamicTargetInput'),
|
||||
value: FlowNodeInputTypeEnum.addInputParam,
|
||||
valueType: ModuleIOValueTypeEnum.any
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const showDataTypeSelect = useMemo(() => {
|
||||
if (!editField.dataType) return false;
|
||||
const inputType = getValues('inputType');
|
||||
const outputType = getValues('outputType');
|
||||
|
||||
if (inputType === FlowNodeInputTypeEnum.target) return true;
|
||||
|
||||
if (outputType === FlowNodeOutputTypeEnum.source) return true;
|
||||
|
||||
return false;
|
||||
}, [editField.dataType, getValues, refresh]);
|
||||
|
||||
const showRequired = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.required;
|
||||
}, [editField.required, getValues, refresh]);
|
||||
|
||||
const showNameInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.name;
|
||||
}, [editField.name, getValues, refresh]);
|
||||
|
||||
const showKeyInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.key;
|
||||
}, [editField.key, getValues, refresh]);
|
||||
|
||||
const showDescriptionInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.description;
|
||||
}, [editField.description, getValues, refresh]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={t('core.module.edit.Field Edit')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody overflow={'visible'}>
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{editField.inputType && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('inputType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('inputType', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
} else if (type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
setValue('label', t('core.module.valueType.dynamicTargetInput'));
|
||||
setValue('key', DYNAMIC_INPUT_KEY);
|
||||
setValue('required', false);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDataTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleIOValueTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === ModuleIOValueTypeEnum.chatHistory ||
|
||||
type === ModuleIOValueTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showNameInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input placeholder="预约字段/sql语句……" {...register('label', { required: true })} />
|
||||
</Flex>
|
||||
)}
|
||||
{showKeyInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input placeholder="appointment/sql" {...register('key', { required: true })} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDescriptionInput && (
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea placeholder={t('common.choosable')} rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit((data) => {
|
||||
if (!data.key) return;
|
||||
if (isCreate && keys.includes(data.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Already Exist')
|
||||
});
|
||||
}
|
||||
onSubmit({
|
||||
data,
|
||||
changeKey: !keys.includes(data.key)
|
||||
});
|
||||
})}
|
||||
>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
@@ -8,7 +8,11 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import {
|
||||
useFlowProviderStore,
|
||||
onChangeNode,
|
||||
type useFlowProviderStoreType
|
||||
} from '../../FlowProvider';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -23,20 +27,28 @@ type Props = FlowModuleItemType & {
|
||||
isPreview?: boolean;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const NodeCard = (
|
||||
props: Props & {
|
||||
onCopyNode: useFlowProviderStoreType['onCopyNode'];
|
||||
onResetNode: useFlowProviderStoreType['onResetNode'];
|
||||
onDelNode: useFlowProviderStoreType['onDelNode'];
|
||||
}
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
children,
|
||||
avatar = LOGO_ICON,
|
||||
name = '未知模块',
|
||||
name = t('core.module.template.UnKnow Module'),
|
||||
intro,
|
||||
minW = '300px',
|
||||
moduleId,
|
||||
flowType,
|
||||
inputs,
|
||||
isPreview
|
||||
isPreview,
|
||||
onCopyNode,
|
||||
onResetNode,
|
||||
onDelNode
|
||||
} = props;
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
@@ -147,10 +159,10 @@ const NodeCard = (props: Props) => {
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={avatar} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
{name}
|
||||
{t(name)}
|
||||
</Box>
|
||||
{intro && (
|
||||
<MyTooltip label={intro} forceShow>
|
||||
<MyTooltip label={t(intro)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mb={'1px'} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
@@ -186,4 +198,10 @@ const NodeCard = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
export default React.memo(function (props: Props) {
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard {...props} onCopyNode={onCopyNode} onResetNode={onResetNode} onDelNode={onDelNode} />
|
||||
);
|
||||
});
|
||||
@@ -1,729 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
Box,
|
||||
Textarea,
|
||||
Input,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
Button,
|
||||
useTheme,
|
||||
Grid,
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MySelect from '@/components/Select';
|
||||
import MySlider from '@/components/Slider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import TargetHandle from './TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { chatModelList, cqModelList } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('../../../DatasetParamsModal'));
|
||||
|
||||
export const Label = React.memo(function Label({
|
||||
moduleId,
|
||||
inputKey,
|
||||
editFiledType = 'input',
|
||||
...item
|
||||
}: FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
key: inputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
// same key
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
// diff key. del and add
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {},
|
||||
editFiledType
|
||||
}: {
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() =>
|
||||
flowInputList
|
||||
.filter((item) => !item.plusField || feConfigs.isPlus)
|
||||
.sort((a, b) => (a.key === FlowNodeInputTypeEnum.switch ? -1 : 1)),
|
||||
[flowInputList]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{sortInputs.map(
|
||||
(item) =>
|
||||
item.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!item.label && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
inputKey={item.key}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{item.type === FlowNodeInputTypeEnum.numberInput && (
|
||||
<NumberInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.input && (
|
||||
<TextInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.switch && (
|
||||
<SwitchRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.textarea && (
|
||||
<TextareaRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.select && (
|
||||
<SelectRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.slider && (
|
||||
<SliderRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectApp && (
|
||||
<SelectAppRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.aiSettings && (
|
||||
<AISetting inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{[
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel
|
||||
].includes(item.type as any) && (
|
||||
<SelectAIModelRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectDataset && (
|
||||
<SelectDatasetRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectDatasetParamsModal && (
|
||||
<SelectDatasetParamsRender item={item} inputs={sortInputs} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
|
||||
<>{CustomComponent[item.key]({ ...item })}</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderInput);
|
||||
|
||||
type RenderProps = {
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
const NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
});
|
||||
|
||||
const TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Textarea
|
||||
rows={5}
|
||||
placeholder={item.placeholder}
|
||||
resize={'both'}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
markList={item.markList}
|
||||
width={'100%'}
|
||||
min={item.min || 0}
|
||||
max={item.max}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
const AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
|
||||
const { t } = useTranslation();
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatModuleProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{t('app.AI Settings')}
|
||||
</Button>
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
isAdEdit
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={chatModulesData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectAIModelRender = React.memo(function SelectAIModelRender({
|
||||
inputs = [],
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const modelList = (() => {
|
||||
if (item.type === FlowNodeInputTypeEnum.selectChatModel) return chatModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectCQModel) return cqModelList;
|
||||
return [];
|
||||
})().map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
// update max tokens
|
||||
const model = modelList.find((item) => item.model === e) || modelList[0];
|
||||
if (!model) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.maxResponse}`, value: model.maxResponse }
|
||||
],
|
||||
max: model.maxResponse,
|
||||
value: model.maxResponse / 2
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, item, modelList, moduleId]
|
||||
);
|
||||
|
||||
const list = modelList.map((item) => {
|
||||
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
|
||||
|
||||
return {
|
||||
value: item.model,
|
||||
label: `${item.name}${priceStr}`
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
}, [item.value, list, onChangeModel]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const theme = useTheme();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
border={theme.borders.base}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg', 'xl']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
{isOpenKbSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectApp,
|
||||
onOpen: onOpenSelectApp,
|
||||
onClose: onCloseSelectApp
|
||||
} = useDisclosure();
|
||||
|
||||
const value = item.value as SelectAppItemType | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box onClick={onOpenSelectApp}>
|
||||
{!value ? (
|
||||
<Button variant={'base'} 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>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{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]
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectDatasetParamsRender = React.memo(function SelectDatasetParamsRender({
|
||||
item,
|
||||
inputs = [],
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5
|
||||
});
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
chatModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [nodes]);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import { EditNodeFieldType, FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
onChangeNode,
|
||||
useFlowProviderStore,
|
||||
useFlowProviderStoreType
|
||||
} from '../../../FlowProvider';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import TargetHandle from '../TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
type Props = FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
};
|
||||
|
||||
const InputLabel = ({
|
||||
moduleId,
|
||||
inputKey,
|
||||
mode,
|
||||
...item
|
||||
}: Props & {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={t(description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: type,
|
||||
valueType: valueType,
|
||||
key: inputKey,
|
||||
required,
|
||||
label,
|
||||
description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
keys={[editField.key]}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...item,
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: Props) {
|
||||
const { mode } = useFlowProviderStore();
|
||||
|
||||
return <InputLabel {...props} mode={mode} />;
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import InputLabel from './Label';
|
||||
import type { RenderInputProps } from './type.d';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from '../../../FlowProvider';
|
||||
|
||||
const RenderList: {
|
||||
types: `${FlowNodeInputTypeEnum}`[];
|
||||
Component: React.ComponentType<RenderInputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.input],
|
||||
Component: dynamic(() => import('./templates/TextInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.numberInput],
|
||||
Component: dynamic(() => import('./templates/NumberInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.switch],
|
||||
Component: dynamic(() => import('./templates/Switch'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.textarea],
|
||||
Component: dynamic(() => import('./templates/Textarea'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.select],
|
||||
Component: dynamic(() => import('./templates/Select'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.slider],
|
||||
Component: dynamic(() => import('./templates/Slider'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectApp],
|
||||
Component: dynamic(() => import('./templates/SelectApp'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.aiSettings],
|
||||
Component: dynamic(() => import('./templates/AiSetting'))
|
||||
},
|
||||
{
|
||||
types: [
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel,
|
||||
FlowNodeInputTypeEnum.selectExtractModel
|
||||
],
|
||||
Component: dynamic(() => import('./templates/SelectAiModel'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDataset],
|
||||
Component: dynamic(() => import('./templates/SelectDataset'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDatasetParamsModal],
|
||||
Component: dynamic(() => import('./templates/SelectDatasetParams'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.addInputParam],
|
||||
Component: dynamic(() => import('./templates/AddInputParam'))
|
||||
}
|
||||
];
|
||||
|
||||
type Props = {
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
};
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {},
|
||||
mode
|
||||
}: Props & {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() =>
|
||||
flowInputList.sort((a, b) => {
|
||||
if (a.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.type === FlowNodeInputTypeEnum.switch) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}),
|
||||
[flowInputList]
|
||||
);
|
||||
const filterInputs = useMemo(
|
||||
() =>
|
||||
sortInputs.filter((input) => {
|
||||
if (mode === 'app' && input.hideInApp) return false;
|
||||
if (mode === 'plugin' && input.hideInPlugin) return false;
|
||||
|
||||
return true;
|
||||
}),
|
||||
[mode, sortInputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterInputs.map((input) => {
|
||||
const RenderComponent = (() => {
|
||||
if (input.type === FlowNodeInputTypeEnum.custom && CustomComponent[input.key]) {
|
||||
return <>{CustomComponent[input.key]({ ...input })}</>;
|
||||
}
|
||||
const Component = RenderList.find((item) => item.types.includes(input.type))?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component inputs={filterInputs} item={input} moduleId={moduleId} />;
|
||||
})();
|
||||
|
||||
return (
|
||||
input.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!input.label && <InputLabel moduleId={moduleId} inputKey={input.key} {...input} />}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: Props) {
|
||||
const { mode } = useFlowProviderStore();
|
||||
return <RenderInput {...props} mode={mode} />;
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddInputParam = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: data.key,
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: item.editField
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddInputParam);
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { AIChatModuleProps } from '@fastgpt/global/core/module/node/type';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import AIChatSettingsModal from '@/components/core/module/AIChatSettingsModal';
|
||||
|
||||
const AiSettingRender = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatModuleProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{t('app.AI Settings')}
|
||||
</Button>
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
isAdEdit
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={chatModulesData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AiSettingRender);
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import {
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper
|
||||
} from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const NumberInputRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NumberInputRender);
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import MySelect from '@/components/Select';
|
||||
|
||||
const SelectRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectRender);
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { chatModelList, cqModelList, extractModelList } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
|
||||
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
|
||||
const modelList = (() => {
|
||||
if (item.type === FlowNodeInputTypeEnum.selectChatModel) return chatModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectCQModel) return cqModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectExtractModel) return extractModelList;
|
||||
|
||||
return [];
|
||||
})().map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
// update max tokens
|
||||
const model = modelList.find((item) => item.model === e) || modelList[0];
|
||||
if (!model) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.maxResponse}`, value: model.maxResponse }
|
||||
],
|
||||
max: model.maxResponse,
|
||||
value: model.maxResponse / 2
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, item, modelList, moduleId]
|
||||
);
|
||||
|
||||
const list = modelList.map((item) => {
|
||||
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
|
||||
|
||||
return {
|
||||
value: item.model,
|
||||
label: `${item.name}${priceStr}`
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
}, [item.value, list, onChangeModel]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectAiModelRender);
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import {
|
||||
onChangeNode,
|
||||
useFlowProviderStore,
|
||||
type useFlowProviderStoreType
|
||||
} 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';
|
||||
|
||||
const SelectAppRender = ({
|
||||
item,
|
||||
moduleId,
|
||||
filterAppIds
|
||||
}: RenderInputProps & {
|
||||
filterAppIds: useFlowProviderStoreType['filterAppIds'];
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectApp,
|
||||
onOpen: onOpenSelectApp,
|
||||
onClose: onCloseSelectApp
|
||||
} = useDisclosure();
|
||||
|
||||
const value = item.value as SelectAppItemType | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box onClick={onOpenSelectApp}>
|
||||
{!value ? (
|
||||
<Button variant={'base'} 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>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{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]
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: RenderInputProps) {
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
return <SelectAppRender {...props} filterAppIds={filterAppIds} />;
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@fastgpt/global/core/module/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import DatasetSelectModal from '@/components/core/module/DatasetSelectModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const theme = useTheme();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
border={theme.borders.base}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg', 'xl']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
{isOpenKbSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectDatasetRender);
|
||||
@@ -0,0 +1,90 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
|
||||
import { Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { chatModelList } from '@/web/common/system/staticData';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import DatasetParamsModal from '@/components/core/module/DatasetParamsModal';
|
||||
|
||||
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5
|
||||
});
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
chatModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [nodes]);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectDatasetParam);
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import MySlider from '@/components/Slider';
|
||||
|
||||
const SliderRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
markList={item.markList}
|
||||
width={'100%'}
|
||||
min={item.min || 0}
|
||||
max={item.max}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SliderRender);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const SwitchRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SwitchRender);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const TextInput = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextInput);
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
|
||||
|
||||
const TextareaRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<PromptTextarea
|
||||
title={t(item.label)}
|
||||
rows={5}
|
||||
bg={'myWhite.400'}
|
||||
placeholder={t(item.placeholder || '')}
|
||||
resize={'both'}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextareaRender);
|
||||
7
projects/app/src/components/core/module/Flow/components/render/RenderInput/type.d.ts
vendored
Normal file
7
projects/app/src/components/core/module/Flow/components/render/RenderInput/type.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
export type RenderInputProps = {
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
@@ -1,34 +1,30 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { EditNodeFieldType, FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import SourceHandle from '../SourceHandle';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import type { EditFieldType, EditFieldModeType } from '../modules/FieldEditModal';
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
export const Label = ({
|
||||
const OutputLabel = ({
|
||||
moduleId,
|
||||
outputKey,
|
||||
outputs,
|
||||
editFiledType = 'output',
|
||||
...item
|
||||
}: FlowNodeOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { label = '', description, edit } = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -48,10 +44,11 @@ export const Label = ({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
key: outputKey,
|
||||
description: item.description
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
valueType: item.valueType,
|
||||
outputType: item.type
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -79,29 +76,41 @@ export const Label = ({
|
||||
)}
|
||||
<Box>{t(label)}</Box>
|
||||
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={outputKey} valueType={item.valueType} />
|
||||
)}
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={[outputKey]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.outputType || !data.key) return;
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...item,
|
||||
...e
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,48 +122,4 @@ export const Label = ({
|
||||
);
|
||||
};
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList,
|
||||
editFiledType
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortOutput = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortOutput.map(
|
||||
(item) =>
|
||||
item.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
outputKey={item.key}
|
||||
outputs={sortOutput}
|
||||
{...item}
|
||||
/>
|
||||
<Box mt={FlowNodeOutputTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderOutput);
|
||||
export default React.memo(OutputLabel);
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import OutputLabel from './Label';
|
||||
import { RenderOutputProps } from './type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const RenderList: {
|
||||
types: `${FlowNodeOutputTypeEnum}`[];
|
||||
Component: React.ComponentType<RenderOutputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeOutputTypeEnum.addOutputParam],
|
||||
Component: dynamic(() => import('./templates/AddOutputParam'))
|
||||
}
|
||||
];
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
}) => {
|
||||
const sortOutputs = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortOutputs.map((output) => {
|
||||
const RenderComponent = (() => {
|
||||
const Component = RenderList.find(
|
||||
(item) => output.type && item.types.includes(output.type)
|
||||
)?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component outputs={sortOutputs} item={output} moduleId={moduleId} />;
|
||||
})();
|
||||
|
||||
return (
|
||||
output.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={output.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{output.label && (
|
||||
<OutputLabel
|
||||
moduleId={moduleId}
|
||||
outputKey={output.key}
|
||||
outputs={sortOutputs}
|
||||
{...output}
|
||||
/>
|
||||
)}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderOutput);
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderOutputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddOutputParam = ({ outputs = [], item, moduleId }: RenderOutputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Box textAlign={'right'}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={outputs.map((output) => output.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
key: data.key,
|
||||
value: {
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
edit: true,
|
||||
editField: item.editField,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddOutputParam);
|
||||
7
projects/app/src/components/core/module/Flow/components/render/RenderOutput/type.d.ts
vendored
Normal file
7
projects/app/src/components/core/module/Flow/components/render/RenderOutput/type.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
export type RenderOutputProps = {
|
||||
outputs?: FlowNodeOutputItemType[];
|
||||
item: FlowNodeOutputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
@@ -1,26 +1,26 @@
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
const valType = valueType ?? ModuleIOValueTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
valueType && FlowValueTypeMap[valueType]
|
||||
? FlowValueTypeMap[valueType]?.handlerStyle
|
||||
: FlowValueTypeMap[ModuleIOValueTypeEnum.any]?.handlerStyle,
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -34,8 +34,8 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
type: t(FlowValueTypeMap[valType]?.label),
|
||||
description: FlowValueTypeMap[valType]?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, OnConnect, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
|
||||
const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
const valType = valueType ?? ModuleIOValueTypeEnum.any;
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
valueType && FlowValueTypeMap[valueType]
|
||||
? FlowValueTypeMap[valueType]?.handlerStyle
|
||||
: FlowValueTypeMap[ModuleIOValueTypeEnum.any]?.handlerStyle,
|
||||
[valueType]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={handleKey}
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
left={'-16px'}
|
||||
@@ -35,8 +34,8 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
type: t(FlowValueTypeMap[valType]?.label),
|
||||
description: FlowValueTypeMap[valType]?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { edgeOptions, connectionLineStyle } from '@/web/core/modules/constants/flowUi';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
@@ -25,14 +24,14 @@ const nodeTypes = {
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
buttonedge: ButtonEdge
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
};
|
||||
type Props = {
|
||||
modules: ModuleItemType[];
|
||||
@@ -40,7 +39,7 @@ type Props = {
|
||||
} & ModuleTemplateProps;
|
||||
|
||||
const Container = React.memo(function Container(props: Props) {
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates } = props;
|
||||
const { modules = [], Header, templates } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
@@ -96,8 +95,10 @@ const Container = React.memo(function Container(props: Props) {
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={edgeOptions}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
defaultEdgeOptions={{
|
||||
animated: true
|
||||
}}
|
||||
connectionLineStyle={{ strokeWidth: 2, stroke: '#5A646Es' }}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
@@ -115,8 +116,7 @@ const Container = React.memo(function Container(props: Props) {
|
||||
</ReactFlow>
|
||||
|
||||
<ModuleTemplateList
|
||||
systemTemplates={systemTemplates}
|
||||
pluginTemplates={pluginTemplates}
|
||||
templates={templates}
|
||||
isOpen={isOpenTemplate}
|
||||
onClose={onCloseTemplate}
|
||||
/>
|
||||
|
||||
51
projects/app/src/components/core/module/utils.ts
Normal file
51
projects/app/src/components/core/module/utils.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { type Node, type Edge } from 'reactflow';
|
||||
|
||||
export function flowNode2Modules({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((input) => ({
|
||||
...input,
|
||||
connected: false
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected = !!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录>
|
||||
|
||||
<字段说明>
|
||||
1. 下面的 JSON 字符串均按照 JSON Schema 的规则描述。
|
||||
2. key 代表字段名,description 代表字段的描述,required 代表字段是否必须。
|
||||
2. key 代表字段名;description 代表字段的描述;required 代表字段是否必须;enum 是可选值,代表可选的 value。
|
||||
3. 如果字段内容为空,你可以返回空字符串。
|
||||
|
||||
{{json}}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
let success = 0;
|
||||
@@ -52,7 +52,7 @@ export async function initApp(limit = 50): Promise<any> {
|
||||
key: ModuleInputKeyEnum.datasetSearchMode,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: val
|
||||
|
||||
@@ -7,7 +7,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
@@ -317,7 +317,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
key: ModuleInputKeyEnum.answerText,
|
||||
value: formData.dataset.searchEmptyText,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: '回复的内容',
|
||||
connected: true
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller';
|
||||
import { Readable } from 'stream';
|
||||
import type { Cursor } from '@fastgpt/service/common/mongo';
|
||||
import { limitCheck } from './checkExportLimit';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
let { datasetId } = req.query as {
|
||||
@@ -81,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
})),
|
||||
...(global.communityPlugins?.map((plugin) => ({
|
||||
id: plugin.id,
|
||||
templateType: ModuleTemplateTypeEnum.communityPlugin,
|
||||
templateType: plugin.templateType ?? ModuleTemplateTypeEnum.other,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
|
||||
69
projects/app/src/pages/api/plugins/TFSwitch/index.ts
Normal file
69
projects/app/src/pages/api/plugins/TFSwitch/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
input: string;
|
||||
rule?: string;
|
||||
}>;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const {
|
||||
data: { input, rule = '' }
|
||||
} = req.body as Props;
|
||||
|
||||
const result = (() => {
|
||||
if (typeof input === 'string') {
|
||||
const defaultReg: any[] = [
|
||||
undefined,
|
||||
'undefined',
|
||||
null,
|
||||
'null',
|
||||
false,
|
||||
'false',
|
||||
0,
|
||||
'0',
|
||||
'none'
|
||||
];
|
||||
const customReg = rule.split('\n');
|
||||
defaultReg.push(...customReg);
|
||||
|
||||
return !defaultReg.find((item) => {
|
||||
const reg = typeof item === 'string' ? stringToRegex(item) : null;
|
||||
if (reg) {
|
||||
return reg.test(input);
|
||||
}
|
||||
return input === item;
|
||||
});
|
||||
}
|
||||
|
||||
return !!input;
|
||||
})();
|
||||
|
||||
res.json({
|
||||
...(result
|
||||
? {
|
||||
true: true
|
||||
}
|
||||
: {
|
||||
false: false
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).send(getErrText(err));
|
||||
}
|
||||
}
|
||||
|
||||
function stringToRegex(str: string) {
|
||||
const regexFormat = /^\/(.+)\/([gimuy]*)$/;
|
||||
const match = str.match(regexFormat);
|
||||
|
||||
if (match) {
|
||||
const [, pattern, flags] = match;
|
||||
return new RegExp(pattern, flags);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
26
projects/app/src/pages/api/plugins/textEditor/index.ts
Normal file
26
projects/app/src/pages/api/plugins/textEditor/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const {
|
||||
data: { text, ...obj }
|
||||
} = req.body as Props;
|
||||
|
||||
const textResult = replaceVariable(text, obj);
|
||||
|
||||
res.json({
|
||||
text: textResult
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).send(getErrText(err));
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,10 @@ import {
|
||||
} from '@fastgpt/global/core/ai/model';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
|
||||
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { getFastGPTFeConfig } from '@fastgpt/service/common/system/config/controller';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await getInitConfig();
|
||||
@@ -251,12 +252,12 @@ function getSystemPlugin() {
|
||||
const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
|
||||
// read json file
|
||||
const fileTemplates = filterFiles.map((item) => {
|
||||
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
|
||||
const fileTemplates: PluginTemplateType[] = filterFiles.map((filename) => {
|
||||
const content = readFileSync(`${basePath}/${filename}`, 'utf-8');
|
||||
return {
|
||||
id: `${PluginTypeEnum.community}-${item.replace('.json', '')}`,
|
||||
type: PluginTypeEnum.community,
|
||||
...JSON.parse(content)
|
||||
...JSON.parse(content),
|
||||
id: `${PluginSourceEnum.community}-${filename.replace('.json', '')}`,
|
||||
source: PluginSourceEnum.community
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -138,6 +138,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
|
||||
// openapi key
|
||||
if (authType === AuthUserTypeEnum.apikey) {
|
||||
if (!apiKeyAppId) {
|
||||
return Promise.reject(
|
||||
'Key is error. You need to use the app key rather than the account key.'
|
||||
);
|
||||
}
|
||||
const app = await MongoApp.findById(apiKeyAppId);
|
||||
|
||||
if (!app) {
|
||||
|
||||
@@ -27,7 +27,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
const { total } = pushReRankBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
source: 'api'
|
||||
source: 'api',
|
||||
inputs
|
||||
});
|
||||
|
||||
if (apikey) {
|
||||
|
||||
@@ -12,9 +12,11 @@ import dynamic from 'next/dynamic';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
|
||||
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
import { flowNode2Modules } from '@/components/core/module/utils';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
|
||||
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
|
||||
|
||||
@@ -35,42 +37,47 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const { openConfirm: openConfirmOut, ConfirmModal } = useConfirm({
|
||||
content: t('core.app.edit.Out Ad Edit')
|
||||
});
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
const { updateAppDetail } = useAppStore();
|
||||
|
||||
const { nodes, edges, onFixView } = useFlowProviderStore();
|
||||
const { nodes, edges } = useFlowProviderStore();
|
||||
|
||||
const flow2ModulesAndCheck = useCallback(
|
||||
(tip = false) => {
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
if (
|
||||
item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) return false;
|
||||
if (!input.value || input.value === '' || input.value?.length === 0) return true;
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
const msg = `【${item.name}】存在未填或未连接参数`;
|
||||
tip &&
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: msg
|
||||
});
|
||||
return Promise.reject(msg);
|
||||
const flow2ModulesAndCheck = useCallback(() => {
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
|
||||
const unconnected = item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) {
|
||||
return false;
|
||||
}
|
||||
if (input.value === undefined || input.value === '' || input.value?.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (unconnected) {
|
||||
const msg = `【${item.name}】存在未填或未连接参数`;
|
||||
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: msg
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return modules;
|
||||
},
|
||||
[edges, nodes, toast]
|
||||
);
|
||||
}
|
||||
return modules;
|
||||
}, [edges, nodes, toast]);
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
mutationFn: async () => {
|
||||
mutationFn: async (modules: ModuleItemType[]) => {
|
||||
return updateAppDetail(app._id, {
|
||||
modules: await flow2ModulesAndCheck(),
|
||||
modules: modules,
|
||||
type: AppTypeEnum.advanced,
|
||||
permission: undefined
|
||||
});
|
||||
@@ -91,7 +98,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<MyTooltip label={'返回'} offset={[10, 10]}>
|
||||
<MyTooltip label={t('common.Back')} offset={[10, 10]}>
|
||||
<IconButton
|
||||
size={'sm'}
|
||||
icon={<MyIcon name={'back'} w={'14px'} />}
|
||||
@@ -99,10 +106,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
borderColor={'myGray.300'}
|
||||
variant={'base'}
|
||||
aria-label={''}
|
||||
onClick={() => {
|
||||
onClick={openConfirmOut(async () => {
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
await onclickSave(modules);
|
||||
}
|
||||
onClose();
|
||||
onFixView();
|
||||
}}
|
||||
}, onClose)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
|
||||
@@ -153,8 +163,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
borderRadius={'lg'}
|
||||
aria-label={'save'}
|
||||
variant={'base'}
|
||||
onClick={async () => {
|
||||
setTestModules(await flow2ModulesAndCheck(true));
|
||||
onClick={() => {
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
setTestModules(modules);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
@@ -166,11 +179,20 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
borderRadius={'lg'}
|
||||
isLoading={isLoading}
|
||||
aria-label={'save'}
|
||||
onClick={onclickSave}
|
||||
onClick={() => {
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
onclickSave(modules);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
|
||||
<ConfirmModal
|
||||
closeText={t('core.app.edit.UnSave')}
|
||||
confirmText={t('core.app.edit.Save and out')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,16 +15,16 @@ const Render = ({ app, onClose }: Props) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
|
||||
|
||||
const filterTemplates = useMemo(() => {
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
|
||||
JSON.stringify(appSystemModuleTemplates)
|
||||
);
|
||||
const moduleTemplates = useMemo(() => {
|
||||
const concatTemplates = [...appSystemModuleTemplates, ...pluginModuleTemplates];
|
||||
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
|
||||
|
||||
const filterType: Record<string, 1> = {
|
||||
[FlowNodeTypeEnum.userGuide]: 1
|
||||
};
|
||||
|
||||
// filter some template
|
||||
// filter some template, There can only be one
|
||||
nodes.forEach((node) => {
|
||||
if (node.type && filterType[node.type]) {
|
||||
copyTemplates.forEach((module, index) => {
|
||||
@@ -36,14 +36,13 @@ const Render = ({ app, onClose }: Props) => {
|
||||
});
|
||||
|
||||
return copyTemplates;
|
||||
}, [nodes]);
|
||||
}, [nodes, pluginModuleTemplates]);
|
||||
|
||||
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
|
||||
|
||||
return (
|
||||
<Flow
|
||||
systemTemplates={filterTemplates}
|
||||
pluginTemplates={pluginModuleTemplates}
|
||||
templates={moduleTemplates}
|
||||
modules={app.modules}
|
||||
Header={<Header app={app} onClose={onClose} />}
|
||||
/>
|
||||
|
||||
@@ -48,15 +48,18 @@ import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import PromptTextarea from '@/components/common/Textarea/PromptTextarea/index';
|
||||
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
|
||||
const TTSSelect = dynamic(
|
||||
() => import('@/components/core/module/Flow/components/modules/TTSSelect')
|
||||
);
|
||||
const QGSwitch = dynamic(() => import('@/components/core/module/Flow/components/modules/QGSwitch'));
|
||||
|
||||
function ConfigForm({
|
||||
divRef,
|
||||
@@ -100,8 +103,7 @@ function ConfigForm({
|
||||
} = useDisclosure();
|
||||
|
||||
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
|
||||
content: t('app.Confirm Save App Tip'),
|
||||
bg: appDetail.type === AppTypeEnum.simple ? '' : 'red.600'
|
||||
content: t('core.app.edit.Confirm Save App Tip')
|
||||
});
|
||||
|
||||
const chatModelSelectList = useMemo(() => {
|
||||
@@ -330,13 +332,18 @@ function ConfigForm({
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
<PromptTextarea
|
||||
flex={1}
|
||||
bg={'myWhite.400'}
|
||||
rows={5}
|
||||
minH={'60px'}
|
||||
placeholder={chatNodeSystemPromptTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('aiSettings.systemPrompt')}
|
||||
></Textarea>
|
||||
showSetModalModeIcon
|
||||
value={getValues('aiSettings.systemPrompt')}
|
||||
onChange={(e) => {
|
||||
setValue('aiSettings.systemPrompt', e.target.value || '');
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
@@ -438,7 +445,7 @@ function ConfigForm({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<ConfirmSaveModal />
|
||||
<ConfirmSaveModal bg={appDetail.type === AppTypeEnum.simple ? '' : 'red.600'} countDown={5} />
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
onClose={onCloseAIChatSetting}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { POST } from '@/web/common/api/request';
|
||||
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
|
||||
const OutLink = ({
|
||||
shareId,
|
||||
@@ -44,10 +44,10 @@ const OutLink = ({
|
||||
const { toast } = useToast();
|
||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||
const { isPc } = useSystemStore();
|
||||
const forbidRefresh = useRef(false);
|
||||
const [isEmbed, setIdEmbed] = useState(true);
|
||||
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const forbidRefresh = useRef(false);
|
||||
const initSign = useRef(false);
|
||||
const [isEmbed, setIdEmbed] = useState(true);
|
||||
|
||||
const {
|
||||
localUId,
|
||||
@@ -161,7 +161,7 @@ const OutLink = ({
|
||||
const res = await getInitOutLinkChatInfo({
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid: authToken || localUId
|
||||
outLinkUid
|
||||
});
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
@@ -176,12 +176,21 @@ const OutLink = ({
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
|
||||
if (res.history.length > 0) {
|
||||
// send init message
|
||||
if (!initSign.current) {
|
||||
initSign.current = true;
|
||||
if (window !== top) {
|
||||
window.top?.postMessage({ type: 'shareChatReady' }, '*');
|
||||
}
|
||||
}
|
||||
|
||||
if (chatId && res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('core.shareChat.Init Error'))
|
||||
@@ -194,16 +203,14 @@ const OutLink = ({
|
||||
}
|
||||
});
|
||||
}
|
||||
if (e?.statusText === OutLinkErrEnum.linkUnInvalid) {
|
||||
router.replace('/');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[authToken, localUId, router, setChatData, t, toast]
|
||||
[outLinkUid, router, setChatData, t, toast]
|
||||
);
|
||||
useQuery(['init', shareId, chatId], () => {
|
||||
|
||||
const { isFetching } = useQuery(['init', shareId, chatId], () => {
|
||||
if (forbidRefresh.current) {
|
||||
forbidRefresh.current = false;
|
||||
return null;
|
||||
@@ -223,11 +230,8 @@ const OutLink = ({
|
||||
return null;
|
||||
});
|
||||
|
||||
// check is embed
|
||||
// window init
|
||||
useEffect(() => {
|
||||
if (window !== top) {
|
||||
window.top?.postMessage({ type: 'shareChatReady' }, '*');
|
||||
}
|
||||
setIdEmbed(window !== top);
|
||||
}, []);
|
||||
|
||||
@@ -258,7 +262,7 @@ const OutLink = ({
|
||||
<Head>
|
||||
<title>{chatData.app.name}</title>
|
||||
</Head>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']}>
|
||||
<MyBox isLoading={isFetching} h={'100%'} display={'flex'} flexDirection={['column', 'row']}>
|
||||
{showHistory === '1'
|
||||
? ((children: React.ReactNode) => {
|
||||
return isPc ? (
|
||||
@@ -372,7 +376,7 @@ const OutLink = ({
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
@@ -7,10 +7,12 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
import { flowNode2Modules } from '@/components/core/module/utils';
|
||||
import { putUpdatePlugin } from '@/web/core/plugin/api';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
|
||||
const PreviewPlugin = dynamic(() => import('./Preview'));
|
||||
@@ -20,58 +22,83 @@ type Props = { plugin: PluginItemSchema; onClose: () => void };
|
||||
const Header = ({ plugin, onClose }: Props) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { copyData } = useCopyData();
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
const { nodes, edges, onFixView } = useFlowProviderStore();
|
||||
const [previewModules, setPreviewModules] = React.useState<ModuleItemType[]>();
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
mutationFn: () => {
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
const flow2ModulesAndCheck = useCallback(() => {
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
|
||||
// update custom input connected
|
||||
if (item.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
item.inputs.forEach((item) => {
|
||||
item.connected = true;
|
||||
// update custom input connected
|
||||
if (item.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
item.inputs.forEach((item) => {
|
||||
item.connected = true;
|
||||
});
|
||||
if (item.outputs.find((output) => output.targets.length === 0)) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('module.Plugin input must connect')
|
||||
});
|
||||
if (item.outputs.find((output) => output.targets.length === 0)) {
|
||||
return Promise.reject(t('module.Plugin input must connect'));
|
||||
}
|
||||
}
|
||||
if (
|
||||
item.flowType === FlowNodeTypeEnum.pluginOutput &&
|
||||
item.inputs.find((input) => !input.connected)
|
||||
) {
|
||||
return Promise.reject(t('core.module.Plugin output must connect'));
|
||||
}
|
||||
|
||||
if (
|
||||
item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) return false;
|
||||
if (!input.value || input.value === '' || input.value?.length === 0) return true;
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return Promise.reject(`【${item.name}】存在未填或未连接参数`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// plugin must have input
|
||||
const pluginInputModule = modules.find(
|
||||
(item) => item.flowType === FlowNodeTypeEnum.pluginInput
|
||||
);
|
||||
|
||||
if (!pluginInputModule) {
|
||||
return Promise.reject(t('module.Plugin input is required'));
|
||||
}
|
||||
if (pluginInputModule.inputs.length < 1) {
|
||||
return Promise.reject(t('module.Plugin input is not value'));
|
||||
if (
|
||||
item.flowType === FlowNodeTypeEnum.pluginOutput &&
|
||||
item.inputs.find((input) => !input.connected)
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.Plugin output must connect')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) return false;
|
||||
if (!input.value || input.value === '' || input.value?.length === 0) return true;
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: `【${item.name}】存在未填或未连接参数`
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// plugin must have input
|
||||
const pluginInputModule = modules.find(
|
||||
(item) => item.flowType === FlowNodeTypeEnum.pluginInput
|
||||
);
|
||||
|
||||
if (!pluginInputModule) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('module.Plugin input is required')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (pluginInputModule.inputs.length < 1) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('module.Plugin input is not value')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return modules;
|
||||
}, [edges, nodes, t, toast]);
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
mutationFn: (modules: ModuleItemType[]) => {
|
||||
return putUpdatePlugin({
|
||||
id: plugin._id,
|
||||
modules
|
||||
@@ -90,7 +117,7 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<MyTooltip label={'返回'} offset={[10, 10]}>
|
||||
<MyTooltip label={t('common.Back')} offset={[10, 10]}>
|
||||
<IconButton
|
||||
size={'sm'}
|
||||
icon={<MyIcon name={'back'} w={'14px'} />}
|
||||
@@ -125,12 +152,12 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
borderRadius={'lg'}
|
||||
variant={'base'}
|
||||
aria-label={'save'}
|
||||
onClick={() =>
|
||||
copyData(
|
||||
JSON.stringify(flowNode2Modules({ nodes, edges }), null, 2),
|
||||
t('app.Export Config Successful')
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
copyData(JSON.stringify(modules, null, 2), t('app.Export Config Successful'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('module.Preview Plugin')}>
|
||||
@@ -141,7 +168,10 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
aria-label={'save'}
|
||||
variant={'base'}
|
||||
onClick={() => {
|
||||
setPreviewModules(flowNode2Modules({ nodes, edges }));
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
setPreviewModules(modules);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
@@ -151,7 +181,12 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
borderRadius={'lg'}
|
||||
isLoading={isLoading}
|
||||
aria-label={'save'}
|
||||
onClick={onclickSave}
|
||||
onClick={() => {
|
||||
const modules = flow2ModulesAndCheck();
|
||||
if (modules) {
|
||||
onclickSave(modules);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
|
||||
@@ -3,7 +3,7 @@ import ReactFlow, { Background, ReactFlowProvider, useNodesState } from 'reactfl
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { formatPluginToPreviewModule } from '@fastgpt/global/core/module/utils';
|
||||
import { plugin2ModuleIO } from '@fastgpt/global/core/module/utils';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -37,7 +37,7 @@ const PreviewPlugin = ({
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
...formatPluginToPreviewModule(plugin._id, modules)
|
||||
...plugin2ModuleIO(plugin._id, modules)
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -24,10 +24,12 @@ const Render = ({ pluginId }: Props) => {
|
||||
const { nodes = [] } = useFlowProviderStore();
|
||||
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
|
||||
|
||||
const filterTemplates = useMemo(() => {
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
|
||||
JSON.stringify(pluginSystemModuleTemplates)
|
||||
);
|
||||
const moduleTemplates = useMemo(() => {
|
||||
const pluginTemplates = pluginModuleTemplates.filter((item) => item.id !== pluginId);
|
||||
const concatTemplates = [...pluginSystemModuleTemplates, ...pluginTemplates];
|
||||
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
|
||||
|
||||
const filterType: Record<string, 1> = {
|
||||
[FlowNodeTypeEnum.userGuide]: 1,
|
||||
[FlowNodeTypeEnum.pluginInput]: 1,
|
||||
@@ -45,8 +47,13 @@ const Render = ({ pluginId }: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
// filter hideInPlugin inputs
|
||||
copyTemplates.forEach((template) => {
|
||||
template.inputs = template.inputs.filter((input) => !input.hideInPlugin);
|
||||
});
|
||||
|
||||
return copyTemplates;
|
||||
}, [nodes]);
|
||||
}, [nodes, pluginId, pluginModuleTemplates]);
|
||||
|
||||
const { data: pluginDetail } = useQuery(
|
||||
['getOnePlugin', pluginId],
|
||||
@@ -61,17 +68,12 @@ const Render = ({ pluginId }: Props) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
console.log(pluginDetail);
|
||||
|
||||
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
|
||||
const filterPlugins = useMemo(() => {
|
||||
return pluginModuleTemplates.filter((item) => item.id !== pluginId);
|
||||
}, [pluginId, pluginModuleTemplates]);
|
||||
|
||||
return pluginDetail ? (
|
||||
<Flow
|
||||
systemTemplates={filterTemplates}
|
||||
pluginTemplates={filterPlugins}
|
||||
templates={moduleTemplates}
|
||||
modules={pluginDetail?.modules || []}
|
||||
Header={<Header plugin={pluginDetail} onClose={() => router.back()} />}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
Input,
|
||||
Textarea,
|
||||
IconButton
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Flex, Button, ModalBody, Input, Textarea, IconButton } from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
@@ -25,6 +16,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
export type FormType = CreateOnePluginParams & {
|
||||
id?: string;
|
||||
@@ -35,7 +28,7 @@ export const defaultForm: FormType = {
|
||||
intro: '',
|
||||
modules: [
|
||||
{
|
||||
moduleId: 'w90mfp',
|
||||
moduleId: nanoid(),
|
||||
name: '定义插件输入',
|
||||
avatar: '/imgs/module/input.png',
|
||||
flowType: 'pluginInput',
|
||||
@@ -44,30 +37,11 @@ export const defaultForm: FormType = {
|
||||
x: 616.4226348688949,
|
||||
y: -165.05298493910115
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'question',
|
||||
valueType: 'string',
|
||||
type: 'target',
|
||||
label: '用户问题',
|
||||
required: true,
|
||||
edit: true,
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'question',
|
||||
valueType: 'string',
|
||||
label: '用户问题',
|
||||
type: 'source',
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
inputs: [],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'tze1ju',
|
||||
moduleId: nanoid(),
|
||||
name: '定义插件输出',
|
||||
avatar: '/imgs/module/output.png',
|
||||
flowType: 'pluginOutput',
|
||||
@@ -76,27 +50,8 @@ export const defaultForm: FormType = {
|
||||
x: 1607.7142331269126,
|
||||
y: -151.8669210746189
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
type: 'target',
|
||||
valueType: 'string',
|
||||
label: '答案',
|
||||
required: true,
|
||||
edit: true,
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
valueType: 'string',
|
||||
label: '答案',
|
||||
type: 'source',
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -127,7 +82,7 @@ const CreateModal = ({
|
||||
});
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png,.svg',
|
||||
fileType: 'image/*',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
|
||||
@@ -40,17 +40,19 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const cqModel = getCQModel(model);
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, tokens } = await (async () => {
|
||||
if (cqModel.functionCall) {
|
||||
return functionCall({
|
||||
...props,
|
||||
histories: getHistories(history, histories),
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
}
|
||||
return completions({
|
||||
...props,
|
||||
histories: getHistories(history, histories),
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
});
|
||||
})();
|
||||
@@ -65,7 +67,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
query: userChatInput,
|
||||
tokens,
|
||||
cqList: agents,
|
||||
cqResult: result.value
|
||||
cqResult: result.value,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -115,7 +118,7 @@ ${systemPrompt}
|
||||
required: ['type']
|
||||
}
|
||||
};
|
||||
const ai = getAIApi(user.openaiAccount, 48000);
|
||||
const ai = getAIApi(user.openaiAccount, 480000);
|
||||
|
||||
const response = await ai.chat.completions.create({
|
||||
model: cqModel.model,
|
||||
|
||||
@@ -38,18 +38,19 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
}
|
||||
|
||||
const extractModel = global.extractModels[0];
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, tokens } = await (async () => {
|
||||
if (extractModel.functionCall) {
|
||||
return functionCall({
|
||||
...props,
|
||||
histories: getHistories(history, histories),
|
||||
histories: chatHistories,
|
||||
extractModel
|
||||
});
|
||||
}
|
||||
return completions({
|
||||
...props,
|
||||
histories: getHistories(history, histories),
|
||||
histories: chatHistories,
|
||||
extractModel
|
||||
});
|
||||
})();
|
||||
@@ -84,7 +85,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
query: content,
|
||||
tokens,
|
||||
extractDescription: description,
|
||||
extractResult: arg
|
||||
extractResult: arg,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -100,11 +102,11 @@ async function functionCall({
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: `<任务描述>
|
||||
${description || '根据用户要求提取适当的 JSON 字符串。'}
|
||||
${description || '根据用户要求获取适当的 JSON 字符串。'}
|
||||
|
||||
- 如果字段为空,你返回空字符串。
|
||||
- 不要换行。
|
||||
- 结合历史记录和文本进行提取。
|
||||
- 结合历史记录和文本进行获取。
|
||||
</任务描述>
|
||||
|
||||
<文本>
|
||||
@@ -128,7 +130,8 @@ ${content}
|
||||
extractKeys.forEach((item) => {
|
||||
properties[item.key] = {
|
||||
type: 'string',
|
||||
description: item.desc
|
||||
description: item.desc,
|
||||
...(item.enum ? { enum: item.enum.split('\n') } : {})
|
||||
};
|
||||
});
|
||||
|
||||
@@ -192,7 +195,9 @@ async function completions({
|
||||
json: extractKeys
|
||||
.map(
|
||||
(item) =>
|
||||
`{"key":"${item.key}", "description":"${item.required}", "required":${item.required}}}`
|
||||
`{"key":"${item.key}", "description":"${item.required}", "required":${item.required}${
|
||||
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
|
||||
}}`
|
||||
)
|
||||
.join('\n'),
|
||||
text: `${histories.map((item) => `${item.obj}:${item.value}`).join('\n')}
|
||||
|
||||
@@ -202,7 +202,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
query: userChatInput,
|
||||
maxToken: max_tokens,
|
||||
quoteList: filterQuoteQA,
|
||||
historyPreview: getHistoryPreview(completeMessages)
|
||||
historyPreview: getHistoryPreview(completeMessages),
|
||||
contextTotalLen: completeMessages.length
|
||||
},
|
||||
history: completeMessages
|
||||
};
|
||||
|
||||
@@ -26,6 +26,21 @@ import { dispatchRunPlugin } from './plugin/run';
|
||||
import { dispatchPluginInput } from './plugin/runInput';
|
||||
import { dispatchPluginOutput } from './plugin/runOutput';
|
||||
|
||||
const callbackMap: Record<string, Function> = {
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
|
||||
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
|
||||
};
|
||||
|
||||
/* running */
|
||||
export async function dispatchModules({
|
||||
res,
|
||||
@@ -111,6 +126,7 @@ export async function dispatchModules({
|
||||
Object.entries(data).map(([key, val]: any) => {
|
||||
updateInputValue(key, val);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
function moduleOutput(
|
||||
@@ -119,6 +135,7 @@ export async function dispatchModules({
|
||||
): Promise<any> {
|
||||
pushStore(module, result);
|
||||
|
||||
//
|
||||
const nextRunModules: RunningModuleItemType[] = [];
|
||||
|
||||
// Assign the output value to the next module
|
||||
@@ -141,18 +158,19 @@ export async function dispatchModules({
|
||||
});
|
||||
});
|
||||
|
||||
return checkModulesCanRun(nextRunModules);
|
||||
}
|
||||
function checkModulesCanRun(modules: RunningModuleItemType[] = []) {
|
||||
// Ensure the uniqueness of running modules
|
||||
const set = new Set<string>();
|
||||
const filterModules = modules.filter((module) => {
|
||||
const filterModules = nextRunModules.filter((module) => {
|
||||
if (set.has(module.moduleId)) return false;
|
||||
set.add(module.moduleId);
|
||||
return true;
|
||||
});
|
||||
|
||||
return checkModulesCanRun(filterModules);
|
||||
}
|
||||
function checkModulesCanRun(modules: RunningModuleItemType[] = []) {
|
||||
return Promise.all(
|
||||
filterModules.map((module) => {
|
||||
modules.map((module) => {
|
||||
if (!module.inputs.find((item: any) => item.value === undefined)) {
|
||||
moduleInput(module, { [ModuleInputKeyEnum.switch]: undefined });
|
||||
return moduleRun(module);
|
||||
@@ -192,20 +210,6 @@ export async function dispatchModules({
|
||||
};
|
||||
|
||||
const dispatchRes: Record<string, any> = await (async () => {
|
||||
const callbackMap: Record<string, Function> = {
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
|
||||
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
|
||||
};
|
||||
if (callbackMap[module.flowType]) {
|
||||
return callbackMap[module.flowType](props);
|
||||
}
|
||||
@@ -232,6 +236,11 @@ export async function dispatchModules({
|
||||
|
||||
// start process width initInput
|
||||
const initModules = runningModules.filter((item) => initRunningModuleType[item.flowType]);
|
||||
|
||||
// runningModules.forEach((item) => {
|
||||
// console.log(item);
|
||||
// });
|
||||
|
||||
initModules.map((module) =>
|
||||
moduleInput(module, {
|
||||
...startParams,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { ModuleDispatchProps } from '@/types/core/chat/type';
|
||||
import { dispatchModules } from '../index';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { getPluginRuntimeById } from '@fastgpt/service/core/plugin/controller';
|
||||
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
|
||||
@@ -29,13 +33,37 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
await authPluginCanUse({ id: pluginId, teamId, tmbId });
|
||||
const plugin = await getPluginRuntimeById(pluginId);
|
||||
|
||||
// concat dynamic inputs
|
||||
const inputModule = plugin.modules.find((item) => item.flowType === FlowNodeTypeEnum.pluginInput);
|
||||
if (!inputModule) return Promise.reject('Plugin error, It has no set input.');
|
||||
const hasDynamicInput = inputModule.inputs.find((input) => input.key === DYNAMIC_INPUT_KEY);
|
||||
|
||||
const startParams: Record<string, any> = (() => {
|
||||
if (!hasDynamicInput) return data;
|
||||
|
||||
const params: Record<string, any> = {
|
||||
[DYNAMIC_INPUT_KEY]: {}
|
||||
};
|
||||
|
||||
for (const key in data) {
|
||||
const input = inputModule.inputs.find((input) => input.key === key);
|
||||
if (input) {
|
||||
params[key] = data[key];
|
||||
} else {
|
||||
params[DYNAMIC_INPUT_KEY][key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
})();
|
||||
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
...props,
|
||||
modules: plugin.modules.map((module) => ({
|
||||
...module,
|
||||
showStatus: false
|
||||
})),
|
||||
startParams: data
|
||||
startParams
|
||||
});
|
||||
|
||||
const output = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
|
||||
@@ -46,6 +74,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
|
||||
return {
|
||||
answerText,
|
||||
// responseData, // debug
|
||||
responseData: {
|
||||
moduleLogo: plugin.avatar,
|
||||
price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@/types/core/chat/type';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import axios from 'axios';
|
||||
import { flatDynamicParams } from '../utils';
|
||||
|
||||
export type HttpRequestProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.httpUrl]: string;
|
||||
[ModuleInputKeyEnum.abandon_httpUrl]: string;
|
||||
[ModuleInputKeyEnum.httpMethod]: string;
|
||||
[ModuleInputKeyEnum.httpReqUrl]: string;
|
||||
[ModuleInputKeyEnum.httpHeader]: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
export type HttpResponse = {
|
||||
@@ -12,39 +17,98 @@ export type HttpResponse = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export const dispatchHttpRequest = async (props: Record<string, any>): Promise<HttpResponse> => {
|
||||
const {
|
||||
export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<HttpResponse> => {
|
||||
let {
|
||||
appId,
|
||||
chatId,
|
||||
variables,
|
||||
inputs: { url, ...body }
|
||||
} = props as HttpRequestProps;
|
||||
inputs: {
|
||||
system_httpMethod: httpMethod,
|
||||
url: abandonUrl,
|
||||
system_httpReqUrl: httpReqUrl,
|
||||
system_httpHeader: httpHeader,
|
||||
...body
|
||||
}
|
||||
} = props;
|
||||
|
||||
const requestBody = {
|
||||
...body,
|
||||
chatId,
|
||||
variables
|
||||
};
|
||||
body = flatDynamicParams(body);
|
||||
|
||||
const { requestMethod, requestUrl, requestHeader, requestBody, requestQuery } = await (() => {
|
||||
// 2024-2-12 clear
|
||||
if (abandonUrl) {
|
||||
return {
|
||||
requestMethod: 'POST',
|
||||
requestUrl: abandonUrl,
|
||||
requestHeader: httpHeader,
|
||||
requestBody: {
|
||||
...body,
|
||||
appId,
|
||||
chatId,
|
||||
variables
|
||||
},
|
||||
requestQuery: {}
|
||||
};
|
||||
}
|
||||
if (httpReqUrl) {
|
||||
return {
|
||||
requestMethod: httpMethod,
|
||||
requestUrl: httpReqUrl,
|
||||
requestHeader: httpHeader,
|
||||
requestBody: {
|
||||
appId,
|
||||
chatId,
|
||||
variables,
|
||||
data: body
|
||||
},
|
||||
requestQuery: {
|
||||
appId,
|
||||
chatId,
|
||||
...variables,
|
||||
...body
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('url is empty');
|
||||
})();
|
||||
|
||||
const formatBody = transformFlatJson({ ...requestBody });
|
||||
|
||||
// parse header
|
||||
const headers = await (() => {
|
||||
try {
|
||||
if (!requestHeader) return {};
|
||||
return JSON.parse(requestHeader);
|
||||
} catch (error) {
|
||||
return Promise.reject('Header 为非法 JSON 格式');
|
||||
}
|
||||
})();
|
||||
|
||||
try {
|
||||
const response = await fetchData({
|
||||
url,
|
||||
body: requestBody
|
||||
method: requestMethod,
|
||||
url: requestUrl,
|
||||
headers,
|
||||
body: formatBody,
|
||||
query: requestQuery
|
||||
});
|
||||
|
||||
return {
|
||||
responseData: {
|
||||
price: 0,
|
||||
body: requestBody,
|
||||
body: formatBody,
|
||||
httpResult: response
|
||||
},
|
||||
...response
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
responseData: {
|
||||
price: 0,
|
||||
body: requestBody,
|
||||
body: formatBody,
|
||||
httpResult: { error }
|
||||
}
|
||||
};
|
||||
@@ -52,19 +116,97 @@ export const dispatchHttpRequest = async (props: Record<string, any>): Promise<H
|
||||
};
|
||||
|
||||
async function fetchData({
|
||||
method,
|
||||
url,
|
||||
body
|
||||
headers,
|
||||
body,
|
||||
query
|
||||
}: {
|
||||
method: string;
|
||||
url: string;
|
||||
headers: Record<string, any>;
|
||||
body: Record<string, any>;
|
||||
query: Record<string, any>;
|
||||
}): Promise<Record<string, any>> {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
const { data: response } = await axios<Record<string, any>>({
|
||||
method,
|
||||
baseURL: `http://${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}`,
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
}).then((res) => res.json());
|
||||
params: method === 'GET' ? query : {},
|
||||
data: method === 'POST' ? body : {}
|
||||
});
|
||||
|
||||
return response;
|
||||
/*
|
||||
parse the json:
|
||||
{
|
||||
user: {
|
||||
name: 'xxx',
|
||||
age: 12
|
||||
}
|
||||
psw: 'xxx'
|
||||
}
|
||||
|
||||
result: {
|
||||
'user': {
|
||||
name: 'xxx',
|
||||
age: 12
|
||||
},
|
||||
'user.name': 'xxx',
|
||||
'user.age': 12,
|
||||
'psw': 'xxx'
|
||||
}
|
||||
*/
|
||||
const parseJson = (obj: Record<string, any>, prefix = '') => {
|
||||
let result: Record<string, any> = {};
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'object') {
|
||||
result[key] = obj[key];
|
||||
result = {
|
||||
...result,
|
||||
...parseJson(obj[key], `${prefix}${key}.`)
|
||||
};
|
||||
} else {
|
||||
result[`${prefix}${key}`] = obj[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return parseJson(response);
|
||||
}
|
||||
|
||||
function transformFlatJson(obj: Record<string, any>) {
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === 'object') {
|
||||
transformFlatJson(obj[key]);
|
||||
}
|
||||
if (key.includes('.')) {
|
||||
let parts = key.split('.');
|
||||
if (parts.length <= 1) continue;
|
||||
|
||||
const firstKey = parts.shift();
|
||||
|
||||
if (!firstKey) continue;
|
||||
|
||||
const lastKey = parts.join('.');
|
||||
|
||||
if (obj[firstKey]) {
|
||||
obj[firstKey] = {
|
||||
...obj[firstKey],
|
||||
[lastKey]: obj[key]
|
||||
};
|
||||
} else {
|
||||
obj[firstKey] = { [lastKey]: obj[key] };
|
||||
}
|
||||
|
||||
transformFlatJson(obj[firstKey]);
|
||||
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
|
||||
import { textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { getHistories } from '../utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
userChatInput: string;
|
||||
@@ -26,6 +27,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
||||
user,
|
||||
stream,
|
||||
detail,
|
||||
histories,
|
||||
inputs: { userChatInput, history = [], app }
|
||||
} = props;
|
||||
|
||||
@@ -52,17 +54,19 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
||||
});
|
||||
}
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
...props,
|
||||
appId: app.id,
|
||||
modules: appData.modules,
|
||||
histories: history,
|
||||
histories: chatHistories,
|
||||
startParams: {
|
||||
userChatInput
|
||||
}
|
||||
});
|
||||
|
||||
const completeMessages = history.concat([
|
||||
const completeMessages = chatHistories.concat([
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: userChatInput
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { DYNAMIC_INPUT_KEY } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
|
||||
if (!history) return [];
|
||||
@@ -7,3 +8,13 @@ export const getHistories = (history?: ChatItemType[] | number, histories: ChatI
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const flatDynamicParams = (params: Record<string, any>) => {
|
||||
const dynamicParams = params[DYNAMIC_INPUT_KEY];
|
||||
if (!dynamicParams) return params;
|
||||
return {
|
||||
...params,
|
||||
...dynamicParams,
|
||||
[DYNAMIC_INPUT_KEY]: undefined
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
|
||||
import { defaultQGModels } from '@fastgpt/global/core/ai/model';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
import { PostReRankProps } from '@fastgpt/global/core/ai/api';
|
||||
|
||||
export function createBill(data: CreateBillProps) {
|
||||
if (!global.systemEnv?.pluginBaseUrl) return;
|
||||
@@ -247,16 +248,21 @@ export function pushWhisperBill({
|
||||
export function pushReRankBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
source
|
||||
source,
|
||||
inputs
|
||||
}: {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
source: `${BillSourceEnum}`;
|
||||
inputs: PostReRankProps['inputs'];
|
||||
}) {
|
||||
const model = global.reRankModels[0];
|
||||
if (!model) return { total: 0 };
|
||||
|
||||
const total = model.price * PRICE_SCALE;
|
||||
const textLength = inputs.reduce((sum, item) => sum + item.text.length, 0);
|
||||
const ratio = Math.ceil(textLength / 1000);
|
||||
|
||||
const total = model.price * PRICE_SCALE * ratio;
|
||||
const name = 'wallet.bill.ReRank';
|
||||
|
||||
createBill({
|
||||
|
||||
2
projects/app/src/types/app.d.ts
vendored
2
projects/app/src/types/app.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { XYPosition } from 'reactflow';
|
||||
import { AppModuleItemTypeEnum, ModulesInputItemTypeEnum } from '../constants/app';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { ModuleItemType, FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { Edge, Node } from 'reactflow';
|
||||
import { connectionLineStyle } from '@/web/core/modules/constants/flowUi';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { EmptyModule } from '@fastgpt/global/core/module/template/system/empty';
|
||||
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
|
||||
import { adaptRole_Message2Chat } from '@fastgpt/global/core/chat/adapt';
|
||||
import { EDGE_TYPE } from '@fastgpt/global/core/module/node/constant';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
export const gptMessage2ChatType = (messages: ChatMessageItemType[]): ChatItemType[] => {
|
||||
@@ -100,14 +100,12 @@ export const appModule2FlowEdge = ({
|
||||
module.outputs.forEach((output) =>
|
||||
output.targets.forEach((target) => {
|
||||
edges.push({
|
||||
style: connectionLineStyle,
|
||||
source: module.moduleId,
|
||||
target: target.moduleId,
|
||||
sourceHandle: output.key,
|
||||
targetHandle: target.key,
|
||||
id: nanoid(),
|
||||
animated: true,
|
||||
type: 'buttonedge',
|
||||
type: EDGE_TYPE,
|
||||
data: { onDelete }
|
||||
});
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ export const postUploadFiles = (
|
||||
onUploadProgress: (progressEvent: AxiosProgressEvent) => void
|
||||
) =>
|
||||
POST<string[]>('/common/file/upload', data, {
|
||||
timeout: 0,
|
||||
timeout: 480000,
|
||||
onUploadProgress,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data; charset=utf-8'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useDisclosure, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyModal from '@/components/MyModal';
|
||||
@@ -7,7 +7,6 @@ export const useConfirm = (props?: {
|
||||
title?: string;
|
||||
iconSrc?: string | '';
|
||||
content?: string;
|
||||
bg?: string;
|
||||
showCancel?: boolean;
|
||||
type?: 'common' | 'delete';
|
||||
}) => {
|
||||
@@ -34,7 +33,6 @@ export const useConfirm = (props?: {
|
||||
title = map?.title || t('Warning'),
|
||||
iconSrc = map?.iconSrc,
|
||||
content,
|
||||
bg = map?.bg,
|
||||
showCancel = true
|
||||
} = props || {};
|
||||
const [customContent, setCustomContent] = useState(content);
|
||||
@@ -57,43 +55,72 @@ export const useConfirm = (props?: {
|
||||
[onOpen]
|
||||
),
|
||||
ConfirmModal: useCallback(
|
||||
({ isLoading }: { isLoading?: boolean }) => (
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc={iconSrc}
|
||||
title={title}
|
||||
maxW={['90vw', '500px']}
|
||||
>
|
||||
<ModalBody pt={5}>{customContent}</ModalBody>
|
||||
<ModalFooter>
|
||||
{showCancel && (
|
||||
({
|
||||
closeText = t('Cancel'),
|
||||
confirmText = t('Confirm'),
|
||||
isLoading,
|
||||
bg,
|
||||
countDown = 0
|
||||
}: {
|
||||
closeText?: string;
|
||||
confirmText?: string;
|
||||
isLoading?: boolean;
|
||||
bg?: string;
|
||||
countDown?: number;
|
||||
}) => {
|
||||
const timer = useRef<any>();
|
||||
const [countDownAmount, setCountDownAmount] = useState(countDown);
|
||||
|
||||
useEffect(() => {
|
||||
timer.current = setInterval(() => {
|
||||
setCountDownAmount((val) => {
|
||||
if (val <= 0) {
|
||||
clearInterval(timer.current);
|
||||
}
|
||||
return val - 1;
|
||||
});
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc={iconSrc}
|
||||
title={title}
|
||||
maxW={['90vw', '500px']}
|
||||
>
|
||||
<ModalBody pt={5}>{customContent}</ModalBody>
|
||||
<ModalFooter>
|
||||
{showCancel && (
|
||||
<Button
|
||||
variant={'base'}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
typeof cancelCb.current === 'function' && cancelCb.current();
|
||||
}}
|
||||
>
|
||||
{closeText}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant={'base'}
|
||||
{...(bg && { bg: `${bg} !important` })}
|
||||
isDisabled={countDownAmount > 0}
|
||||
ml={4}
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
typeof cancelCb.current === 'function' && cancelCb.current();
|
||||
typeof confirmCb.current === 'function' && confirmCb.current();
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
{countDownAmount > 0 ? `${countDownAmount}s` : confirmText}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
{...(bg && { bg: `${bg} !important` })}
|
||||
ml={4}
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
typeof confirmCb.current === 'function' && confirmCb.current();
|
||||
}}
|
||||
>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
),
|
||||
[bg, customContent, iconSrc, isOpen, onClose, showCancel, t, title]
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
},
|
||||
[customContent, iconSrc, isOpen, onClose, showCancel, t, title]
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ export const useCopyData = () => {
|
||||
throw new Error('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = data;
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
@@ -2,4 +2,4 @@ import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
|
||||
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
|
||||
|
||||
export const postFetchUrls = (data: UrlFetchParams) =>
|
||||
POST<UrlFetchResponse>(`/tools/urlFetch`, data);
|
||||
POST<UrlFetchResponse>(`/common/tools/urlFetch`, data);
|
||||
|
||||
@@ -1,60 +1,48 @@
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export const FlowValueTypeStyle: Record<`${ModuleDataTypeEnum}`, BoxProps> = {
|
||||
[ModuleDataTypeEnum.string]: {
|
||||
background: '#36ADEF'
|
||||
},
|
||||
[ModuleDataTypeEnum.number]: {
|
||||
background: '#FB7C3C'
|
||||
},
|
||||
[ModuleDataTypeEnum.boolean]: {
|
||||
background: '#E7D118'
|
||||
},
|
||||
[ModuleDataTypeEnum.chatHistory]: {
|
||||
background: '#00A9A6'
|
||||
},
|
||||
[ModuleDataTypeEnum.datasetQuote]: {
|
||||
background: '#A558C9'
|
||||
},
|
||||
[ModuleDataTypeEnum.any]: {
|
||||
background: '#9CA2A8'
|
||||
},
|
||||
[ModuleDataTypeEnum.selectApp]: {
|
||||
background: '#6a6efa'
|
||||
},
|
||||
[ModuleDataTypeEnum.selectDataset]: {
|
||||
background: '#21ba45'
|
||||
}
|
||||
};
|
||||
export const FlowValueTypeMap = {
|
||||
[ModuleDataTypeEnum.string]: {
|
||||
[ModuleIOValueTypeEnum.string]: {
|
||||
handlerStyle: {
|
||||
background: '#36ADEF'
|
||||
},
|
||||
label: 'core.module.valueType.string',
|
||||
value: ModuleDataTypeEnum.string,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.string,
|
||||
description: ''
|
||||
},
|
||||
[ModuleDataTypeEnum.number]: {
|
||||
[ModuleIOValueTypeEnum.number]: {
|
||||
handlerStyle: {
|
||||
background: '#FB7C3C'
|
||||
},
|
||||
label: 'core.module.valueType.number',
|
||||
value: ModuleDataTypeEnum.number,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.number,
|
||||
description: ''
|
||||
},
|
||||
[ModuleDataTypeEnum.boolean]: {
|
||||
[ModuleIOValueTypeEnum.boolean]: {
|
||||
handlerStyle: {
|
||||
background: '#E7D118'
|
||||
},
|
||||
label: 'core.module.valueType.boolean',
|
||||
value: ModuleDataTypeEnum.boolean,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.boolean,
|
||||
description: ''
|
||||
},
|
||||
[ModuleDataTypeEnum.chatHistory]: {
|
||||
[ModuleIOValueTypeEnum.chatHistory]: {
|
||||
handlerStyle: {
|
||||
background: '#00A9A6'
|
||||
},
|
||||
label: 'core.module.valueType.chatHistory',
|
||||
value: ModuleDataTypeEnum.chatHistory,
|
||||
example: `{
|
||||
value: ModuleIOValueTypeEnum.chatHistory,
|
||||
description: `{
|
||||
obj: System | Human | AI;
|
||||
value: string;
|
||||
}[]`
|
||||
},
|
||||
[ModuleDataTypeEnum.datasetQuote]: {
|
||||
[ModuleIOValueTypeEnum.datasetQuote]: {
|
||||
handlerStyle: {
|
||||
background: '#A558C9'
|
||||
},
|
||||
label: 'core.module.valueType.datasetQuote',
|
||||
value: ModuleDataTypeEnum.datasetQuote,
|
||||
example: `{
|
||||
value: ModuleIOValueTypeEnum.datasetQuote,
|
||||
description: `{
|
||||
id: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
@@ -64,19 +52,28 @@ export const FlowValueTypeMap = {
|
||||
a: string
|
||||
}[]`
|
||||
},
|
||||
[ModuleDataTypeEnum.any]: {
|
||||
[ModuleIOValueTypeEnum.any]: {
|
||||
handlerStyle: {
|
||||
background: '#9CA2A8'
|
||||
},
|
||||
label: 'core.module.valueType.any',
|
||||
value: ModuleDataTypeEnum.any,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.any,
|
||||
description: ''
|
||||
},
|
||||
[ModuleDataTypeEnum.selectApp]: {
|
||||
[ModuleIOValueTypeEnum.selectApp]: {
|
||||
handlerStyle: {
|
||||
background: '#6a6efa'
|
||||
},
|
||||
label: 'core.module.valueType.selectApp',
|
||||
value: ModuleDataTypeEnum.selectApp,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.selectApp,
|
||||
description: ''
|
||||
},
|
||||
[ModuleDataTypeEnum.selectDataset]: {
|
||||
[ModuleIOValueTypeEnum.selectDataset]: {
|
||||
handlerStyle: {
|
||||
background: '#21ba45'
|
||||
},
|
||||
label: 'core.module.valueType.selectDataset',
|
||||
value: ModuleDataTypeEnum.selectDataset,
|
||||
example: ''
|
||||
value: ModuleIOValueTypeEnum.selectDataset,
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const edgeOptions = {
|
||||
style: {
|
||||
strokeWidth: 1.5,
|
||||
stroke: '#5A646Es'
|
||||
}
|
||||
};
|
||||
export const connectionLineStyle = { strokeWidth: 1.5, stroke: '#5A646Es' };
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UserGuideModule } from '@fastgpt/global/core/module/template/system/userGuide';
|
||||
import { UserInputModule } from '@fastgpt/global/core/module/template/system/userInput';
|
||||
import { HistoryModule } from '@fastgpt/global/core/module/template/system/history';
|
||||
import { HistoryModule } from '@fastgpt/global/core/module/template/system/abandon/history';
|
||||
import { AiChatModule } from '@fastgpt/global/core/module/template/system/aiChat';
|
||||
import { DatasetSearchModule } from '@fastgpt/global/core/module/template/system/datasetSearch';
|
||||
import { AssignedAnswerModule } from '@fastgpt/global/core/module/template/system/assignedAnswer';
|
||||
@@ -12,6 +12,7 @@ import { RunAppModule } from '@fastgpt/global/core/module/template/system/runApp
|
||||
import { PluginInputModule } from '@fastgpt/global/core/module/template/system/pluginInput';
|
||||
import { PluginOutputModule } from '@fastgpt/global/core/module/template/system/pluginOutput';
|
||||
import { RunPluginModule } from '@fastgpt/global/core/module/template/system/runPlugin';
|
||||
|
||||
import type {
|
||||
FlowModuleTemplateType,
|
||||
moduleTemplateListType
|
||||
@@ -68,19 +69,19 @@ export const moduleTemplatesList: moduleTemplateListType = [
|
||||
label: '系统输入',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.tools,
|
||||
label: '工具',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.textAnswer,
|
||||
label: '文本输出',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.dataset,
|
||||
label: '知识库',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.functionCall,
|
||||
label: '函数调用',
|
||||
label: '功能调用',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
@@ -93,59 +94,9 @@ export const moduleTemplatesList: moduleTemplateListType = [
|
||||
label: '个人插件',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.communityPlugin,
|
||||
label: '社区插件',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.commercialPlugin,
|
||||
label: '商业插件',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: ModuleTemplateTypeEnum.other,
|
||||
label: '其他',
|
||||
list: []
|
||||
}
|
||||
];
|
||||
// export const appSystemModuleTemplates = [
|
||||
// {
|
||||
// label: '引导模块',
|
||||
// list: [UserGuideModule]
|
||||
// },
|
||||
// {
|
||||
// label: '输入模块',
|
||||
// list: [UserInputModule, HistoryModule]
|
||||
// },
|
||||
// {
|
||||
// label: '内容生成',
|
||||
// list: [AiChatModule, AssignedAnswerModule]
|
||||
// },
|
||||
// {
|
||||
// label: '核心调用',
|
||||
// list: [DatasetSearchModule, RunAppModule]
|
||||
// },
|
||||
// {
|
||||
// label: '函数模块',
|
||||
// list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
|
||||
// }
|
||||
// ];
|
||||
// export const pluginModuleTemplates = [
|
||||
// {
|
||||
// label: '输入输出',
|
||||
// list: [PluginInputModule, PluginOutputModule, HistoryModule]
|
||||
// },
|
||||
// {
|
||||
// label: '内容生成',
|
||||
// list: [AiChatModule, AssignedAnswerModule]
|
||||
// },
|
||||
// {
|
||||
// label: '核心调用',
|
||||
// list: [DatasetSearchModule, RunAppModule]
|
||||
// },
|
||||
// {
|
||||
// label: '函数模块',
|
||||
// list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
|
||||
// }
|
||||
// ];
|
||||
|
||||
@@ -71,7 +71,7 @@ const Button = defineStyleConfig({
|
||||
color: 'myBlue.700',
|
||||
border: '1px solid #EFF0F1',
|
||||
_hover: {
|
||||
bg: '#3370FF1A'
|
||||
background: '#3370FF1A'
|
||||
}
|
||||
},
|
||||
base: {
|
||||
@@ -82,13 +82,16 @@ const Button = defineStyleConfig({
|
||||
transition: 'background 0.3s',
|
||||
_hover: {
|
||||
color: 'myBlue.600',
|
||||
bg: 'myWhite.400',
|
||||
background: 'myWhite.400',
|
||||
boxShadow: '0 0 5px rgba(0,0,0,0.1)'
|
||||
},
|
||||
_active: {
|
||||
color: 'myBlue.700'
|
||||
},
|
||||
_disabled: { bg: 'myGray.100 !important', color: 'myGray.700 !important' }
|
||||
_disabled: {
|
||||
bg: 'myGray.100 !important',
|
||||
color: 'myGray.700 !important'
|
||||
}
|
||||
},
|
||||
boxBtn: {
|
||||
px: 3,
|
||||
|
||||
Reference in New Issue
Block a user