4.7-alpha2 (#1027)

* feat: stop toolCall and rename some field. (#46)

* perf: node delete tip;pay tip

* fix: toolCall cannot save child answer

* feat: stop tool

* fix: team modal

* fix feckbackMoal  auth bug (#47)

* 简单的支持提示词运行tool。优化workflow模板 (#49)

* remove templates

* fix: request body undefined

* feat: prompt tool run

* feat: workflow tamplates modal

* perf: plugin start

* 4.7 (#50)

* fix docker-compose download url (#994)

original code is a bad url with '404 NOT FOUND' return.
fix docker-compose download url, add 'v' before docker-compose version

* Update ai_settings.md (#1000)

* Update configuration.md

* Update configuration.md

* Fix history in classifyQuestion and extract modules (#1012)

* Fix history in classifyQuestion and extract modules

* Add chatValue2RuntimePrompt import and update text formatting

* flow controller to packages

* fix: rerank select

* modal ui

* perf: modal code path

* point not sufficient

* feat: http url support variable

* fix http key

* perf: prompt

* perf: ai setting modal

* simple edit ui

---------

Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>

* fix team share redirect to login (#51)

* feat: support openapi import plugins (#48)

* feat: support openapi import plugins

* feat: import from url

* fix: add body params parse

* fix build

* fix

* fix

* fix

* tool box ui (#52)

* fix: training queue

* feat: simple edit tool select

* perf: simple edit dataset prompt

* fix: chatbox tool ux

* feat: quote prompt module

* perf: plugin tools sign

* perf: model avatar

* tool selector ui

* feat: max histories

* perf: http plugin import (#53)

* perf: plugin http import

* chatBox ui

* perf: name

* fix: Node template card (#54)

* fix: ts

* setting modal

* package

* package

* feat: add plugins search (#57)

* feat: add plugins search

* perf: change http plugin header input

* Yjl (#56)

* perf: prompt tool call

* perf: chat box ux

* doc

* doc

* price tip

* perf: tool selector

* ui'

* fix: vector queue

* fix: empty tool and empty response

* fix: empty msg

* perf: pg index

* perf: ui tip

* doc

* tool tip

---------

Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com>
Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-21 13:32:31 +08:00
committed by GitHub
parent 6d4b331db9
commit 9d27de154b
322 changed files with 9282 additions and 6498 deletions

View File

@@ -30,7 +30,7 @@ import MyBox from '@/components/common/MyBox';
import { useRequest } from '@/web/common/hooks/useRequest';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import MySelect from '@fastgpt/web/components/common/MySelect';
import MyModal from '@fastgpt/web/components/common/CustomModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
const BillTable = () => {

View File

@@ -16,6 +16,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useUserStore } from '@/web/support/user/useUserStore';
import type { UserType } from '@fastgpt/global/support/user/type.d';
import { useQuery } from '@tanstack/react-query';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
@@ -29,16 +30,24 @@ import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tool
import { putUpdateMemberName } from '@/web/support/user/team/api';
import { getDocPath } from '@/web/common/system/doc';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import {
StandardSubLevelEnum,
standardSubLevelMap
} from '@fastgpt/global/support/wallet/sub/constants';
import { formatTime2YMD } from '@fastgpt/global/common/string/time';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
import {
AI_POINT_USAGE_CARD_ROUTE,
EXTRA_PLAN_CARD_ROUTE
} from '@/web/support/wallet/sub/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'));
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
const Account = () => {
const { isPc } = useSystemStore();
@@ -113,11 +122,11 @@ const MyInfo = () => {
});
reset(data);
toast({
title: '更新数据成功',
title: t('dataset.data.Update Success Tip'),
status: 'success'
});
},
[reset, toast, updateUserInfo]
[reset, t, toast, updateUserInfo]
);
const onSelectFile = useCallback(
@@ -184,7 +193,7 @@ const MyInfo = () => {
cursor={'pointer'}
onClick={onOpenSelectFile}
>
<MyTooltip label={'更换头像'}>
<MyTooltip label={t('common.avatar.Select Avatar')}>
<Box
w={['44px', '54px']}
h={['44px', '54px']}
@@ -269,7 +278,6 @@ const MyInfo = () => {
);
};
const PlanUsage = () => {
const { isPc } = useSystemStore();
const router = useRouter();
const { t } = useTranslation();
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
@@ -288,6 +296,21 @@ const PlanUsage = () => {
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
}, [teamPlanStatus?.standard?.currentSubLevel]);
const standardPlan = teamPlanStatus?.standard;
const isFreeTeam = useMemo(() => {
if (!teamPlanStatus || !teamPlanStatus?.standardConstants) return false;
const hasExtraDatasetSize =
teamPlanStatus.datasetMaxSize > teamPlanStatus.standardConstants.maxDatasetSize;
const hasExtraPoints =
teamPlanStatus.totalPoints > teamPlanStatus.standardConstants.totalPoints;
if (
teamPlanStatus?.standard?.currentSubLevel === StandardSubLevelEnum.free &&
!hasExtraDatasetSize &&
!hasExtraPoints
) {
return true;
}
return false;
}, [teamPlanStatus]);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
@@ -374,6 +397,11 @@ const PlanUsage = () => {
<Box fontWeight={'bold'} fontSize="xl">
{t(planName)}
</Box>
{isFreeTeam && (
<Box mt="3" color={'#485264'} fontSize="sm">
15使
</Box>
)}
<Flex mt="3" color={'#485264'} fontSize="sm">
<Box>{t('common.Expired Time')}:</Box>
<Box ml={2}>{formatTime2YMD(standardPlan?.expiredTime)}</Box>
@@ -399,9 +427,29 @@ const PlanUsage = () => {
borderColor={'borderColor.low'}
borderRadius={'md'}
px={[5, 10]}
py={[4, 7]}
pt={[2, 4]}
pb={[4, 7]}
>
<Box width={'100%'}>
<Flex>
<Flex flex={'1 0 0'} alignItems={'flex-end'}>
<Box fontSize={'xl'}></Box>
<Box fontSize={'sm'} color={'myGray.500'}>
()
</Box>
</Flex>
<Link
href={EXTRA_PLAN_CARD_ROUTE}
transform={'translateX(15px)'}
display={'flex'}
alignItems={'center'}
color={'primary.600'}
cursor={'pointer'}
>
<MyIcon ml={1} name={'common/rightArrowLight'} w={'12px'} />
</Link>
</Flex>
<Box width={'100%'} mt={5}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.user.team.Dataset usage')}</Box>
@@ -426,7 +474,10 @@ const PlanUsage = () => {
<Box mt="9" width={'100%'}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points')}</Box>
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points usage')}</Box>
<MyTooltip label={t('support.wallet.subscription.AI points usage tip')}>
<QuestionOutlineIcon ml={'2px'} />
</MyTooltip>
<Box color={'myGray.600'} ml={2}>
{aiPointsUsageMap.used}/{aiPointsUsageMap.max}
</Box>
@@ -445,7 +496,6 @@ const PlanUsage = () => {
/>
</Box>
</Box>
<Flex></Flex>
</Box>
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
</Box>
@@ -462,6 +512,7 @@ const Other = () => {
});
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
const { isOpen: isOpenConcat, onClose: onCloseConcat, onOpen: onOpenConcat } = useDisclosure();
const onclickSave = useCallback(
async (data: UserType) => {
@@ -552,6 +603,17 @@ const Other = () => {
/>
</Flex>
)}
{feConfigs?.concatMd && (
<Button
variant={'whiteBase'}
justifyContent={'flex-start'}
leftIcon={<MyIcon name={'modal/concat'} w={'18px'} color={'myGray.600'} />}
onClick={onOpenConcat}
h={'48px'}
>
</Button>
)}
</Grid>
{isOpenOpenai && userInfo && (
@@ -566,6 +628,7 @@ const Other = () => {
onClose={onCloseOpenai}
/>
)}
{isOpenConcat && <CommunityModal onClose={onCloseConcat} />}
</Box>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';

View File

@@ -1,15 +1,16 @@
import React, { useState, useCallback, useMemo } from 'react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid, Link } from '@chakra-ui/react';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { EXTRA_PLAN_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
const PayModal = ({
onClose,
@@ -59,8 +60,12 @@ const PayModal = ({
return (
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
<Box px={6} fontSize={'sm'} color={'myGray.600'} mb={2} maxW={'400px'}>
<Box px={6} fontSize={'sm'} mb={2} py={2} maxW={'400px'}>
<Link href={EXTRA_PLAN_CARD_ROUTE} color={'primary.600'} textDecoration={'underline'}>
</Link>
</Box>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{payList.map((item) => (

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';

View File

@@ -14,7 +14,7 @@ import {
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type.d';
import dayjs from 'dayjs';
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { formatNumber } from '@fastgpt/global/common/math/tools';

View File

@@ -11,7 +11,7 @@ import {
TableContainer,
ModalCloseButton
} from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@fastgpt/web/hooks/useLoading';

View File

@@ -1,70 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/common/string/jieba';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
console.log(
'total',
await MongoDatasetData.countDocuments({
fullTextToken: { $exists: false },
updateTime: { $lt: new Date() }
})
);
await initFullTextToken(limit, new Date());
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50, endDate: Date): Promise<any> {
try {
const dataList = await MongoDatasetData.find(
{ fullTextToken: { $exists: false }, updateTime: { $lt: endDate } },
'_id q a'
)
.limit(limit)
.lean();
if (dataList.length === 0) return;
const result = await Promise.allSettled(
dataList.map((item) => {
const text = item.q + (item.a || '');
const tokens = jiebaSplit({ text });
return MongoDatasetData.findByIdAndUpdate(item._id, {
$set: {
fullTextToken: tokens
}
});
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit, endDate);
} catch (error) {
await delay(1000);
return initFullTextToken(limit, endDate);
}
}

View File

@@ -1,62 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/common/string/jieba';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
console.log('total', await MongoDatasetData.countDocuments({ inited: { $exists: false } }));
await initFullTextToken(limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50): Promise<any> {
try {
const dataList = await MongoDatasetData.find({ inited: { $exists: false } }, '_id q a')
.limit(limit)
.lean();
if (dataList.length === 0) return;
const result = await Promise.allSettled(
dataList.map((item) => {
const text = item.q + (item.a || '');
const tokens = jiebaSplit({ text });
return MongoDatasetData.findByIdAndUpdate(item._id, {
$set: {
inited: true,
fullTextToken: tokens
}
});
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit);
} catch (error) {
await delay(1000);
return initFullTextToken(limit);
}
}

View File

@@ -1,109 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetStatusEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
await MongoDatasetCollection.updateMany({ createTime: { $exists: false } }, [
{
$set: {
createTime: '$updateTime'
}
}
]);
await MongoDatasetCollection.updateMany({ trainingType: { $exists: false } }, [
{
$set: {
trainingType: {
$cond: {
if: { $ifNull: ['$a', false] },
then: TrainingModeEnum.qa,
else: TrainingModeEnum.chunk
}
}
}
}
]);
await MongoDatasetCollection.updateMany({ chunkSize: { $exists: false } }, [
{
$set: {
chunkSize: 0
}
}
]);
await MongoDatasetCollection.updateMany({ fileId: { $exists: false } }, [
{
$set: {
fileId: '$metadata.fileId'
}
}
]);
await MongoDatasetCollection.updateMany({ rawLink: { $exists: false } }, [
{
$set: {
rawLink: '$metadata.rawLink'
}
}
]);
await MongoDatasetData.updateMany(
{ chunkIndex: { $exists: false } },
{
chunkIndex: 0
}
);
await MongoDatasetData.updateMany(
{ updateTime: { $exists: false } },
{
updateTime: new Date()
}
);
await MongoDataset.updateMany(
{ status: { $exists: false } },
{
$set: {
status: DatasetStatusEnum.active
}
}
);
// dataset tags to intro
await MongoDataset.updateMany({ tags: { $exists: true } }, [
{
$set: {
intro: {
$reduce: {
input: '$tags',
initialValue: '',
in: { $concat: ['$$value', ' ', '$$this'] }
}
}
}
}
]);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -0,0 +1,49 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { checkFiles } from '../timerTask/dataset/checkInValidDatasetFiles';
import { addHours } from 'date-fns';
import { checkInvalid as checkInvalidImg } from '../timerTask/dataset/checkInvalidDatasetImage';
import { checkInvalidCollection } from '../timerTask/dataset/checkInvalidMongoCollection';
import { checkInvalidVector } from '../timerTask/dataset/checkInvalidVector';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
await MongoPlugin.updateMany(
{ type: { $exists: false } },
{
$set: {
type: PluginTypeEnum.custom
}
}
);
await MongoPlugin.updateMany(
{ parentId: { $exists: false } },
{
$set: {
parentId: null
}
}
);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -30,8 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
})) || [],
whisperModel: global.whisperModel,
audioSpeechModels: global.audioSpeechModels,
systemVersion: global.systemVersion || '0.0.0',
simpleModeTemplates: global.simpleModeTemplates
systemVersion: global.systemVersion || '0.0.0'
}
});
}
@@ -43,7 +42,7 @@ const defaultFeConfigs: FastGPTFeConfigsType = {
openAPIDocUrl: 'https://doc.fastgpt.in/docs/development/openapi',
systemTitle: 'FastGPT',
concatMd:
'* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://doc.fastgpt.in/wechat-fastgpt.webp)',
'* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://oss.laf.run/htr4n1-images/fastgpt-qr-code.jpg)',
limit: {
exportDatasetLimitMinutes: 0,
websiteSyncLimitMinuted: 0
@@ -68,7 +67,6 @@ export async function getInitConfig() {
]);
console.log({
// simpleModeTemplates: global.simpleModeTemplates,
communityPlugins: global.communityPlugins
});
} catch (error) {
@@ -141,39 +139,6 @@ export function getSystemVersion() {
}
}
// async function getSimpleModeTemplates() {
// if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
// try {
// const basePath =
// process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
// // read data/simpleTemplates directory, get all json file
// const files = readdirSync(basePath);
// // filter json file
// const filterFiles = files.filter((item) => item.endsWith('.json'));
// // read json file
// const fileTemplates = filterFiles.map((item) => {
// const content = readFileSync(`${basePath}/${item}`, 'utf-8');
// return {
// id: item.replace('.json', ''),
// ...JSON.parse(content)
// };
// });
// // fetch templates from plus
// const plusTemplates = await getSimpleTemplatesFromPlus();
// global.simpleModeTemplates = [
// SimpleModeTemplate_FastGPT_Universal,
// ...plusTemplates,
// ...fileTemplates
// ];
// } catch (error) {
// global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
// }
// }
function getSystemPlugin() {
if (global.communityPlugins && global.communityPlugins.length > 0) return;

View File

@@ -5,7 +5,6 @@ import type { CreateAppParams } from '@fastgpt/global/core/app/api.d';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -35,8 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
teamId,
tmbId,
modules,
type,
simpleTemplateId: SimpleModeTemplate_FastGPT_Universal.id
type
});
jsonRes(res, {

View File

@@ -1,620 +0,0 @@
/*
universal mode.
@author: FastGpt Team
*/
import type { NextApiRequest, NextApiResponse } from 'next';
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 { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData, chatModelMaxToken } = req.body as FormatForm2ModulesProps;
const modules = [
...(formData.dataset.datasets.length > 0
? datasetTemplate({ formData, maxToken: chatModelMaxToken })
: simpleChatTemplate({ formData, maxToken: chatModelMaxToken }))
];
jsonRes<ModuleItemType[]>(res, {
data: modules
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
type Props = { formData: AppSimpleEditFormType; maxToken: number };
function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 981.9682828103937,
y: 890.014595014464
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 1,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 8,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'datasetSearch',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
type: 'hidden',
label: '最低相关性',
value: 0.15,
valueType: 'number',
min: 0,
max: 1,
step: 0.01,
markList: [
{
label: '0',
value: 0
},
{
label: '1',
value: 1
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: 2000,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'searchMode',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: DatasetSearchModeEnum.mixedRecall,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'isEmpty',
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: []
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: []
},
{
key: 'quoteQA',
label: '引用内容',
description:
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
type: 'source',
valueType: 'datasetQuote',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 0,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
return modules;
}

View File

@@ -1,721 +0,0 @@
/*
universal mode.
@author: FastGpt Team
*/
import type { NextApiRequest, NextApiResponse } from 'next';
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 { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData } = req.body as FormatForm2ModulesProps;
const modules =
formData.dataset.datasets.length > 0
? datasetTemplate(formData)
: simpleChatTemplate(formData);
jsonRes<ModuleItemType[]>(res, {
data: modules
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 981.9682828103937,
y: 890.014595014464
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'datasetSearch',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
type: 'hidden',
label: '最低相关性',
value: formData.dataset.similarity,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: formData.dataset.limit,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'searchMode',
type: 'hidden',
label: 'core.dataset.search.Mode',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.searchMode,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.usingReRank,
connected: false
},
{
key: 'datasetSearchUsingExtensionQuery',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchUsingExtensionQuery,
connected: false
},
{
key: 'datasetSearchExtensionBg',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionBg,
connected: false
},
{
key: 'datasetSearchExtensionModel',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionModel,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'isEmpty',
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: '6dtsvu',
key: 'switch'
}
]
: []
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: 'chatModule',
key: 'switch'
}
]
: []
},
{
key: 'quoteQA',
label: '引用内容',
description:
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
type: 'source',
valueType: 'datasetQuote',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: !!formData.dataset?.searchEmptyText
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
if (formData.dataset?.searchEmptyText) {
modules.push({
moduleId: '6dtsvu',
name: '指定回复',
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 2018.2744321961648,
y: 616.1220817209096
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'text',
type: 'textarea',
value: formData.dataset.searchEmptyText,
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
}
],
outputs: [
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
});
}
return modules;
}

View File

@@ -12,7 +12,7 @@ import { getLLMModel } from '@fastgpt/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
const { name, avatar, type, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -29,7 +29,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
let maxTokens = 3000;
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.chatNode) {
if (
item.flowType === FlowNodeTypeEnum.chatNode ||
item.flowType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
@@ -61,7 +64,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,

View File

@@ -12,7 +12,7 @@ import { getLLMModel } from '@fastgpt/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
const { name, avatar, type, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -61,7 +61,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,

View File

@@ -8,10 +8,10 @@ import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import type { ChatItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { dispatchWorkFlow } from '@/service/moduleDispatch';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { setEntryEntries } from '@/service/moduleDispatch/utils';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatch/utils';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
export type Props = {

View File

@@ -7,8 +7,17 @@ import { autChatCrud } from '@/service/support/permission/auth/chat';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { appId, chatId, chatItemId, shareId, outLinkUid, userBadFeedback, userGoodFeedback } =
req.body as UpdateChatFeedbackProps;
const {
appId,
chatId,
chatItemId,
shareId,
teamId,
teamToken,
outLinkUid,
userBadFeedback,
userGoodFeedback
} = req.body as UpdateChatFeedbackProps;
try {
await connectToDatabase();
@@ -17,6 +26,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
req,
authToken: true,
appId,
teamId,
teamToken,
chatId,
shareId,
outLinkUid,

View File

@@ -9,12 +9,13 @@ import { autChatCrud } from '@/service/support/permission/auth/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, chatId, shareId, outLinkUid, customTitle, top } = req.body as UpdateHistoryProps;
const { appId, chatId, teamId, shareId, outLinkUid, customTitle, top } =
req.body as UpdateHistoryProps;
await autChatCrud({
req,
authToken: true,
appId,
teamId,
chatId,
shareId,
outLinkUid,

View File

@@ -14,7 +14,7 @@ import {
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
@@ -83,7 +83,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
]);
// 4. push chunks to training queue
const insertResults = await pushDataToTrainingQueue({
const insertResults = await pushDataListToTrainingQueue({
teamId,
tmbId,
collectionId,

View File

@@ -23,11 +23,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps;
if (!q) {
return Promise.reject('q is required');
throw new Error('q is required');
}
if (!collectionId) {
return Promise.reject('collectionId is required');
throw new Error('collectionId is required');
}
// 凭证校验

View File

@@ -10,7 +10,7 @@ import type {
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -41,7 +41,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
jsonRes<PushDatasetDataResponse>(res, {
data: await pushDataToTrainingQueue({
data: await pushDataListToTrainingQueue({
...req.body,
teamId,
tmbId

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authDatasetFile } from '@fastgpt/service/support/permission/auth/dataset';
import { createFileToken } from '@fastgpt/service/support/permission/controller';
import { BucketNameEnum, FileBaseUrl } from '@fastgpt/global/common/file/constants';
import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
jsonRes(res, {
data: `${FileBaseUrl}?token=${token}`
data: `${ReadFileBaseUrl}?token=${token}`
});
} catch (error) {
jsonRes(res, {

View File

@@ -5,7 +5,7 @@ import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/
import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { searchDatasetData } from '@fastgpt/service/core/dataset/search/controller';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { getLLMModel } from '@fastgpt/service/core/ai/model';

View File

@@ -5,6 +5,8 @@ import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controll
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { checkTeamPluginLimit } from '@fastgpt/service/support/permission/teamLimit';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -12,17 +14,58 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
const body = req.body as CreateOnePluginParams;
await checkTeamPluginLimit(teamId);
// await checkTeamPluginLimit(teamId);
const { _id } = await MongoPlugin.create({
...body,
teamId,
tmbId
});
// create parent plugin and child plugin
if (body.metadata?.apiSchemaStr) {
const parentId = await mongoSessionRun(async (session) => {
const [{ _id: parentId }] = await MongoPlugin.create(
[
{
...body,
parentId: null,
teamId,
tmbId
}
],
{ session }
);
jsonRes(res, {
data: _id
});
const childrenPlugins = httpApiSchema2Plugins({
parentId,
apiSchemaStr: body.metadata?.apiSchemaStr,
customHeader: body.metadata?.customHeaders
});
await MongoPlugin.create(
childrenPlugins.map((item) => ({
...item,
metadata: {
pluginUid: item.name
},
teamId,
tmbId
})),
{
session
}
);
return parentId;
});
jsonRes(res, {
data: parentId
});
} else {
const { _id } = await MongoPlugin.create({
...body,
teamId,
tmbId
});
jsonRes(res, {
data: _id
});
}
} catch (err) {
jsonRes(res, {
code: 500,

View File

@@ -3,14 +3,32 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as { id: string };
await connectToDatabase();
await authPluginCrud({ req, authToken: true, id, per: 'owner' });
const { teamId } = await authUserNotVisitor({ req, authToken: true });
const { pluginId } = req.query as { pluginId: string };
await MongoPlugin.findByIdAndRemove(id);
if (!pluginId) {
throw new Error('缺少参数');
}
await authPluginCrud({ req, authToken: true, id: pluginId, per: 'owner' });
await mongoSessionRun(async (session) => {
await MongoPlugin.deleteMany(
{
teamId,
parentId: pluginId
},
{
session
}
);
await MongoPlugin.findByIdAndDelete(pluginId, { session });
});
jsonRes(res, {});
} catch (err) {

View File

@@ -6,7 +6,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getPluginPreviewModule } from '@fastgpt/service/core/plugin/controller';
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authCert({ req, authToken: true });
await authPluginCanUse({ id, teamId, tmbId });
jsonRes<FlowModuleTemplateType>(res, {
jsonRes<FlowNodeTemplateType>(res, {
data: await getPluginPreviewModule({ id })
});
} catch (err) {

View File

@@ -0,0 +1,20 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import * as SwaggerParser from '@apidevtools/swagger-parser';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const apiURL = req.body.url as string;
const api = await (SwaggerParser as any).validate(apiURL);
return jsonRes(res, {
data: api
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,16 +1,31 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { PluginListItemType } from '@fastgpt/global/core/plugin/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` };
const { teamId } = await authCert({ req, authToken: true });
jsonRes(res, {
data: await MongoPlugin.find({ teamId })
const plugins = await MongoPlugin.find(
{
teamId,
...(parentId !== undefined && { parentId: parentId || null }),
...(type && { type })
},
'_id parentId type name avatar intro metadata'
)
.sort({ updateTime: -1 })
.lean();
jsonRes<PluginListItemType[]>(res, {
data: plugins
});
} catch (err) {
jsonRes(res, {

View File

@@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type.d';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId } = req.query as { parentId: string };
if (!parentId) {
return jsonRes(res, {
data: []
});
}
await authCert({ req, authToken: true });
jsonRes<ParentTreePathItemType[]>(res, {
data: await getParents(parentId)
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
async function getParents(parentId?: string): Promise<ParentTreePathItemType[]> {
if (!parentId) {
return [];
}
const parent = await MongoPlugin.findById(parentId, 'name parentId');
if (!parent) return [];
const paths = await getParents(parent.parentId);
paths.push({ parentId, parentName: parent.name });
return paths;
}

View File

@@ -0,0 +1,37 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
await authCert({ req, authToken: true });
const data: FlowNodeTemplateType[] =
global.communityPlugins?.map((plugin) => ({
id: plugin.id,
templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: true,
isTool: plugin.isTool,
inputs: [],
outputs: []
})) || [];
jsonRes<FlowNodeTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,62 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId, searchKey } = req.query as { parentId?: string; searchKey?: string };
const { teamId } = await authCert({ req, authToken: true });
const userPlugins = await (async () => {
if (searchKey) {
return MongoPlugin.find({
teamId,
// search name or intro
$or: [
{ name: { $regex: searchKey, $options: 'i' } },
{ intro: { $regex: searchKey, $options: 'i' } }
]
})
.sort({
updateTime: -1
})
.lean();
} else {
return MongoPlugin.find({ teamId, parentId: parentId || null })
.sort({
updateTime: -1
})
.lean();
}
})();
const data: FlowNodeTemplateType[] = userPlugins.map((plugin) => ({
id: String(plugin._id),
parentId: String(plugin.parentId),
pluginType: plugin.type,
templateType: FlowNodeTemplateTypeEnum.personalPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: false,
inputs: [],
outputs: []
}));
jsonRes<FlowNodeTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,57 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
import { GET } from '@fastgpt/service/common/api/plusRequest';
import type { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { teamId } = await authCert({ req, authToken: true });
const [userPlugins, plusPlugins] = await Promise.all([
MongoPlugin.find({ teamId }).lean(),
FastGPTProUrl ? GET<PluginTemplateType[]>('/core/plugin/getTemplates') : []
]);
const data: FlowModuleTemplateType[] = [
...userPlugins.map((plugin) => ({
id: String(plugin._id),
templateType: ModuleTemplateTypeEnum.personalPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: false,
inputs: [],
outputs: []
})),
...(global.communityPlugins?.map((plugin) => ({
id: plugin.id,
templateType: plugin.templateType ?? ModuleTemplateTypeEnum.other,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: true,
inputs: [],
outputs: []
})) || [])
];
jsonRes<FlowModuleTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -4,17 +4,37 @@ import { connectToDatabase } from '@/service/mongo';
import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { ClientSession } from '@fastgpt/service/common/mongo';
import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id, ...props } = req.body as UpdatePluginParams;
const body = req.body as UpdatePluginParams;
await authPluginCrud({ req, authToken: true, id, per: 'owner' });
const { id, ...props } = body;
jsonRes(res, {
data: await MongoPlugin.findByIdAndUpdate(id, props)
});
const { teamId, tmbId } = await authPluginCrud({ req, authToken: true, id, per: 'owner' });
if (props.metadata?.apiSchemaStr) {
await mongoSessionRun(async (session) => {
// update children
await updateHttpChildrenPlugin({
teamId,
tmbId,
parent: body,
session
});
await MongoPlugin.findByIdAndUpdate(id, props, { session });
});
jsonRes(res, {});
} else {
jsonRes(res, {
data: await MongoPlugin.findByIdAndUpdate(id, props)
});
}
} catch (err) {
jsonRes(res, {
code: 500,
@@ -22,3 +42,64 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}
const updateHttpChildrenPlugin = async ({
teamId,
tmbId,
parent,
session
}: {
teamId: string;
tmbId: string;
parent: UpdatePluginParams;
session: ClientSession;
}) => {
if (!parent.metadata?.apiSchemaStr) return;
const dbPlugins = await MongoPlugin.find(
{
parentId: parent.id,
teamId
},
'_id metadata'
);
const schemaPlugins = httpApiSchema2Plugins({
parentId: parent.id,
apiSchemaStr: parent.metadata?.apiSchemaStr,
customHeader: parent.metadata?.customHeaders
});
// 数据库中存在schema不存在删除
for await (const plugin of dbPlugins) {
if (!schemaPlugins.find((p) => p.name === plugin.metadata?.pluginUid)) {
await MongoPlugin.deleteOne({ _id: plugin._id }, { session });
}
}
// 数据库中不存在schema存在新增
for await (const plugin of schemaPlugins) {
if (!dbPlugins.find((p) => p.metadata?.pluginUid === plugin.name)) {
await MongoPlugin.create(
[
{
...plugin,
metadata: {
pluginUid: plugin.name
},
teamId,
tmbId
}
],
{
session
}
);
}
}
// 数据库中存在schema存在更新
for await (const plugin of schemaPlugins) {
const dbPlugin = dbPlugins.find((p) => plugin.name === p.metadata?.pluginUid);
if (dbPlugin) {
await MongoPlugin.findByIdAndUpdate(dbPlugin._id, plugin, { session });
}
}
};

View File

@@ -6,7 +6,7 @@ import { addLog } from '@fastgpt/service/common/system/log';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
import { dispatchWorkFlow } from '@/service/moduleDispatch';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
@@ -32,7 +32,7 @@ import { AuthOutLinkChatProps } from '@fastgpt/global/support/outLink/api';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { setEntryEntries } from '@/service/moduleDispatch/utils';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatch/utils';
import { UserChatItemType } from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';

View File

@@ -17,6 +17,7 @@ import { flowNode2Modules, filterExportModules } from '@/components/core/module/
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { getErrText } from '@fastgpt/global/common/error/utils';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
@@ -43,6 +44,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { updateAppDetail } = useAppStore();
const { nodes, edges, splitToolInputs } = useFlowProviderStore();
const [isSaving, setIsSaving] = useState(false);
const flow2ModulesAndCheck = useCallback(async () => {
const modules = flowNode2Modules({ nodes, edges });
@@ -75,20 +77,45 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
return modules;
}, [edges, nodes, splitToolInputs, t, toast]);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: async (modules: ModuleItemType[]) => {
return updateAppDetail(app._id, {
modules: modules,
type: AppTypeEnum.advanced,
permission: undefined
});
const onclickSave = useCallback(
async (modules: ModuleItemType[]) => {
setIsSaving(true);
try {
await updateAppDetail(app._id, {
modules: modules,
type: AppTypeEnum.advanced,
permission: undefined
});
toast({
status: 'success',
title: t('common.Save Success')
});
ChatTestRef.current?.resetChatTest();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error, t('common.Save Failed'))
});
}
setIsSaving(false);
},
successToast: t('common.Save Success'),
errorToast: t('common.Save Failed'),
onSuccess() {
ChatTestRef.current?.resetChatTest();
[ChatTestRef, app._id, t, toast, updateAppDetail]
);
const saveAndBack = useCallback(async () => {
try {
const modules = await flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
}
onClose();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error)
});
}
});
}, [flow2ModulesAndCheck, onClose, onclickSave, toast]);
return (
<>
@@ -109,13 +136,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
borderColor={'myGray.300'}
variant={'whiteBase'}
aria-label={''}
onClick={openConfirmOut(async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
}
onClose();
}, onClose)}
isLoading={isSaving}
onClick={openConfirmOut(saveAndBack, onClose)}
/>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
@@ -178,7 +200,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<IconButton
icon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
size={'smSquare'}
isLoading={isLoading}
isLoading={isSaving}
aria-label={'save'}
onClick={async () => {
const modules = await flow2ModulesAndCheck();

View File

@@ -3,22 +3,25 @@ import { AppSchema } from '@fastgpt/global/core/app/type.d';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import type { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import type { FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import { appSystemModuleTemplates } from '@fastgpt/global/core/module/template/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { usePluginStore } from '@/web/core/plugin/store/plugin';
import { useQuery } from '@tanstack/react-query';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
type Props = { app: AppSchema; onClose: () => void };
const Render = ({ app, onClose }: Props) => {
const { nodes, initData } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const { setBasicNodeTemplates } = useWorkflowStore();
const moduleTemplates = useMemo(() => {
const concatTemplates = [...appSystemModuleTemplates, ...pluginModuleTemplates];
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
useEffect(() => {
const concatTemplates = [...appSystemModuleTemplates];
const copyTemplates: FlowNodeTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1
@@ -35,18 +38,12 @@ const Render = ({ app, onClose }: Props) => {
}
});
return copyTemplates;
}, [nodes, pluginModuleTemplates]);
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
setBasicNodeTemplates(copyTemplates);
}, [nodes, setBasicNodeTemplates]);
const memoRender = useMemo(() => {
return <Flow templates={moduleTemplates} Header={<Header app={app} onClose={onClose} />} />;
}, [app, moduleTemplates.length, onClose]);
return <Flow Header={<Header app={app} onClose={onClose} />} />;
}, [app, onClose]);
return memoRender;
};

View File

@@ -18,7 +18,7 @@ import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest } from '@/web/common/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionRadio from '@/components/support/permission/Radio';
import { useTranslation } from 'next-i18next';

View File

@@ -26,7 +26,7 @@ import type { ComponentRef } from '@/components/ChatBox/type.d';
import { useQuery } from '@tanstack/react-query';
import { getInitChatInfo } from '@/web/core/chat/api';
import Tag from '@/components/Tag';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { addDays } from 'date-fns';
import MyBox from '@/components/common/MyBox';
import { usePagination } from '@fastgpt/web/hooks/usePagination';

View File

@@ -1,6 +1,6 @@
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import React, { useCallback, useState } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Flex, FlexProps, Grid, Image, ModalBody, Switch, useTheme } from '@chakra-ui/react';
import MyRadio from '@/components/common/MyRadio';

View File

@@ -37,7 +37,7 @@ import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import dayjs from 'dayjs';
import { getDocPath } from '@/web/common/system/doc';
import dynamic from 'next/dynamic';

View File

@@ -27,7 +27,8 @@ const AppCard = ({ appId }: { appId: string }) => {
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
content: t('app.Confirm Del App Tip')
content: t('app.Confirm Del App Tip'),
type: 'delete'
});
/* 点击删除 */

View File

@@ -1,16 +1,7 @@
import React, { useMemo, useState, useTransition } from 'react';
import {
Box,
Flex,
Grid,
BoxProps,
useTheme,
useDisclosure,
Button,
Image
} from '@chakra-ui/react';
import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useForm, useFieldArray } from 'react-hook-form';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { appModules2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
@@ -34,11 +25,13 @@ import MyTextarea from '@/components/common/Textarea/MyTextarea/index';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/module/utils';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
import SelectAiModel from '@/components/Select/SelectAiModel';
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
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 ToolSelectModal = dynamic(() => import('./ToolSelectModal'));
const TTSSelect = dynamic(
() => import('@/components/core/module/Flow/components/modules/TTSSelect')
);
@@ -56,7 +49,7 @@ const EditForm = ({
const { t } = useTranslation();
const { appDetail, updateAppDetail } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc, llmModelList, reRankModelList } = useSystemStore();
const { isPc, llmModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const [, startTst] = useTransition();
@@ -69,12 +62,8 @@ const EditForm = ({
control,
name: 'dataset.datasets'
});
const selectedTools = watch('selectedTools');
const {
isOpen: isOpenAIChatSetting,
onOpen: onOpenAIChatSetting,
onClose: onCloseAIChatSetting
} = useDisclosure();
const {
isOpen: isOpenDatasetSelect,
onOpen: onOpenKbSelect,
@@ -85,6 +74,11 @@ const EditForm = ({
onOpen: onOpenDatasetParams,
onClose: onCloseDatasetParams
} = useDisclosure();
const {
isOpen: isOpenToolsSelect,
onOpen: onOpenToolsSelect,
onClose: onCloseToolsSelect
} = useDisclosure();
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('core.app.edit.Confirm Save App Tip')
@@ -97,12 +91,6 @@ const EditForm = ({
const formatVariables = useMemo(() => formatEditorVariablePickerIcon(variables), [variables]);
const searchMode = watch('dataset.searchMode');
const chatModelSelectList = (() =>
llmModelList.map((item) => ({
value: item.model,
label: item.name
})))();
const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
[allDatasets, datasets]
@@ -112,6 +100,7 @@ const EditForm = ({
return llmModelList.find((item) => item.model === selectLLMModel)?.quoteMaxToken || 3000;
}, [selectLLMModel, llmModelList]);
/* on save app */
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data);
@@ -126,7 +115,7 @@ const EditForm = ({
errorToast: t('common.Save Failed')
});
const { isSuccess: isInitd } = useQuery(
useQuery(
['init', appDetail],
() => {
const formatVal = appModules2Form({
@@ -148,15 +137,6 @@ const EditForm = ({
borderBottomWidth: '1px',
borderBottomColor: 'borderColor.low'
};
const BoxBtnStyles: BoxProps = {
cursor: 'pointer',
px: 3,
py: 1,
borderRadius: 'md',
_hover: {
bg: 'myGray.150'
}
};
const LabelStyles: BoxProps = {
w: ['60px', '100px'],
flexShrink: 0,
@@ -207,29 +187,6 @@ const EditForm = ({
<Box px={4}>
<Box bg={'white'} borderRadius={'md'} borderWidth={'1px'} borderColor={'borderColor.base'}>
{/* simple mode select */}
{/* <Flex {...BoxStyles}>
<Flex alignItems={'center'} flex={'1 0 0'}>
<MyIcon name={'core/app/simpleMode/template'} w={'20px'} />
<Box mx={2}>{t('core.app.simple.mode template select')}</Box>
</Flex>
<MySelect
w={['200px', '250px']}
list={
simpleModeTemplates?.map((item) => ({
alias: t(item.name),
label: t(item.desc),
value: item.id
})) || []
}
value={getValues('templateId')}
onchange={(val) => {
setValue('templateId', val);
setRefresh(!refresh);
}}
/>
</Flex> */}
{/* ai */}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
@@ -237,39 +194,36 @@ const EditForm = ({
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'common/settingLight'} w={'14px'} />
{t('common.More settings')}
</Flex>
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<SelectAiModel
width={'100%'}
value={getValues(`aiSettings.model`)}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('aiSettings.model', val);
const maxToken =
llmModelList.find((item) => item.model === getValues('aiSettings.model'))
?.maxResponse || 4000;
const token = maxToken / 2;
setValue('aiSettings.maxToken', token);
setRefresh(!refresh);
<SettingLLMModel
llmModelType={'all'}
defaultData={{
model: getValues('aiSettings.model'),
temperature: getValues('aiSettings.temperature'),
maxToken: getValues('aiSettings.maxToken'),
maxHistories: getValues('aiSettings.maxHistories')
}}
onChange={({ model, temperature, maxToken, maxHistories }: SettingAIDataType) => {
setValue('aiSettings.model', model);
setValue('aiSettings.maxToken', maxToken);
setValue('aiSettings.temperature', temperature);
setValue('aiSettings.maxHistories', maxHistories ?? 6);
}}
/>
</Box>
</Flex>
<Flex mt={10} alignItems={'flex-start'}>
<Box mt={3}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={t(chatNodeSystemPromptTip)} forceShow>
<MyTooltip label={t('core.app.tip.chatNodeSystemPromptTip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
{isInitd && (
<Box mt={1}>
<PromptEditor
value={aiSystemPrompt}
onChange={(text) => {
@@ -281,8 +235,8 @@ const EditForm = ({
placeholder={t('core.app.tip.chatNodeSystemPromptTip')}
title={t('core.ai.Prompt')}
/>
)}
</Flex>
</Box>
</Box>
</Box>
{/* dataset */}
@@ -292,14 +246,26 @@ const EditForm = ({
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
<Button
variant={'transparentBase'}
leftIcon={<AddIcon fontSize={'xs'} />}
iconSpacing={1}
size={'sm'}
fontSize={'md'}
onClick={onOpenKbSelect}
>
{t('common.Choose')}
</Flex>
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenDatasetParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
</Button>
<Button
variant={'transparentBase'}
leftIcon={<MyIcon name={'edit'} w={'14px'} />}
iconSpacing={1}
size={'sm'}
fontSize={'md'}
onClick={onOpenDatasetParams}
>
{t('common.Params')}
</Flex>
</Button>
</Flex>
{getValues('dataset.datasets').length > 0 && (
<Box my={3}>
@@ -309,7 +275,6 @@ const EditForm = ({
limit={getValues('dataset.limit')}
usingReRank={getValues('dataset.usingReRank')}
usingQueryExtension={getValues('dataset.datasetSearchUsingExtensionQuery')}
responseEmptyText={getValues('dataset.searchEmptyText')}
/>
</Box>
)}
@@ -347,6 +312,65 @@ const EditForm = ({
</Grid>
</Box>
{/* tool choice */}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<MyIcon name={'core/app/toolCall'} w={'20px'} />
<Box ml={2}>{t('core.app.Tool call')}()</Box>
<MyTooltip label={t('core.app.Tool call tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Button
variant={'transparentBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
mr={'-5px'}
size={'sm'}
fontSize={'md'}
onClick={onOpenToolsSelect}
>
{t('common.Choose')}
</Button>
</Flex>
<Grid
mt={selectedTools.length > 0 ? 2 : 0}
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
gridGap={[2, 4]}
>
{selectedTools.map((item) => (
<Flex
key={item.id}
overflow={'hidden'}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
_hover={{
...hoverDeleteStyles,
borderColor: 'primary.300'
}}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
<DeleteIcon
onClick={() => {
setValue(
'selectedTools',
selectedTools.filter((tool) => tool.id !== item.id)
);
}}
/>
</Flex>
))}
</Grid>
</Box>
{/* variable */}
<Box {...BoxStyles}>
<VariableEdit
@@ -406,17 +430,6 @@ const EditForm = ({
</Box>
<ConfirmSaveModal bg={appDetail.type === AppTypeEnum.simple ? '' : 'red.600'} countDown={5} />
{isOpenAIChatSetting && (
<AIChatSettingsModal
onClose={onCloseAIChatSetting}
onSuccess={(e) => {
setValue('aiSettings', e);
onCloseAIChatSetting();
}}
defaultData={getValues('aiSettings')}
pickerMenu={formatVariables}
/>
)}
{isOpenDatasetSelect && (
<DatasetSelectModal
isOpen={isOpenDatasetSelect}
@@ -443,6 +456,19 @@ const EditForm = ({
}}
/>
)}
{isOpenToolsSelect && (
<ToolSelectModal
selectedTools={selectedTools}
onAddTool={(e) => setValue('selectedTools', [...selectedTools, e])}
onRemoveTool={(e) => {
setValue(
'selectedTools',
selectedTools.filter((item) => item.id !== e.id)
);
}}
onClose={onCloseToolsSelect}
/>
)}
</Box>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import {
Button,

View File

@@ -0,0 +1,270 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import {
Box,
Button,
Flex,
Input,
InputGroup,
InputLeftElement,
ModalBody
} from '@chakra-ui/react';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useQuery } from '@tanstack/react-query';
import EmptyTip from '@/components/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { AddIcon } from '@chakra-ui/icons';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import MyBox from '@/components/common/MyBox';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import ParentPaths from '@/components/common/ParentPaths';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { debounce } from 'lodash';
type Props = {
selectedTools: FlowNodeTemplateType[];
onAddTool: (tool: FlowNodeTemplateType) => void;
onRemoveTool: (tool: FlowNodeTemplateType) => void;
};
enum TemplateTypeEnum {
'systemPlugin' = 'systemPlugin',
'teamPlugin' = 'teamPlugin'
}
const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => {
const { t } = useTranslation();
const {
systemNodeTemplates,
loadSystemNodeTemplates,
teamPluginNodeTemplates,
loadTeamPluginNodeTemplates
} = useWorkflowStore();
const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin);
const [currentParent, setCurrentParent] = useState<{
parentId: string;
parentName: string;
}>();
const [searchKey, setSearchKey] = useState('');
const templates = useMemo(() => {
const map = {
[TemplateTypeEnum.systemPlugin]: systemNodeTemplates.filter(
(item) => item.isTool && item.name.toLowerCase().includes(searchKey.toLowerCase())
),
[TemplateTypeEnum.teamPlugin]: teamPluginNodeTemplates.filter((item) =>
searchKey ? item.pluginType !== PluginTypeEnum.folder : true
)
};
return map[templateType];
}, [searchKey, systemNodeTemplates, teamPluginNodeTemplates, templateType]);
const { mutate: onChangeTab } = useRequest({
mutationFn: async (e: any) => {
const val = e as TemplateTypeEnum;
if (val === TemplateTypeEnum.systemPlugin) {
await loadSystemNodeTemplates();
} else if (val === TemplateTypeEnum.teamPlugin) {
await loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId
});
}
setTemplateType(val);
},
errorToast: t('core.module.templates.Load plugin error')
});
const { isLoading } = useQuery(['teamNodeTemplate', currentParent?.parentId, searchKey], () =>
loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId,
searchKey,
init: true
})
);
return (
<MyModal
isOpen
title={t('core.app.Tool call')}
iconSrc="core/app/toolCall"
onClose={onClose}
maxW={['90vw', '700px']}
w={'700px'}
h={['90vh', '80vh']}
overflow={'none'}
>
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
<RowTabs
list={[
{
icon: 'core/modules/teamPlugin',
label: t('core.app.ToolCall.Team'),
value: TemplateTypeEnum.teamPlugin
},
{
icon: 'core/modules/systemPlugin',
label: t('core.app.ToolCall.System'),
value: TemplateTypeEnum.systemPlugin
}
]}
py={'5px'}
px={'15px'}
value={templateType}
onChange={onChangeTab}
/>
<InputGroup w={300}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
</Box>
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex mt={2} px={[3, 6]}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
)}
<MyBox isLoading={isLoading} mt={2} px={[3, 6]} pb={3} flex={'1 0 0'} overflowY={'auto'}>
<RenderList
templates={templates}
isLoadingData={isLoading}
setCurrentParent={setCurrentParent}
{...props}
/>
</MyBox>
</MyModal>
);
};
export default React.memo(ToolSelectModal);
const RenderList = React.memo(function RenderList({
templates,
selectedTools,
isLoadingData,
onAddTool,
onRemoveTool,
setCurrentParent
}: Props & {
templates: FlowNodeTemplateType[];
isLoadingData: boolean;
setCurrentParent: (e: { parentId: string; parentName: string }) => void;
}) {
const { t } = useTranslation();
const { toast } = useToast();
const { mutate: onClickAdd, isLoading } = useRequest({
mutationFn: async (template: FlowNodeTemplateType) => {
const res = await getPreviewPluginModule(template.id);
// check inputs valid
for (const input of res.inputs) {
if (
[
ModuleInputKeyEnum.switch,
ModuleInputKeyEnum.pluginStart,
ModuleInputKeyEnum.pluginId
].includes(input.key as any)
) {
continue;
}
if (!input.toolDescription) {
return toast({
status: 'warning',
title: t('core.app.ToolCall.This plugin cannot be called as a tool')
});
}
}
return res;
},
onSuccess(res: FlowNodeTemplateType) {
res && onAddTool(res);
},
errorToast: t('core.module.templates.Load plugin error')
});
return templates.length === 0 && !isLoadingData ? (
<EmptyTip text={t('core.app.ToolCall.No plugin')} />
) : (
<MyBox isLoading={isLoading}>
{templates.map((item, i) => {
const selected = !!selectedTools.find((tool) => tool.id === item.id);
return (
<Flex
key={item.id}
alignItems={'center'}
p={[4, 5]}
_notLast={{
borderBottomWidth: '1px',
borderBottomColor: 'myGray.150'
}}
_hover={{
bg: 'myGray.50'
}}
>
<Avatar
src={item.avatar}
w={['26px', '32px']}
objectFit={'contain'}
borderRadius={'0'}
/>
<Box ml={5} flex={'1 0 0'}>
<Box color={'black'}>{t(item.name)}</Box>
<Box className="textEllipsis3" color={'myGray.500'} fontSize={['xs', 'sm']}>
{t(item.intro)}
</Box>
</Box>
{selected ? (
<Button
size={'sm'}
variant={'grayDanger'}
leftIcon={<MyIcon name={'delete'} w={'14px'} />}
onClick={() => onRemoveTool(item)}
>
{t('common.Remove')}
</Button>
) : item.pluginType === PluginTypeEnum.folder ? (
<Button
size={'sm'}
variant={'whiteBase'}
onClick={() => setCurrentParent({ parentId: item.id, parentName: item.name })}
>
{t('common.Open')}
</Button>
) : (
<Button
size={'sm'}
variant={'whiteBase'}
leftIcon={<AddIcon fontSize={'10px'} />}
onClick={() => onClickAdd(item)}
>
{t('common.Add')}
</Button>
)}
</Flex>
);
})}
</MyBox>
);
});

View File

@@ -12,7 +12,7 @@ const SimpleEdit = ({ appId }: { appId: string }) => {
const { parentRef, divRef, isSticky } = useSticky();
return (
<Grid gridTemplateColumns={['1fr', '550px 1fr']} h={'100%'}>
<Grid gridTemplateColumns={['1fr', '560px 1fr']} h={'100%'}>
<Box
ref={parentRef}
h={'100%'}

View File

@@ -43,14 +43,14 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const setCurrentTab = useCallback(
(tab: `${TabEnum}`) => {
router.replace({
router.push({
query: {
appId,
...router.query,
currentTab: tab
}
});
},
[appId, router]
[router]
);
const tabList = useMemo(

View File

@@ -26,7 +26,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';

View File

@@ -36,19 +36,23 @@ import MyBox from '@/components/common/MyBox';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
const OutLink = ({
teamId,
appId,
chatId,
teamToken
}: {
teamId: string;
appId: string;
chatId: string;
teamToken: string;
}) => {
const OutLink = () => {
const { t } = useTranslation();
const router = useRouter();
const {
teamId = '',
appId = '',
chatId = '',
teamToken,
...customVariables
} = router.query as {
teamId: string;
appId: string;
chatId: string;
teamToken: string;
[key: string]: string;
};
const { toast } = useToast();
const theme = useTheme();
const { isPc } = useSystemStore();
@@ -78,7 +82,10 @@ const OutLink = ({
const { responseText, responseData } = await streamFetch({
data: {
messages: prompts,
variables,
variables: {
...customVariables,
...variables
},
appId,
teamId,
teamToken,
@@ -372,17 +379,8 @@ const OutLink = ({
};
export async function getServerSideProps(context: any) {
const teamId = context?.query?.teamId || '';
const appId = context?.query?.appId || '';
const chatId = context?.query?.chatId || '';
const teamToken: string = context?.query?.teamToken || '';
return {
props: {
teamId,
appId,
chatId,
teamToken,
...(await serviceSideProps(context))
}
};

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useRef, useState } from 'react';
import { ModalFooter, ModalBody, Input, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useRequest } from '@/web/common/hooks/useRequest';

View File

@@ -4,7 +4,7 @@ import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import { DatasetItemType } from '@fastgpt/global/core/dataset/type';
import { Prompt_AgentQA } from '@/global/core/prompt/agent';
import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent';
import { UseFormReturn, useForm } from 'react-hook-form';
import { ImportProcessWayEnum } from '@/web/core/dataset/constants';
import { ImportSourceItemType } from '@/web/core/dataset/type';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Button, Input, Link, ModalBody, ModalFooter } from '@chakra-ui/react';
import { strIsLink } from '@fastgpt/global/common/string/tools';

View File

@@ -23,8 +23,8 @@ import MyTooltip from '@/components/MyTooltip';
import { useImportStore } from '../Provider';
import Tag from '@/components/Tag';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyModal from '@/components/MyModal';
import { Prompt_AgentQA } from '@/global/core/prompt/agent';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent';
import Preview from '../components/Preview';
function DataProcess({

View File

@@ -13,7 +13,6 @@ const PreviewData = ({
}) => {
const { t } = useTranslation();
const { sources, setSources } = useImportStore();
console.log(sources);
return (
<Flex flexDirection={'column'} h={'100%'} maxW={'1080px'}>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { ModalBody } from '@chakra-ui/react';
export type PreviewRawTextProps = {

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';

View File

@@ -17,6 +17,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import MySelect from '@fastgpt/web/components/common/MySelect';
import AIModelSelector from '@/components/Select/AIModelSelector';
const Info = ({ datasetId }: { datasetId: string }) => {
const { t } = useTranslation();
@@ -137,7 +138,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
{t('core.ai.model.Dataset Agent Model')}
</Box>
<Box flex={[1, '0 0 300px']}>
<MySelect
<AIModelSelector
w={'100%'}
value={getValues('agentModel').model}
list={datasetModelList.map((item) => ({

View File

@@ -10,7 +10,7 @@ import {
} from '@/web/core/dataset/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
@@ -250,7 +250,7 @@ const InputDataModal = ({
return openConfirm(onDeleteData)();
}
if (e === TabEnum.doc) {
return window.open(getDocPath('/docs/use-cases/datasetengine'), '_blank');
return window.open(getDocPath('/docs/course/datasetengine'), '_blank');
}
setCurrentTab(e);
}}

View File

@@ -54,7 +54,6 @@ type FormType = {
similarity?: number;
limit?: number;
usingReRank?: boolean;
searchEmptyText?: string;
datasetSearchUsingExtensionQuery?: boolean;
datasetSearchExtensionModel?: string;
datasetSearchExtensionBg?: string;

View File

@@ -10,7 +10,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { postCreateDataset } from '@/web/core/dataset/api';
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import { useTranslation } from 'next-i18next';
@@ -19,6 +19,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySelect from '@fastgpt/web/components/common/MySelect';
import AIModelSelector from '@/components/Select/AIModelSelector';
const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: string }) => {
const { t } = useTranslation();
@@ -162,7 +163,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
</MyTooltip>
</Flex>
<Box flex={1}>
<MySelect
<AIModelSelector
w={'100%'}
value={getValues('vectorModel')}
list={filterNotHiddenVectorModelList.map((item) => ({
@@ -181,7 +182,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 100px'}>{t('core.ai.model.Dataset Agent Model')}</Box>
<Box flex={1}>
<MySelect
<AIModelSelector
w={'100%'}
value={getValues('agentModel')}
list={datasetModelList.map((item) => ({

View File

@@ -12,7 +12,7 @@ import {
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';

View File

@@ -28,12 +28,8 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { serviceSideProps } from '@/web/common/utils/i18n';
import dynamic from 'next/dynamic';
import {
DatasetTypeEnum,
DatasetTypeMap,
FolderIcon,
FolderImgUrl
} from '@fastgpt/global/core/dataset/constants';
import { DatasetTypeEnum, DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
import { FolderImgUrl, FolderIcon } from '@fastgpt/global/common/file/image/constants';
import MyMenu from '@/components/MyMenu';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useSystemStore } from '@/web/common/system/useSystemStore';

View File

@@ -13,6 +13,7 @@ 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 '@fastgpt/web/hooks/useToast';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
const PreviewPlugin = dynamic(() => import('./Preview'));
@@ -41,7 +42,12 @@ const Header = ({ plugin, onClose }: Props) => {
item.inputs.forEach((item) => {
item.connected = true;
});
if (item.outputs.find((output) => output.targets.length === 0)) {
if (
item.outputs.find(
(output) =>
output.key !== ModuleOutputKeyEnum.pluginStart && output.targets.length === 0
)
) {
toast({
status: 'warning',
title: t('module.Plugin input must connect')

View File

@@ -4,7 +4,7 @@ import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import dynamic from 'next/dynamic';
import { plugin2ModuleIO } from '@fastgpt/global/core/module/utils';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';

View File

@@ -3,7 +3,7 @@ import { useRouter } from 'next/router';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import { pluginSystemModuleTemplates } from '@fastgpt/global/core/module/template/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { serviceSideProps } from '@/web/common/utils/i18n';
@@ -13,7 +13,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import { usePluginStore } from '@/web/core/plugin/store/plugin';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
type Props = { pluginId: string };
@@ -22,13 +22,30 @@ const Render = ({ pluginId }: Props) => {
const router = useRouter();
const { toast } = useToast();
const { nodes, initData } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const { setBasicNodeTemplates } = useWorkflowStore();
const moduleTemplates = useMemo(() => {
const pluginTemplates = pluginModuleTemplates.filter((item) => item.id !== pluginId);
const concatTemplates = [...pluginSystemModuleTemplates, ...pluginTemplates];
const { data: pluginDetail } = useQuery(
['getOnePlugin', pluginId],
() => getOnePlugin(pluginId),
{
onError: (error) => {
toast({
status: 'warning',
title: getErrText(error, t('plugin.Load Plugin Failed'))
});
router.replace('/plugin/list');
}
}
);
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
useEffect(() => {
initData(JSON.parse(JSON.stringify(pluginDetail?.modules || [])));
}, [pluginDetail?.modules]);
useEffect(() => {
const concatTemplates = [...pluginSystemModuleTemplates];
const copyTemplates: FlowNodeTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1,
@@ -52,34 +69,11 @@ const Render = ({ pluginId }: Props) => {
template.inputs = template.inputs.filter((input) => !input.hideInPlugin);
});
return copyTemplates;
}, [nodes, pluginId, pluginModuleTemplates]);
const { data: pluginDetail } = useQuery(
['getOnePlugin', pluginId],
() => getOnePlugin(pluginId),
{
onError: (error) => {
toast({
status: 'warning',
title: getErrText(error, t('plugin.Load Plugin Failed'))
});
router.replace('/plugin/list');
}
}
);
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
useEffect(() => {
initData(JSON.parse(JSON.stringify(pluginDetail?.modules || [])));
}, [pluginDetail?.modules]);
setBasicNodeTemplates(copyTemplates);
}, [nodes, setBasicNodeTemplates]);
return pluginDetail ? (
<Flow
templates={moduleTemplates}
Header={<Header plugin={pluginDetail} onClose={() => router.back()} />}
/>
<Flow Header={<Header plugin={pluginDetail} onClose={() => router.back()} />} />
) : (
<Loading />
);

View File

@@ -11,22 +11,23 @@ import { useRequest } from '@/web/common/hooks/useRequest';
import { delOnePlugin, postCreatePlugin, putUpdatePlugin } from '@/web/core/plugin/api';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
import { customAlphabet } from 'nanoid';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { EditFormType } from './type';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
export type FormType = CreateOnePluginParams & {
id?: string;
};
export const defaultForm: FormType = {
export const defaultForm: EditFormType = {
avatar: '/icon/logo.svg',
name: '',
intro: '',
parentId: null,
type: PluginTypeEnum.custom,
modules: [
{
moduleId: nanoid(),
@@ -63,7 +64,7 @@ const CreateModal = ({
onSuccess,
onDelete
}: {
defaultValue?: FormType;
defaultValue?: EditFormType;
onClose: () => void;
onSuccess: () => void;
onDelete: () => void;
@@ -72,14 +73,17 @@ const CreateModal = ({
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const router = useRouter();
const { parentId } = router.query as { parentId: string };
const { isPc } = useSystemStore();
const { loadTeamPluginNodeTemplates } = useWorkflowStore();
const { openConfirm, ConfirmModal } = useConfirm({
title: t('common.Delete Tip'),
content: t('plugin.Confirm Delete')
});
const { register, setValue, getValues, handleSubmit } = useForm<FormType>({
defaultValues: defaultValue
const { register, setValue, getValues, handleSubmit } = useForm<EditFormType>({
defaultValues: { ...defaultValue, parentId: parentId || null }
});
const { File, onOpen: onOpenSelectFile } = useSelectFile({
@@ -111,19 +115,20 @@ const CreateModal = ({
);
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (data: FormType) => {
mutationFn: async (data: EditFormType) => {
return postCreatePlugin(data);
},
onSuccess(id: string) {
router.push(`/plugin/edit?pluginId=${id}`);
onSuccess();
onClose();
loadTeamPluginNodeTemplates();
},
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: async (data: FormType) => {
mutationFn: async (data: EditFormType) => {
if (!data.id) return Promise.resolve('');
// @ts-ignore
return putUpdatePlugin(data);
@@ -163,34 +168,41 @@ const CreateModal = ({
isCentered={!isPc}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('plugin.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
<>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('plugin.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
autoFocus={!defaultValue.id}
bg={'myWhite.600'}
{...register('name', {
required: t("common.Name Can't Be Empty")
})}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
autoFocus={!defaultValue.id}
bg={'myWhite.600'}
{...register('name', {
required: t("common.Name Can't Be Empty")
})}
/>
</Flex>
</Flex>
</>
<Box mt={3}>
<Box mb={1}>{t('plugin.Intro')}</Box>
<Textarea {...register('intro')} bg={'myWhite.600'} rows={5} />
<Textarea
{...register('intro')}
bg={'myWhite.600'}
rows={5}
placeholder={t('core.plugin.Intro placeholder')}
/>
</Box>
</ModalBody>

View File

@@ -0,0 +1,539 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
Box,
Flex,
Button,
ModalBody,
Input,
Textarea,
TableContainer,
Table,
Thead,
Th,
Tbody,
Tr,
Td,
IconButton,
useDisclosure
} 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';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRequest } from '@/web/common/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import {
delOnePlugin,
getApiSchemaByUrl,
postCreatePlugin,
putUpdatePlugin
} from '@/web/core/plugin/api';
import { str2OpenApiSchema } from '@fastgpt/global/core/plugin/httpPlugin/utils';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { AddIcon } from '@chakra-ui/icons';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { EditFormType } from './type';
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import { HttpHeaders } from '@/components/core/module/Flow/components/nodes/NodeHttp';
export const defaultHttpPlugin: CreateOnePluginParams = {
avatar: FolderImgUrl,
name: '',
intro: '',
parentId: null,
type: PluginTypeEnum.folder,
modules: [],
metadata: {
apiSchemaStr: '',
customHeaders: ''
}
};
const HttpPluginEditModal = ({
defaultPlugin = defaultHttpPlugin,
onClose,
onSuccess,
onDelete
}: {
defaultPlugin?: EditFormType;
onClose: () => void;
onSuccess: () => void;
onDelete: () => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const isEdit = !!defaultPlugin.id;
const [refresh, setRefresh] = useState(false);
const [schemaUrl, setSchemaUrl] = useState('');
const [customHeaders, setCustomHeaders] = useState<{ key: string; value: string }[]>(() => {
const keyValue = JSON.parse(defaultPlugin.metadata?.customHeaders || '{}');
return Object.keys(keyValue).map((key) => ({ key, value: keyValue[key] }));
});
const [updateTrigger, setUpdateTrigger] = useState(false);
const { register, setValue, getValues, handleSubmit, watch } = useForm<CreateOnePluginParams>({
defaultValues: defaultPlugin
});
const apiSchemaStr = watch('metadata.apiSchemaStr');
const apiData = useMemo(() => {
if (!apiSchemaStr) {
return { pathData: [], serverPath: '' };
}
try {
return str2OpenApiSchema(apiSchemaStr);
} catch (err) {
toast({
status: 'warning',
title: t('plugin.Invalid Schema')
});
return { pathData: [], serverPath: '' };
}
}, [apiSchemaStr, t, toast]);
const {
isOpen: isOpenUrlImport,
onOpen: onOpenUrlImport,
onClose: onCloseUrlImport
} = useDisclosure();
const { mutate: onCreate, isLoading: isCreating } = useRequest({
mutationFn: async (data: CreateOnePluginParams) => {
return postCreatePlugin(data);
},
onSuccess() {
onSuccess();
onClose();
},
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
const { mutate: updatePlugins, isLoading: isUpdating } = useRequest({
mutationFn: async (data: EditFormType) => {
if (!data.id) return Promise.resolve('');
return putUpdatePlugin({
id: data.id,
name: data.name,
avatar: data.avatar,
intro: data.intro,
metadata: data.metadata
});
},
onSuccess() {
onClose();
onSuccess();
},
successToast: t('common.Update Success'),
errorToast: t('common.Update Failed')
});
const { openConfirm, ConfirmModal } = useConfirm({
title: t('common.Delete Tip'),
content: t('core.plugin.Delete http plugin')
});
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: 'image/*',
multiple: false
});
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImgFileAndUpload({
type: MongoImageTypeEnum.pluginAvatar,
file,
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, t('common.Select File Failed')),
status: 'warning'
});
}
},
[setValue, t, toast]
);
const { mutate: onclickDelPlugin, isLoading: isDeleting } = useRequest({
mutationFn: async () => {
if (!defaultPlugin.id) return;
await delOnePlugin(defaultPlugin.id);
onDelete();
onClose();
},
successToast: t('common.Delete Success'),
errorToast: t('common.Delete Failed')
});
/* load api from url */
const { mutate: onClickUrlLoadApi, isLoading: isLoadingUrlApi } = useRequest({
mutationFn: async () => {
if (!schemaUrl || !schemaUrl.startsWith('https://')) {
return toast({
title: t('plugin.Invalid URL'),
status: 'warning'
});
}
const schema = await getApiSchemaByUrl(schemaUrl);
setValue('metadata.apiSchemaStr', JSON.stringify(schema, null, 2));
onCloseUrlImport();
},
errorToast: t('plugin.Invalid Schema')
});
const leftVariables = useMemo(
() =>
HttpHeaders.filter((variable) => {
const existVariables = customHeaders.map((item) => item.key);
return !existVariables.includes(variable.key);
}),
[customHeaders]
);
return (
<>
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/module/http.png"
title={isEdit ? t('plugin.Edit Http Plugin') : t('plugin.Import Plugin')}
w={['90vw', '600px']}
h={['90vh', '80vh']}
position={'relative'}
>
<ModalBody flex={'1 0 0'} overflow={'auto'}>
<>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('plugin.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
bg={'myWhite.600'}
{...register('name', {
required: t("common.Name Can't Be Empty")
})}
/>
</Flex>
</>
<>
<Box color={'myGray.800'} fontWeight={'bold'} mt={3}>
{t('plugin.Intro')}
</Box>
<Textarea {...register('intro')} bg={'myWhite.600'} rows={3} mt={3} />
</>
<Box mt={4}>
<Box
color={'myGray.800'}
fontWeight={'bold'}
justifyContent={'space-between'}
display={'flex'}
>
<Box my={'auto'}>{'OpenAPI Schema'}</Box>
<Box>
{isOpenUrlImport ? (
<Flex alignItems={'center'}>
<Input
mr={2}
placeholder={'https://...'}
h={'30px'}
onBlur={(e) => setSchemaUrl(e.target.value)}
/>
<Button size={'sm'} isLoading={isLoadingUrlApi} onClick={onClickUrlLoadApi}>
{t('common.Confirm')}
</Button>
<Button ml={2} variant={'whiteBase'} size={'sm'} onClick={onCloseUrlImport}>
{t('common.Cancel')}
</Button>
</Flex>
) : (
<Button
variant={'whiteBase'}
size={'sm'}
fontSize={'xs'}
leftIcon={<AddIcon fontSize={'xs'} />}
onClick={onOpenUrlImport}
>
{t('plugin.Import from URL')}
</Button>
)}
</Box>
</Box>
<Textarea
{...register('metadata.apiSchemaStr')}
bg={'myWhite.600'}
rows={10}
mt={3}
onBlur={(e) => {
const content = e.target.value;
if (!content) return;
setValue('metadata.apiSchemaStr', content);
}}
/>
</Box>
<>
<Box color={'myGray.800'} fontWeight={'bold'} mt={3}>
{t('core.plugin.Custom headers')}
</Box>
<Box mt={1}>
<TableContainer overflowY={'visible'} overflowX={'unset'}>
<Table>
<Thead>
<Tr>
<Th px={2}>{t('core.module.http.Props name')}</Th>
<Th px={2}>{t('core.module.http.Props value')}</Th>
</Tr>
</Thead>
<Tbody>
{customHeaders.map((item, index) => (
<Tr key={`${index}`}>
<Td p={0} w={'150px'}>
<HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
setDropdownValue={(val) => {
setCustomHeaders((prev) => {
const newHeaders = prev.map((item, i) =>
i === index ? { ...item, key: val } : item
);
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('core.module.http.Props name')}
value={item.key}
variables={leftVariables}
onBlur={(val) => {
setCustomHeaders((prev) => {
const newHeaders = prev.map((item, i) =>
i === index ? { ...item, key: val } : item
);
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
}}
updateTrigger={updateTrigger}
/>
</Td>
<Td p={0}>
<Box display={'flex'} alignItems={'center'}>
<HttpInput
placeholder={t('core.module.http.Props value')}
hasVariablePlugin={false}
value={item.value}
onBlur={(val) =>
setCustomHeaders((prev) => {
const newHeaders = prev.map((item, i) =>
i === index ? { ...item, value: val } : item
);
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
})
}
/>
<MyIcon
name={'delete'}
cursor={'pointer'}
_hover={{ color: 'red.600' }}
w={'14px'}
onClick={() =>
setCustomHeaders((prev) => {
const newHeaders = prev.filter((val) => val.key !== item.key);
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
})
}
/>
</Box>
</Td>
</Tr>
))}
<Tr>
<Td p={0} w={'150px'}>
<HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
setDropdownValue={(val) => {
setCustomHeaders((prev) => {
const newHeaders = [...prev, { key: val, value: '' }];
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('core.module.http.Add props')}
value={''}
variables={leftVariables}
updateTrigger={updateTrigger}
onBlur={(val) => {
if (!val) return;
setCustomHeaders((prev) => {
const newHeaders = [...prev, { key: val, value: '' }];
setValue(
'metadata.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
setUpdateTrigger((prev) => !prev);
}}
/>
</Td>
<Td p={0}>
<Box display={'flex'} alignItems={'center'}>
<HttpInput />
</Box>
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
</Box>
</>
<>
<Box color={'myGray.800'} fontWeight={'bold'} mt={3}>
{t('plugin.Plugin List')}
</Box>
<TableContainer maxH={400} overflowY={'auto'} mt={3}>
<Table border={'1px solid'} borderColor={'myGray.200'}>
<Thead>
<Th>{t('Name')}</Th>
<Th>{t('plugin.Description')}</Th>
<Th>{t('plugin.Method')}</Th>
<Th>{t('plugin.Path')}</Th>
</Thead>
<Tbody>
{apiData?.pathData?.map((item, index) => (
<Tr key={index}>
<Td>{item.name}</Td>
<Td
fontSize={'sm'}
textColor={'gray.600'}
w={'auto'}
maxW={80}
whiteSpace={'pre-wrap'}
>
{item.description}
</Td>
<Td>{item.method}</Td>
<Td>{item.path}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</>
</ModalBody>
<Flex px={5} py={4} alignItems={'center'}>
{isEdit && (
<IconButton
className="delete"
size={'xsSquare'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'whiteDanger'}
aria-label={'delete'}
_hover={{
bg: 'red.100'
}}
isLoading={isDeleting}
onClick={(e) => {
e.stopPropagation();
openConfirm(onclickDelPlugin)();
}}
/>
)}
<Box flex={1} />
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
{!isEdit ? (
<Button onClick={handleSubmit((data) => onCreate(data))} isLoading={isCreating}>
{t('common.Confirm Create')}
</Button>
) : (
<Button isLoading={isUpdating} onClick={handleSubmit((data) => updatePlugins(data))}>
{t('common.Confirm Update')}
</Button>
)}
</Flex>
</MyModal>
<File onSelect={onSelectFile} />
<ConfirmModal />
</>
);
};
export default HttpPluginEditModal;

View File

@@ -0,0 +1,5 @@
import { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
export type EditFormType = CreateOnePluginParams & {
id?: string;
};

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Box, Grid, Card, useTheme, Flex, IconButton, Button, Image } from '@chakra-ui/react';
import { Box, Grid, useTheme, Flex, IconButton, Button, Image } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import { AddIcon } from '@chakra-ui/icons';
@@ -9,44 +9,102 @@ import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
import EditModal, { defaultForm, FormType } from './component/EditModal';
import { getUserPlugins } from '@/web/core/plugin/api';
import EditModal, { defaultForm } from './component/EditModal';
import { getPluginPaths, getUserPlugins } from '@/web/core/plugin/api';
import EmptyTip from '@/components/EmptyTip';
import { useUserStore } from '@/web/support/user/useUserStore';
import MyMenu from '@/components/MyMenu';
import HttpPluginEditModal, { defaultHttpPlugin } from './component/HttpPluginEditModal';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import ParentPaths from '@/components/common/ParentPaths';
import { EditFormType } from './component/type';
const MyModules = () => {
const TeamPlugins = () => {
const { t } = useTranslation();
const theme = useTheme();
const { userInfo } = useUserStore();
const router = useRouter();
const [editModalData, setEditModalData] = useState<FormType>();
const { parentId } = router.query as { parentId: string };
const [editModalData, setEditModalData] = useState<EditFormType>();
const [httpPluginEditModalData, setHttpPluginModalData] = useState<EditFormType>();
/* load plugins */
const {
data = [],
isLoading,
refetch
} = useQuery(['loadModules'], () => getUserPlugins(), {
refetchOnMount: true
});
} = useQuery(
['loadModules', parentId],
() => {
return Promise.all([
getUserPlugins({ parentId: parentId === undefined ? '' : parentId }),
getPluginPaths(parentId)
]);
},
{
refetchOnMount: true
}
);
const paths = data?.[1] || [];
const plugins = data?.[0] || [];
return (
<PageContainer isLoading={isLoading} insertProps={{ px: [5, '48px'] }}>
<Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}>
<Flex flex={1} alignItems={'center'}>
<Image src={'/imgs/module/plugin.svg'} alt={''} mr={2} h={'24px'} />
<Box className="textlg" letterSpacing={1} fontSize={['20px', '24px']} fontWeight={'bold'}>
{t('plugin.My Plugins')}({t('common.Beta')})
</Box>
</Flex>
<ParentPaths
paths={paths.map((path, i) => ({
parentId: path.parentId,
parentName: path.parentName
}))}
FirstPathDom={
<Flex flex={1} alignItems={'center'}>
<Image src={'/imgs/module/plugin.svg'} alt={''} mr={2} h={'24px'} />
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
{t('plugin.My Plugins')}({t('common.Beta')})
</Box>
</Flex>
}
onClick={(e) => {
router.push({
query: {
parentId: e
}
});
}}
/>
{userInfo?.team?.canWrite && (
<Button
leftIcon={<AddIcon />}
variant={'primaryOutline'}
onClick={() => setEditModalData(defaultForm)}
>
{t('common.New Create')}
</Button>
<MyMenu
offset={[-30, 5]}
width={120}
Button={
<Button variant={'primaryOutline'} px={0}>
<Flex alignItems={'center'} px={'20px'}>
<AddIcon mr={2} />
<Box>{t('common.Create New')}</Box>
</Flex>
</Button>
}
menuList={[
{
label: (
<Flex>
<Image src={'/imgs/module/plugin.svg'} alt={''} w={'18px'} mr={1} />
{t('plugin.Custom Plugin')}
</Flex>
),
onClick: () => setEditModalData(defaultForm)
},
{
label: (
<Flex display={'flex'} alignItems={'center'}>
<Image src={'/imgs/module/http.png'} alt={''} w={'18px'} h={'14px'} mr={1} />
{t('plugin.HTTP Plugin')}
</Flex>
),
onClick: () => setHttpPluginModalData(defaultHttpPlugin)
}
]}
/>
)}
</Flex>
<Grid
@@ -54,7 +112,7 @@ const MyModules = () => {
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
gridGap={5}
>
{data.map((plugin) => (
{plugins.map((plugin) => (
<Box
key={plugin._id}
py={3}
@@ -74,7 +132,22 @@ const MyModules = () => {
display: 'flex'
}
}}
onClick={() => router.push(`/plugin/edit?pluginId=${plugin._id}`)}
onClick={() => {
if (plugin.type === PluginTypeEnum.folder) {
router.push({
query: {
parentId: plugin._id
}
});
} else {
router.push({
pathname: '/plugin/edit',
query: {
pluginId: plugin._id
}
});
}
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={plugin.avatar} borderRadius={'md'} w={'28px'} />
@@ -94,12 +167,20 @@ const MyModules = () => {
}}
onClick={(e) => {
e.stopPropagation();
setEditModalData({
const data = {
id: plugin._id,
parentId: plugin.parentId,
name: plugin.name,
avatar: plugin.avatar,
intro: plugin.intro
});
intro: plugin.intro,
modules: [],
type: plugin.type,
metadata: plugin.metadata
};
if (plugin.type === PluginTypeEnum.folder) {
return setHttpPluginModalData(data);
}
setEditModalData(data);
}}
/>
</Flex>
@@ -124,6 +205,14 @@ const MyModules = () => {
onDelete={refetch}
/>
)}
{!!httpPluginEditModalData && (
<HttpPluginEditModal
defaultPlugin={httpPluginEditModalData}
onClose={() => setHttpPluginModalData(undefined)}
onSuccess={refetch}
onDelete={refetch}
/>
)}
</PageContainer>
);
};
@@ -136,4 +225,4 @@ export async function getServerSideProps(content: any) {
};
}
export default MyModules;
export default TeamPlugins;

View File

@@ -23,7 +23,7 @@ const FAQ = () => {
},
{
title: '知识库存储怎么计算?',
desc: '1条知识库存储等于1条知识库索引。一条知识库数据可以包含1条或多条知识库索引。'
desc: '1条知识库存储等于1条知识库索引。一条知识库数据可以包含1条或多条知识库索引。增强训练中1条数据会生成5条索引。'
},
{
title: '知识库索引超出会删除么?',
@@ -35,7 +35,7 @@ const FAQ = () => {
},
{
title: '免费版数据会清除么?',
desc: '免费版用户15天无使用记录后会自动清除所有知识库内容。'
desc: '免费版用户(免费版且未购买额外套餐)15天无使用记录后系统会自动清除账号下所有知识库内容。'
}
];

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Flex, Grid } from '@chakra-ui/react';
import { Box, Flex, Grid, Link } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -18,7 +18,10 @@ const Points = () => {
<Box id="point-card" fontWeight={'bold'} fontSize={['24px', '36px']}>
{t('support.wallet.subscription.Ai points')}
</Box>
<Grid gap={6} mt={['30px', '48px']} w={'100%'}>
<Link href="https://tiktokenizer.vercel.app/" target="_blank">
线 Tokens
</Link>
<Grid gap={6} mt={['30px', '40px']} w={'100%'}>
<Box
display={['block', 'flex']}
borderRadius={'xl'}

View File

@@ -12,7 +12,7 @@ import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/su
import { useToast } from '@fastgpt/web/hooks/useToast';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
@@ -130,6 +130,7 @@ const Standard = ({
gap={[4, 6, 8]}
w={'100%'}
maxW={'1440px'}
minH={'550px'}
>
{standardSubList.map((item) => {
const isCurrentPlan =