mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 09:44:47 +00:00
4.8-preview fix (#1324)
* feishu app release (#85) * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * feat: feishu app release * chore: sovle the conflicts files; fix the feishu entry * fix: rename Feishu interface to FeishuType * fix: fix type problem in app.ts * fix: type problem * fix: style problem --------- Co-authored-by: Archer <545436317@qq.com> * perf: publish channel code * change system variable position (#94) * perf: workflow context * perf: variable select * hide publish * perf: simple edit auto refresh * perf: simple edit data refresh * fix: target handle --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { Box, Card, Flex, Select } from '@chakra-ui/react';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { Box, Card, Flex } from '@chakra-ui/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { UserType } from '@fastgpt/global/support/user/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
@@ -28,10 +28,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
avatar: props.avatar,
|
||||
parentId: props.parentId,
|
||||
version: 'v2',
|
||||
...(modules && {
|
||||
...(modules?.length && {
|
||||
modules: modules
|
||||
}),
|
||||
...(edges && { edges }),
|
||||
...(edges?.length && { edges }),
|
||||
metadata: props.metadata
|
||||
};
|
||||
|
||||
|
@@ -2,8 +2,6 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { request } from 'https';
|
||||
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
|
||||
import url from 'url';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -25,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('lafEnv is empty');
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(lafEnv);
|
||||
const parsedUrl = new URL(lafEnv);
|
||||
delete req.headers?.cookie;
|
||||
delete req.headers?.host;
|
||||
delete req.headers?.origin;
|
||||
|
@@ -3,7 +3,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { request } from 'http';
|
||||
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
|
||||
import url from 'url';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -15,8 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('url is empty');
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(FastGPTProUrl);
|
||||
|
||||
const parsedUrl = new URL(FastGPTProUrl);
|
||||
delete req.headers?.rootkey;
|
||||
|
||||
const requestResult = request({
|
||||
@@ -37,6 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
response.statusCode && res.writeHead(response.statusCode);
|
||||
response.pipe(res);
|
||||
});
|
||||
|
||||
requestResult.on('error', (e) => {
|
||||
res.send(e);
|
||||
res.end();
|
||||
|
@@ -5,17 +5,18 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
|
||||
/* create a shareChat */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, ...props } = req.body as OutLinkEditType & {
|
||||
appId: string;
|
||||
type: `${OutLinkTypeEnum}`;
|
||||
};
|
||||
const { appId, ...props } = req.body as OutLinkEditType &
|
||||
OutLinkEditType & {
|
||||
appId: string;
|
||||
type: PublishChannelEnum;
|
||||
};
|
||||
|
||||
const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
|
||||
|
@@ -9,15 +9,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
const { appId } = req.query as {
|
||||
const { appId, type } = req.query as {
|
||||
appId: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' });
|
||||
|
||||
const data = await MongoOutLink.find({
|
||||
appId,
|
||||
...(isOwner ? { teamId } : { tmbId })
|
||||
...(isOwner ? { teamId } : { tmbId }),
|
||||
type: type
|
||||
}).sort({
|
||||
_id: -1
|
||||
});
|
||||
|
@@ -9,10 +9,6 @@ import dynamic from 'next/dynamic';
|
||||
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest';
|
||||
import {
|
||||
getWorkflowStore,
|
||||
useFlowProviderStore
|
||||
} from '@/components/core/workflow/Flow/FlowProvider';
|
||||
import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -28,6 +24,8 @@ import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { formatTime2HM } from '@fastgpt/global/common/string/time';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
|
||||
|
||||
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
|
||||
|
||||
@@ -59,9 +57,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
});
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
const { publishApp, updateAppDetail } = useAppStore();
|
||||
const { edges, onUpdateNodeError } = useFlowProviderStore();
|
||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save'));
|
||||
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
|
||||
|
||||
const flowData2StoreDataAndCheck = useCallback(async () => {
|
||||
const { nodes } = await getWorkflowStore();
|
||||
@@ -101,13 +101,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
time: formatTime2HM()
|
||||
})
|
||||
);
|
||||
ChatTestRef.current?.resetChatTest();
|
||||
// ChatTestRef.current?.resetChatTest();
|
||||
} catch (error) {}
|
||||
|
||||
setIsSaving(false);
|
||||
|
||||
return null;
|
||||
}, [updateAppDetail, app._id, edges, ChatTestRef, t]);
|
||||
}, [updateAppDetail, app._id, edges, t]);
|
||||
|
||||
const onclickPublish = useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
|
@@ -2,10 +2,11 @@ import React, { useEffect, useMemo } from 'react';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import Header from './Header';
|
||||
import Flow from '@/components/core/workflow/Flow';
|
||||
import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider';
|
||||
import { appSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
||||
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
type Props = { app: AppSchema; onClose: () => void };
|
||||
|
||||
@@ -17,7 +18,7 @@ const Render = ({ app, onClose }: Props) => {
|
||||
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大,会导致许多工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试,无需点击保存,点击保存为新版工作流。'
|
||||
});
|
||||
|
||||
const { initData } = useFlowProviderStore();
|
||||
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
|
||||
|
||||
const workflowStringData = JSON.stringify({
|
||||
nodes: app.modules || [],
|
||||
@@ -53,13 +54,15 @@ export default React.memo(function FlowEdit(props: Props) {
|
||||
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
|
||||
|
||||
return (
|
||||
<FlowProvider
|
||||
appId={props.app._id}
|
||||
mode={'app'}
|
||||
filterAppIds={filterAppIds}
|
||||
basicNodeTemplates={appSystemModuleTemplates}
|
||||
<WorkflowContextProvider
|
||||
value={{
|
||||
appId: props.app._id,
|
||||
mode: 'app',
|
||||
filterAppIds,
|
||||
basicNodeTemplates: appSystemModuleTemplates
|
||||
}}
|
||||
>
|
||||
<Render {...props} />
|
||||
</FlowProvider>
|
||||
</WorkflowContextProvider>
|
||||
);
|
||||
});
|
||||
|
@@ -1,58 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
|
||||
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import Share from './Share';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
const API = dynamic(() => import('./API'));
|
||||
|
||||
const OutLink = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [linkType, setLinkType] = useState<`${OutLinkTypeEnum}`>(OutLinkTypeEnum.share);
|
||||
|
||||
return (
|
||||
<Box pt={[1, 5]}>
|
||||
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
|
||||
{t('core.app.navbar.Publish app')}
|
||||
</Box>
|
||||
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
|
||||
<MyRadio
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 400px))']}
|
||||
iconSize={'20px'}
|
||||
list={[
|
||||
{
|
||||
icon: '/imgs/modal/shareFill.svg',
|
||||
title: t('core.app.Share link'),
|
||||
desc: t('core.app.Share link desc'),
|
||||
value: OutLinkTypeEnum.share
|
||||
},
|
||||
{
|
||||
icon: 'support/outlink/apikeyFill',
|
||||
title: t('core.app.Api request'),
|
||||
desc: t('core.app.Api request desc'),
|
||||
value: OutLinkTypeEnum.apikey
|
||||
}
|
||||
// {
|
||||
// icon: 'support/outlink/iframeLight',
|
||||
// title: '网页嵌入',
|
||||
// desc: '嵌入到已有网页中,右下角会生成对话按键',
|
||||
// value: OutLinkTypeEnum.iframe
|
||||
// }
|
||||
]}
|
||||
value={linkType}
|
||||
onChange={(e) => setLinkType(e as `${OutLinkTypeEnum}`)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{linkType === OutLinkTypeEnum.share && <Share appId={appId} />}
|
||||
{linkType === OutLinkTypeEnum.apikey && <API appId={appId} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutLink;
|
@@ -0,0 +1,211 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, Box, Button, ModalFooter, ModalBody, Input } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import dayjs from 'dayjs';
|
||||
import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
|
||||
|
||||
const FeiShuEditModal = ({
|
||||
appId,
|
||||
defaultData,
|
||||
onClose,
|
||||
onCreate,
|
||||
onEdit
|
||||
}: {
|
||||
appId: string;
|
||||
defaultData: OutLinkEditType<FeishuType>;
|
||||
onClose: () => void;
|
||||
onCreate: (id: string) => void;
|
||||
onEdit: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit: submitShareChat
|
||||
} = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const isEdit = useMemo(() => !!defaultData?._id, [defaultData]);
|
||||
|
||||
const { mutate: onclickCreate, isLoading: creating } = useRequest({
|
||||
mutationFn: async (e: OutLinkEditType<FeishuType>) => {
|
||||
createShareChat({
|
||||
...e,
|
||||
appId,
|
||||
type: PublishChannelEnum.feishu
|
||||
});
|
||||
},
|
||||
errorToast: t('common.Create Failed'),
|
||||
onSuccess: onCreate
|
||||
});
|
||||
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||
mutationFn: (e: OutLinkEditType<FeishuType>) => {
|
||||
return updateShareChat(e);
|
||||
},
|
||||
errorToast: t('common.Update Failed'),
|
||||
onSuccess: onEdit
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/modal/shareFill.svg"
|
||||
title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 90px'}>{t('Name')}</Box>
|
||||
<Input
|
||||
placeholder={t('outlink.Feishu name') || 'Link Name'} // TODO: i18n
|
||||
maxLength={20}
|
||||
{...register('name', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
QPM
|
||||
<MyTooltip label={t('outlink.QPM Tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
max={1000}
|
||||
{...register('limit.QPM', {
|
||||
min: 0,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: t('outlink.QPM is empty') || ''
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('support.outlink.Max usage points')}
|
||||
<MyTooltip label={t('support.outlink.Max usage points tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.maxUsagePoints', {
|
||||
min: -1,
|
||||
max: 10000000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Expired Time')}
|
||||
</Flex>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
defaultValue={
|
||||
defaultData.limit?.expiredTime
|
||||
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
setValue('limit.expiredTime', new Date(e.target.value));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
默认回复
|
||||
{/* TODO: i18n */}
|
||||
</Flex>
|
||||
<Input
|
||||
placeholder={t('outlink.Default Response') || 'Link Name'}
|
||||
maxLength={20}
|
||||
{...register('defaultResponse', {
|
||||
required: t('common.default Response is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
立即回复
|
||||
{/* TODO: i18n */}
|
||||
</Flex>
|
||||
<Input
|
||||
placeholder={t('outlink.Default Response') || 'Link Name'}
|
||||
maxLength={20}
|
||||
{...register('immediateResponse', {
|
||||
required: t('common.default Response is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 90px'}>{t('core.module.http.AppId')}</Box>
|
||||
<Input
|
||||
placeholder={t('core.module.http.appId') || 'Link Name'}
|
||||
// maxLength={20}
|
||||
{...register('app.appId', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 90px'}>{t('core.module.http.AppSecret')}</Box>
|
||||
<Input
|
||||
placeholder={t('outlink.AppSecret') || 'Link Name'}
|
||||
// maxLength={20}
|
||||
{...register('app.appSecret', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 90px'}>Encrypt Key</Box>
|
||||
<Input
|
||||
placeholder="Encrypt Key"
|
||||
// maxLength={20}
|
||||
{...register('app.encryptKey', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 90px'}>Verification Token</Box>
|
||||
<Input
|
||||
placeholder="Verification Token"
|
||||
// maxLength={20}
|
||||
{...register('app.verificationToken', {
|
||||
required: t('common.Name is empty') || 'Name is empty'
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
{/* <Flex alignItems={'center'} mt={4}> */}
|
||||
{/* <Flex flex={'0 0 90px'} alignItems={'center'}> */}
|
||||
{/* 限制回复 */}
|
||||
{/* </Flex> */}
|
||||
{/* <Switch {...register('wecomConfig.ReplyLimit')} size={'lg'} /> */}
|
||||
{/* </Flex> */}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={creating || updating}
|
||||
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
|
||||
>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeiShuEditModal;
|
@@ -0,0 +1,193 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Button,
|
||||
TableContainer,
|
||||
Table,
|
||||
Thead,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
Tbody
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
|
||||
import { formatTimeToChatTime } from '@/utils/tools';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { defaultFeishuOutLinkForm } from '@/constants/app';
|
||||
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import dayjs from 'dayjs';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
|
||||
const FeiShuEditModal = dynamic(() => import('./FeiShuEditModal'));
|
||||
|
||||
const FeiShu = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { copyData } = useCopyData();
|
||||
const [editFeiShuLinkData, setEditFeiShuLinkData] = useState<OutLinkEditType<FeishuType>>();
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
isFetching,
|
||||
data: shareChatList = [],
|
||||
refetch: refetchShareChatList
|
||||
} = useQuery(['initShareChatList', appId], () =>
|
||||
getShareChatList<FeishuType>({ appId, type: PublishChannelEnum.feishu })
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
|
||||
<Flex justifyContent={'space-between'}>
|
||||
<Box fontWeight={'bold'} fontSize={['md', 'xl']}>
|
||||
{t('core.app.publish.Fei shu bot publish')}
|
||||
</Box>
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
colorScheme={'blue'}
|
||||
size={['sm', 'md']}
|
||||
{...(shareChatList.length >= 10
|
||||
? {
|
||||
isDisabled: true,
|
||||
title: t('core.app.share.Amount limit tip')
|
||||
}
|
||||
: {})}
|
||||
onClick={() => setEditFeiShuLinkData(defaultFeishuOutLinkForm)}
|
||||
>
|
||||
{t('core.app.share.Create link')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<TableContainer mt={3}>
|
||||
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common.Name')}</Th>
|
||||
<Th>{t('support.outlink.Usage points')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Th>{t('core.app.share.Ip limit title')}</Th>
|
||||
<Th>{t('common.Expired Time')}</Th>
|
||||
</>
|
||||
)}
|
||||
<Th>{t('common.Last use time')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{shareChatList.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{Math.round(item.usagePoints)}
|
||||
{feConfigs?.isPlus
|
||||
? `${
|
||||
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
|
||||
? ` / ${item.limit.maxUsagePoints}`
|
||||
: ` / ${t('common.Unlimited')}`
|
||||
}`
|
||||
: ''}
|
||||
</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>{item?.limit?.QPM || '-'}</Td>
|
||||
<Td>
|
||||
{item?.limit?.expiredTime
|
||||
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||
: '-'}
|
||||
</Td>
|
||||
</>
|
||||
)}
|
||||
<Td>
|
||||
{item.lastTime ? t(formatTimeToChatTime(item.lastTime)) : t('common.Un used')}
|
||||
</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<MyMenu
|
||||
Button={
|
||||
<MyIcon
|
||||
name={'more'}
|
||||
_hover={{ bg: 'myGray.100 ' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
w={'14px'}
|
||||
p={2}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
label: t('common.Edit'),
|
||||
icon: 'edit',
|
||||
onClick: () =>
|
||||
setEditFeiShuLinkData({
|
||||
_id: item._id,
|
||||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
})
|
||||
},
|
||||
{
|
||||
label: t('common.Delete'),
|
||||
icon: 'delete',
|
||||
onClick: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await delShareChatById(item._id);
|
||||
refetchShareChatList();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{editFeiShuLinkData && (
|
||||
<FeiShuEditModal
|
||||
appId={appId}
|
||||
// type={'feishu' as PublishChannelEnum}
|
||||
defaultData={editFeiShuLinkData}
|
||||
onCreate={(id) => {
|
||||
refetchShareChatList();
|
||||
setEditFeiShuLinkData(undefined);
|
||||
}}
|
||||
onEdit={() => {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common.Update Successful')
|
||||
});
|
||||
refetchShareChatList();
|
||||
setEditFeiShuLinkData(undefined);
|
||||
}}
|
||||
onClose={() => setEditFeiShuLinkData(undefined)}
|
||||
/>
|
||||
)}
|
||||
{shareChatList.length === 0 && !isFetching && (
|
||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
{t('core.app.share.Not share link')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Loading loading={isFetching} fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FeiShu);
|
@@ -32,8 +32,8 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { defaultOutLinkForm } from '@/constants/app';
|
||||
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -47,7 +47,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
|
||||
const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal'));
|
||||
|
||||
const Share = ({ appId }: { appId: string }) => {
|
||||
const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { feConfigs } = useSystemStore();
|
||||
@@ -64,7 +64,9 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
isFetching,
|
||||
data: shareChatList = [],
|
||||
refetch: refetchShareChatList
|
||||
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
||||
} = useQuery(['initShareChatList', appId], () =>
|
||||
getShareChatList({ appId, type: PublishChannelEnum.share })
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
|
||||
@@ -95,12 +97,16 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common.Name')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Th>{t('common.Expired Time')}</Th>
|
||||
</>
|
||||
)}
|
||||
<Th>{t('support.outlink.Usage points')}</Th>
|
||||
<Th>{t('core.app.share.Is response quote')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Th>{t('core.app.share.Ip limit title')}</Th>
|
||||
<Th>{t('common.Expired Time')}</Th>
|
||||
<Th>{t('core.app.share.Role check')}</Th>
|
||||
</>
|
||||
)}
|
||||
@@ -112,6 +118,15 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
{shareChatList.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>
|
||||
{item.limit?.expiredTime
|
||||
? dayjs(item.limit.expiredTime).format('YYYY-MM-DD HH:mm')
|
||||
: '-'}
|
||||
</Td>
|
||||
</>
|
||||
)}
|
||||
<Td>
|
||||
{Math.round(item.usagePoints)}
|
||||
{feConfigs?.isPlus
|
||||
@@ -126,18 +141,14 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>{item?.limit?.QPM || '-'}</Td>
|
||||
<Td>
|
||||
{item?.limit?.expiredTime
|
||||
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||
: '-'}
|
||||
</Td>
|
||||
|
||||
<Th>{item?.limit?.hookUrl ? '✔' : '✖'}</Th>
|
||||
</>
|
||||
)}
|
||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : t('common.Un used')}</Td>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Button
|
||||
onClick={() => setSelectedLinkData(item)}
|
||||
onClick={() => setSelectedLinkData(item as OutLinkSchema)}
|
||||
size={'sm'}
|
||||
mr={3}
|
||||
variant={'whitePrimary'}
|
||||
@@ -201,7 +212,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
{!!editLinkData && (
|
||||
<EditLinkModal
|
||||
appId={appId}
|
||||
type={'share'}
|
||||
type={PublishChannelEnum.share}
|
||||
defaultData={editLinkData}
|
||||
onCreate={(id) => {
|
||||
const url = `${location.origin}/chat/share?shareId=${id}`;
|
||||
@@ -242,7 +253,7 @@ function EditLinkModal({
|
||||
onEdit
|
||||
}: {
|
||||
appId: string;
|
||||
type: `${OutLinkTypeEnum}`;
|
||||
type: PublishChannelEnum;
|
||||
defaultData: OutLinkEditType;
|
||||
onClose: () => void;
|
||||
onCreate: (id: string) => void;
|
||||
@@ -297,6 +308,22 @@ function EditLinkModal({
|
||||
</Flex>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Expired Time')}
|
||||
</Flex>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
defaultValue={
|
||||
defaultData.limit?.expiredTime
|
||||
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
setValue('limit.expiredTime', new Date(e.target.value));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
QPM
|
||||
@@ -330,22 +357,7 @@ function EditLinkModal({
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Expired Time')}
|
||||
</Flex>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
defaultValue={
|
||||
defaultData.limit?.expiredTime
|
||||
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
setValue('limit.expiredTime', new Date(e.target.value));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('outlink.token auth')}
|
@@ -0,0 +1,64 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import Link from './Link';
|
||||
const API = dynamic(() => import('./API'));
|
||||
const FeiShu = dynamic(() => import('./FeiShu'));
|
||||
|
||||
const OutLink = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const publishList = useRef([
|
||||
{
|
||||
icon: '/imgs/modal/shareFill.svg',
|
||||
title: t('core.app.Share link'),
|
||||
desc: t('core.app.Share link desc'),
|
||||
value: PublishChannelEnum.share
|
||||
},
|
||||
{
|
||||
icon: 'support/outlink/apikeyFill',
|
||||
title: t('core.app.Api request'),
|
||||
desc: t('core.app.Api request desc'),
|
||||
value: PublishChannelEnum.apikey
|
||||
}
|
||||
// {
|
||||
// icon: 'core/app/publish/lark',
|
||||
// title: t('core.app.publish.Fei shu bot'),
|
||||
// desc: t('core.app.publish.Fei Shu Bot Desc'),
|
||||
// value: PublishChannelEnum.feishu
|
||||
// }
|
||||
]);
|
||||
|
||||
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
|
||||
|
||||
return (
|
||||
<Box pt={[1, 5]}>
|
||||
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
|
||||
{t('core.app.navbar.Publish app')}
|
||||
</Box>
|
||||
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
|
||||
<MyRadio
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 300px))']}
|
||||
iconSize={'20px'}
|
||||
list={publishList.current}
|
||||
value={linkType}
|
||||
onChange={(e) => setLinkType(e as PublishChannelEnum)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{linkType === PublishChannelEnum.share && (
|
||||
<Link appId={appId} type={PublishChannelEnum.share} />
|
||||
)}
|
||||
{linkType === PublishChannelEnum.apikey && <API appId={appId} />}
|
||||
{linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutLink;
|
@@ -1,11 +1,9 @@
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
@@ -13,27 +11,41 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import {
|
||||
getDefaultEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { useCreation, useMemoizedFn, useSafeState } from 'ahooks';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
|
||||
const ChatTest = ({ appId }: { appId: string }) => {
|
||||
const ChatTest = ({
|
||||
editForm,
|
||||
appId
|
||||
}: {
|
||||
editForm: UseFormReturn<AppSimpleEditFormType, any>;
|
||||
appId: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const { appDetail } = useAppStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const [workflowData, setWorkflowData] = useState<{
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
}>({
|
||||
nodes: [],
|
||||
edges: []
|
||||
});
|
||||
const { appDetail } = useAppStore();
|
||||
|
||||
const startChat = useCallback(
|
||||
const { watch } = editForm;
|
||||
|
||||
const [workflowData, setWorkflowData] = useSafeState({
|
||||
nodes: appDetail.modules || [],
|
||||
edges: appDetail.edges || []
|
||||
});
|
||||
const userGuideModule = useCreation(
|
||||
() => getGuideModule(workflowData.nodes),
|
||||
[workflowData.nodes]
|
||||
);
|
||||
|
||||
const startChat = useMemoizedFn(
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
if (!workflowData) return Promise.reject('workflowData is empty');
|
||||
|
||||
@@ -72,8 +84,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
});
|
||||
|
||||
return { responseText, responseData };
|
||||
},
|
||||
[workflowData, appId, appDetail.name]
|
||||
}
|
||||
);
|
||||
|
||||
const resetChatBox = useCallback(() => {
|
||||
@@ -82,12 +93,15 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
resetChatBox();
|
||||
setWorkflowData({
|
||||
nodes: appDetail.modules || [],
|
||||
edges: appDetail.edges || []
|
||||
const wat = watch((data) => {
|
||||
const { nodes, edges } = form2AppWorkflow(data as AppSimpleEditFormType);
|
||||
setWorkflowData({ nodes, edges });
|
||||
});
|
||||
}, [appDetail, resetChatBox]);
|
||||
|
||||
return () => {
|
||||
wat.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -124,7 +138,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
appAvatar={appDetail.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
userGuideModule={getGuideModule(workflowData.nodes)}
|
||||
userGuideModule={userGuideModule}
|
||||
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={() => {}}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import React, { useMemo, useState, useTransition } from 'react';
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
@@ -51,26 +49,24 @@ const LabelStyles: BoxProps = {
|
||||
};
|
||||
|
||||
const EditForm = ({
|
||||
editForm,
|
||||
divRef,
|
||||
isSticky
|
||||
}: {
|
||||
editForm: UseFormReturn<AppSimpleEditFormType, any>;
|
||||
divRef: React.RefObject<HTMLDivElement>;
|
||||
isSticky: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { appDetail, publishApp } = useAppStore();
|
||||
const { publishApp, appDetail } = useAppStore();
|
||||
|
||||
const { loadAllDatasets, allDatasets } = useDatasetStore();
|
||||
const { isPc, llmModelList } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const { llmModelList } = useSystemStore();
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
const { setValue, getValues, reset, handleSubmit, control, watch } =
|
||||
useForm<AppSimpleEditFormType>({
|
||||
defaultValues: getDefaultAppForm()
|
||||
});
|
||||
const { setValue, getValues, handleSubmit, control, watch } = editForm;
|
||||
|
||||
const { fields: datasets, replace: replaceKbList } = useFieldArray({
|
||||
control,
|
||||
@@ -107,6 +103,9 @@ const EditForm = ({
|
||||
[t, variables]
|
||||
);
|
||||
const searchMode = watch('dataset.searchMode');
|
||||
const tts = getValues('userGuide.tts');
|
||||
const whisperConfig = getValues('userGuide.whisper');
|
||||
const postQuestionGuide = getValues('userGuide.questionGuide');
|
||||
|
||||
const selectDatasets = useMemo(
|
||||
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
|
||||
@@ -118,7 +117,7 @@ const EditForm = ({
|
||||
}, [selectLLMModel, llmModelList]);
|
||||
|
||||
/* on save app */
|
||||
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
|
||||
const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({
|
||||
mutationFn: async (data: AppSimpleEditFormType) => {
|
||||
const { nodes, edges } = form2AppWorkflow(data);
|
||||
|
||||
@@ -132,22 +131,6 @@ const EditForm = ({
|
||||
errorToast: t('common.Save Failed')
|
||||
});
|
||||
|
||||
useQuery(
|
||||
['init', appDetail],
|
||||
() => {
|
||||
const formatVal = appWorkflow2Form({
|
||||
nodes: appDetail.modules
|
||||
});
|
||||
reset(formatVal);
|
||||
setRefresh(!refresh);
|
||||
return formatVal;
|
||||
},
|
||||
{
|
||||
enabled: !!appDetail._id
|
||||
}
|
||||
);
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* title */}
|
||||
@@ -177,16 +160,23 @@ const EditForm = ({
|
||||
<Button
|
||||
isLoading={isSaving}
|
||||
size={['sm', 'md']}
|
||||
leftIcon={
|
||||
appDetail.type === AppTypeEnum.simple ? (
|
||||
<MyIcon name={'common/publishFill'} w={['14px', '16px']} />
|
||||
) : undefined
|
||||
}
|
||||
variant={appDetail.type === AppTypeEnum.simple ? 'primary' : 'whitePrimary'}
|
||||
onClick={() => {
|
||||
if (appDetail.type !== AppTypeEnum.simple) {
|
||||
openConfirmSave(handleSubmit((data) => onSubmitSave(data)))();
|
||||
openConfirmSave(handleSubmit((data) => onSubmitPublish(data)))();
|
||||
} else {
|
||||
handleSubmit((data) => onSubmitSave(data))();
|
||||
handleSubmit((data) => onSubmitPublish(data))();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isPc ? t('core.app.Save and preview') : t('common.Save')}
|
||||
{appDetail.type !== AppTypeEnum.simple
|
||||
? t('core.app.Change to simple mode')
|
||||
: t('core.app.Publish')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -272,7 +262,7 @@ const EditForm = ({
|
||||
{t('common.Params')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{getValues('dataset.datasets').length > 0 && (
|
||||
{datasetSearchSetting.datasets?.length > 0 && (
|
||||
<Box my={3}>
|
||||
<SearchParamsTip
|
||||
searchMode={searchMode}
|
||||
@@ -382,7 +372,6 @@ const EditForm = ({
|
||||
variables={variables}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.variables', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -411,10 +400,9 @@ const EditForm = ({
|
||||
{/* tts */}
|
||||
<Box {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('userGuide.tts')}
|
||||
value={tts}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.tts', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -422,11 +410,10 @@ const EditForm = ({
|
||||
{/* whisper */}
|
||||
<Box {...BoxStyles}>
|
||||
<WhisperConfig
|
||||
isOpenAudio={getValues('userGuide.tts').type !== TTSTypeEnum.none}
|
||||
value={getValues('userGuide.whisper')}
|
||||
isOpenAudio={tts.type !== TTSTypeEnum.none}
|
||||
value={whisperConfig}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.whisper', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -434,12 +421,10 @@ const EditForm = ({
|
||||
{/* question guide */}
|
||||
<Box {...BoxStyles} borderBottom={'none'}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('userGuide.questionGuide')}
|
||||
isChecked={postQuestionGuide}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue('userGuide.questionGuide', value);
|
||||
setRefresh((state) => !state);
|
||||
setValue('userGuide.questionGuide', e.target.checked);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -468,8 +453,6 @@ const EditForm = ({
|
||||
...getValues('dataset'),
|
||||
...e
|
||||
});
|
||||
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@@ -2,14 +2,33 @@ import React from 'react';
|
||||
import { Box, Grid } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useSticky } from '@/web/common/hooks/useSticky';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
|
||||
|
||||
import ChatTest from './ChatTest';
|
||||
import AppCard from './AppCard';
|
||||
import EditForm from './EditForm';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
const SimpleEdit = ({ appId }: { appId: string }) => {
|
||||
const { isPc } = useSystemStore();
|
||||
const { parentRef, divRef, isSticky } = useSticky();
|
||||
const { loadAllDatasets } = useDatasetStore();
|
||||
const { appDetail } = useAppStore();
|
||||
|
||||
const editForm = useForm<AppSimpleEditFormType>({
|
||||
defaultValues: appWorkflow2Form({
|
||||
nodes: appDetail.modules
|
||||
})
|
||||
});
|
||||
|
||||
// show selected dataset
|
||||
useMount(() => {
|
||||
loadAllDatasets();
|
||||
});
|
||||
|
||||
return (
|
||||
<Grid gridTemplateColumns={['1fr', '560px 1fr']} h={'100%'}>
|
||||
@@ -25,10 +44,10 @@ const SimpleEdit = ({ appId }: { appId: string }) => {
|
||||
<AppCard appId={appId} />
|
||||
|
||||
<Box mt={2}>
|
||||
<EditForm divRef={divRef} isSticky={isSticky} />
|
||||
<EditForm editForm={editForm} divRef={divRef} isSticky={isSticky} />
|
||||
</Box>
|
||||
</Box>
|
||||
{isPc && <ChatTest appId={appId} />}
|
||||
{isPc && <ChatTest editForm={editForm} appId={appId} />}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
@@ -21,7 +21,7 @@ import { useTranslation } from 'next-i18next';
|
||||
const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
|
||||
loading: () => <Loading />
|
||||
});
|
||||
const OutLink = dynamic(() => import('./components/OutLink'), {});
|
||||
const Publish = dynamic(() => import('./components/Publish'), {});
|
||||
const Logs = dynamic(() => import('./components/Logs'), {});
|
||||
|
||||
enum TabEnum {
|
||||
@@ -39,7 +39,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const { appId } = router.query as { appId: string };
|
||||
const { appDetail, loadAppDetail, clearAppModules } = useAppStore();
|
||||
const { appDetail, loadAppDetail } = useAppStore();
|
||||
|
||||
const setCurrentTab = useCallback(
|
||||
(tab: `${TabEnum}`) => {
|
||||
@@ -82,7 +82,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
|
||||
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
|
||||
|
||||
useQuery([appId], () => loadAppDetail(appId, true), {
|
||||
const { isSuccess, isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
|
||||
onError(err: any) {
|
||||
toast({
|
||||
title: err?.message || t('core.app.error.Get app failed'),
|
||||
@@ -100,88 +100,90 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
<Head>
|
||||
<title>{appDetail.name}</title>
|
||||
</Head>
|
||||
<PageContainer>
|
||||
<Flex flexDirection={['column', 'row']} h={'100%'}>
|
||||
{/* pc tab */}
|
||||
<Box
|
||||
display={['none', 'flex']}
|
||||
flexDirection={'column'}
|
||||
p={4}
|
||||
w={'180px'}
|
||||
borderRight={theme.borders.base}
|
||||
>
|
||||
<Flex mb={4} alignItems={'center'}>
|
||||
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'md'} />
|
||||
<Box ml={2} fontWeight={'bold'}>
|
||||
<PageContainer isLoading={isLoading}>
|
||||
{isSuccess && (
|
||||
<Flex flexDirection={['column', 'row']} h={'100%'}>
|
||||
{/* pc tab */}
|
||||
<Box
|
||||
display={['none', 'flex']}
|
||||
flexDirection={'column'}
|
||||
p={4}
|
||||
w={'180px'}
|
||||
borderRight={theme.borders.base}
|
||||
>
|
||||
<Flex mb={4} alignItems={'center'}>
|
||||
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'md'} />
|
||||
<Box ml={2} fontWeight={'bold'}>
|
||||
{appDetail.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
<SideTabs
|
||||
flex={1}
|
||||
mx={'auto'}
|
||||
mt={2}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => {
|
||||
if (e === 'startChat') {
|
||||
router.push(`/chat?appId=${appId}`);
|
||||
} else {
|
||||
setCurrentTab(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
py={2}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
onClick={() => router.replace('/app/list')}
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
size={'smSquare'}
|
||||
borderRadius={'50%'}
|
||||
aria-label={''}
|
||||
/>
|
||||
{t('app.My Apps')}
|
||||
</Flex>
|
||||
</Box>
|
||||
{/* phone tab */}
|
||||
<Box display={['block', 'none']} textAlign={'center'} py={3}>
|
||||
<Box className="textlg" fontSize={'xl'} fontWeight={'bold'}>
|
||||
{appDetail.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
<SideTabs
|
||||
flex={1}
|
||||
mx={'auto'}
|
||||
mt={2}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => {
|
||||
if (e === 'startChat') {
|
||||
router.push(`/chat?appId=${appId}`);
|
||||
} else {
|
||||
setCurrentTab(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
py={2}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
onClick={() => router.replace('/app/list')}
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
size={'smSquare'}
|
||||
borderRadius={'50%'}
|
||||
aria-label={''}
|
||||
<Tabs
|
||||
mx={'auto'}
|
||||
mt={2}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
size={'sm'}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => {
|
||||
if (e === 'startChat') {
|
||||
router.push(`/chat?appId=${appId}`);
|
||||
} else {
|
||||
setCurrentTab(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{t('app.My Apps')}
|
||||
</Flex>
|
||||
</Box>
|
||||
{/* phone tab */}
|
||||
<Box display={['block', 'none']} textAlign={'center'} py={3}>
|
||||
<Box className="textlg" fontSize={'xl'} fontWeight={'bold'}>
|
||||
{appDetail.name}
|
||||
</Box>
|
||||
<Tabs
|
||||
mx={'auto'}
|
||||
mt={2}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
size={'sm'}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => {
|
||||
if (e === 'startChat') {
|
||||
router.push(`/chat?appId=${appId}`);
|
||||
} else {
|
||||
setCurrentTab(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
|
||||
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
|
||||
{currentTab === TabEnum.adEdit && appDetail && (
|
||||
<FlowEdit app={appDetail} onClose={onCloseFlowEdit} />
|
||||
)}
|
||||
{currentTab === TabEnum.logs && <Logs appId={appId} />}
|
||||
{currentTab === TabEnum.publish && <OutLink appId={appId} />}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
|
||||
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
|
||||
{currentTab === TabEnum.adEdit && appDetail && (
|
||||
<FlowEdit app={appDetail} onClose={onCloseFlowEdit} />
|
||||
)}
|
||||
{currentTab === TabEnum.logs && <Logs appId={appId} />}
|
||||
{currentTab === TabEnum.publish && <Publish appId={appId} />}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</PageContainer>
|
||||
</>
|
||||
);
|
||||
|
@@ -11,14 +11,12 @@ import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
|
||||
import { putUpdatePlugin } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import {
|
||||
getWorkflowStore,
|
||||
useFlowProviderStore
|
||||
} from '@/components/core/workflow/Flow/FlowProvider';
|
||||
import {
|
||||
checkWorkflowNodeAndConnection,
|
||||
filterSensitiveNodesData
|
||||
} from '@/web/core/workflow/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
|
||||
|
||||
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
|
||||
|
||||
@@ -29,11 +27,13 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { copyData } = useCopyData();
|
||||
const { edges, onUpdateNodeError } = useFlowProviderStore();
|
||||
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
|
||||
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
|
||||
const flowData2StoreDataAndCheck = useCallback(async () => {
|
||||
const { nodes } = await getWorkflowStore();
|
||||
|
||||
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
||||
if (!checkResults) {
|
||||
const storeNodes = flowNode2StoreNodes({ nodes, edges });
|
||||
|
@@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Header from './Header';
|
||||
import Flow from '@/components/core/workflow/Flow';
|
||||
import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider';
|
||||
import { pluginSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -14,6 +13,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
type Props = { pluginId: string };
|
||||
|
||||
@@ -21,7 +22,7 @@ const Render = ({ pluginId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { initData } = useFlowProviderStore();
|
||||
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
|
||||
|
||||
const { data: pluginDetail } = useQuery(
|
||||
['getOnePlugin', pluginId],
|
||||
@@ -78,9 +79,11 @@ const Render = ({ pluginId }: Props) => {
|
||||
|
||||
export default function FlowEdit(props: any) {
|
||||
return (
|
||||
<FlowProvider mode={'plugin'} basicNodeTemplates={pluginSystemModuleTemplates}>
|
||||
<WorkflowContextProvider
|
||||
value={{ mode: 'plugin', basicNodeTemplates: pluginSystemModuleTemplates }}
|
||||
>
|
||||
<Render {...props} />
|
||||
</FlowProvider>
|
||||
</WorkflowContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user