mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-30 02:12:38 +00:00
V4.6.5-alpha (#609)
This commit is contained in:
@@ -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
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user