4.6.7-alpha commit (#743)

Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-01-19 11:17:28 +08:00
committed by GitHub
parent 8ee7407c4c
commit c031e6dcc9
324 changed files with 8509 additions and 4757 deletions

View File

@@ -56,7 +56,7 @@ const FeedbackModal = ({
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('Cancel')}
{t('common.Close')}
</Button>
<Button isLoading={isLoading} onClick={mutate}>
{t('core.chat.Feedback Submit')}

View File

@@ -12,6 +12,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import ChatBoxDivider from '@/components/core/chat/Divider';
import { strIsLink } from '@fastgpt/global/common/string/tools';
import MyIcon from '@fastgpt/web/components/common/Icon';
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
@@ -123,7 +124,7 @@ const ResponseTags = ({
});
}}
>
<Image src={item.icon} alt={''} mr={1} flexShrink={0} w={'12px'} />
<MyIcon name={item.icon as any} mr={1} flexShrink={0} w={'12px'} />
<Box className="textEllipsis3" wordBreak={'break-all'} flex={'1 0 0'}>
{item.sourceName}
</Box>

View File

@@ -3,7 +3,7 @@ import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from
import { useTranslation } from 'next-i18next';
import Avatar from '../Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
import dynamic from 'next/dynamic';
import { AdminFbkType } from '@fastgpt/global/core/chat/type.d';
@@ -46,7 +46,7 @@ const SelectMarkCollection = ({
paths={paths}
onClose={onClose}
setParentId={setParentId}
tips={t('core.chat.Select Mark Kb Desc')}
tips={t('core.chat.Select dataset Desc')}
>
<ModalBody flex={'1 0 0'} overflowY={'auto'}>
<Grid

View File

@@ -11,7 +11,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import Markdown from '../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
function Row({
label,
@@ -141,6 +141,7 @@ const ResponseBox = React.memo(function ResponseBox({
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
<Row label={t('wallet.bill.Output Token Length')} value={`${activeModule?.outputTokens}`} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />

View File

@@ -174,7 +174,7 @@ const Navbar = ({ unread }: { unread: number }) => {
mb={0}
color={'myGray.500'}
>
<MyIcon name={'common/courseLight'} width={'26px'} height={'26px'} />
<MyIcon name={'common/courseLight'} width={'24px'} height={'24px'} />
</Link>
</MyTooltip>
)}
@@ -189,7 +189,7 @@ const Navbar = ({ unread }: { unread: number }) => {
mt={0}
color={'myGray.500'}
>
<MyIcon name={'common/gitLight'} width={'22px'} height={'22px'} />
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
</Link>
</MyTooltip>
)}

View File

@@ -15,6 +15,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
{
label: t('navbar.Chat'),
icon: 'core/chat/chatLight',
activeIcon: 'core/chat/chatFill',
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
activeLink: ['/chat'],
unread: 0
@@ -22,20 +23,23 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
{
label: t('navbar.Apps'),
icon: 'core/app/aiLight',
activeIcon: 'core/app/aiFill',
link: `/app/list`,
activeLink: ['/app/list', '/app/detail'],
unread: 0
},
{
label: t('navbar.Tools'),
icon: 'phoneTabbar/more',
icon: 'phoneTabbar/tool',
activeIcon: 'phoneTabbar/toolFill',
link: '/tools',
activeLink: ['/tools'],
unread: 0
},
{
label: t('navbar.Account'),
icon: 'phoneTabbar/me',
icon: 'support/user/userLight',
activeIcon: 'support/user/userFill',
link: '/account',
activeLink: ['/account'],
unread
@@ -68,35 +72,24 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
transform={'scale(0.9)'}
{...(item.activeLink.includes(router.pathname)
? {
color: '#7089f1'
color: 'primary.600'
}
: {
color: 'myGray.500'
})}
_after={
item.activeLink.includes(router.pathname)
? {
content: '""',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%,-50%)',
borderRadius: '50%',
w: '18px',
h: '18px',
bg: ' #6782f1',
filter: 'blur(10px)',
boxShadow: '0px 2px 4px 0px rgba(0, 0, 0, 0.25)'
}
: {}
}
onClick={() => {
if (item.link === router.asPath) return;
router.push(item.link);
}}
>
<Badge isDot count={item.unread}>
<MyIcon name={item.icon as any} width={'20px'} height={'20px'} />
<MyIcon
name={
(item.activeLink.includes(router.pathname) ? item.activeIcon : item.icon) as any
}
width={'20px'}
height={'20px'}
/>
<Box fontSize={'12px'}>{item.label}</Box>
</Badge>
</Flex>

View File

@@ -17,6 +17,7 @@ const Loading = ({
position={fixed ? 'fixed' : 'absolute'}
zIndex={zIndex}
bg={bg}
borderRadius={'md'}
top={0}
left={0}
right={0}

View File

@@ -19,8 +19,8 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
display: 'flex',
alignItems: 'center',
_hover: {
backgroundColor: 'myWhite.600',
color: 'hover.blue'
backgroundColor: 'myGray.05',
color: 'primary.600'
}
};
@@ -41,7 +41,7 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
e.stopPropagation();
item.onClick && item.onClick();
}}
color={item.isActive ? 'primary.500' : ''}
color={item.isActive ? 'primary.700' : 'myGray.600'}
whiteSpace={'pre-wrap'}
>
{item.child}

View File

@@ -1,16 +1,7 @@
import React from 'react';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalContentProps,
Box,
Image
} from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { ModalContentProps } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import CustomModal from '@fastgpt/web/components/common/MyModal';
export interface MyModalProps extends ModalContentProps {
iconSrc?: string;
@@ -33,59 +24,18 @@ const MyModal = ({
}: MyModalProps) => {
const { isPc } = useSystemStore();
return (
<Modal
<CustomModal
isOpen={isOpen}
onClose={() => onClose && onClose()}
autoFocus={false}
onClose={onClose}
iconSrc={iconSrc}
title={title}
isCentered={isPc ? isCentered : true}
w={w}
maxW={maxW}
{...props}
>
<ModalOverlay />
<ModalContent
w={w}
minW={['90vw', '400px']}
maxW={maxW}
position={'relative'}
maxH={'85vh'}
{...props}
>
{!title && onClose && <ModalCloseButton zIndex={1} />}
{!!title && (
<ModalHeader
display={'flex'}
alignItems={'center'}
fontWeight={500}
background={'#FBFBFC'}
borderBottom={'1px solid #F4F6F8'}
roundedTop={'lg'}
py={'10px'}
>
{iconSrc && (
<>
{iconSrc.startsWith('/') ? (
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
) : (
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
)}
</>
)}
{title}
<Box flex={1} />
{onClose && (
<ModalCloseButton position={'relative'} fontSize={'sm'} top={0} right={0} />
)}
</ModalHeader>
)}
<Box
overflow={props.overflow || 'overlay'}
h={'100%'}
display={'flex'}
flexDirection={'column'}
>
{children}
</Box>
</ModalContent>
</Modal>
{children}
</CustomModal>
);
};

View File

@@ -25,9 +25,9 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
color: '#A558C9'
},
gray: {
borderColor: '#979797',
bg: '#F7F7F7',
color: '#979797'
borderColor: 'borderColor.base',
bg: 'myGray.50',
color: 'myGray.700'
}
};
return map[colorSchema];
@@ -35,14 +35,14 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
return (
<Flex
border={'1px solid'}
{...theme}
borderWidth={'1px'}
px={2}
lineHeight={1}
py={1}
borderRadius={'sm'}
fontSize={'xs'}
alignItems={'center'}
{...theme}
{...props}
>
{children}

View File

@@ -87,7 +87,7 @@ const MyRadio = ({
<Box pr={hiddenCircle ? 0 : 2} color={'myGray.800'}>
<Box>{typeof item.title === 'string' ? t(item.title) : item.title}</Box>
{!!item.desc && (
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
<Box fontSize={'xs'} color={'myGray.500'} lineHeight={1.2}>
{t(item.desc)}
</Box>
)}

View File

@@ -0,0 +1,95 @@
import React, { useRef } from 'react';
import {
Box,
Button,
ModalBody,
ModalFooter,
Textarea,
TextareaProps,
useDisclosure
} from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@/components/MyModal';
type Props = TextareaProps & {
title?: string;
// variables: string[];
};
const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTextarea(props, ref) {
const ModalTextareaRef = useRef<HTMLTextAreaElement>(null);
const TextareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const { title = t('core.app.edit.Prompt Editor'), ...childProps } = props;
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
{isOpen && (
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
<ModalBody>
<Editor
textareaRef={ModalTextareaRef}
{...childProps}
minH={'300px'}
maxH={'auto'}
minW={['100%', '512px']}
/>
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
if (ModalTextareaRef.current && TextareaRef.current) {
TextareaRef.current.value = ModalTextareaRef.current.value;
}
onClose();
}}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</>
);
});
export default React.memo(MyTextarea);
const Editor = React.memo(function Editor({
onOpenModal,
textareaRef,
...props
}: Props & {
textareaRef: React.RefObject<HTMLTextAreaElement>;
onOpenModal?: () => void;
}) {
const { t } = useTranslation();
return (
<Box h={'100%'} w={'100%'} position={'relative'}>
<Textarea ref={textareaRef} maxW={'100%'} {...props} />
{onOpenModal && (
<Box
zIndex={1}
position={'absolute'}
bottom={1}
right={2}
cursor={'pointer'}
onClick={onOpenModal}
>
<MyTooltip label={t('common.ui.textarea.Magnifying')}>
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
</MyTooltip>
</Box>
)}
</Box>
);
});

View File

@@ -1,97 +0,0 @@
import React, { useRef } from 'react';
import {
Box,
Button,
ModalBody,
ModalFooter,
Textarea,
TextareaProps,
useDisclosure
} from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@/components/MyModal';
type Props = TextareaProps & {
title?: string;
// variables: string[];
};
const PromptTextarea = React.forwardRef<HTMLTextAreaElement, Props>(
function PromptTextarea(props, ref) {
const ModalTextareaRef = useRef<HTMLTextAreaElement>(null);
const TextareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const { title = t('core.app.edit.Prompt Editor'), ...childProps } = props;
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
{isOpen && (
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
<ModalBody>
<Editor
textareaRef={ModalTextareaRef}
{...childProps}
minH={'300px'}
maxH={'auto'}
minW={['100%', '512px']}
/>
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
if (ModalTextareaRef.current && TextareaRef.current) {
TextareaRef.current.value = ModalTextareaRef.current.value;
}
onClose();
}}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</>
);
}
);
export default React.memo(PromptTextarea);
const Editor = React.memo(function Editor({
onOpenModal,
textareaRef,
...props
}: Props & {
textareaRef: React.RefObject<HTMLTextAreaElement>;
onOpenModal?: () => void;
}) {
const { t } = useTranslation();
return (
<Box h={'100%'} w={'100%'} position={'relative'}>
<Textarea ref={textareaRef} maxW={'100%'} {...props} />
{onOpenModal && (
<Box
zIndex={1}
position={'absolute'}
bottom={1}
right={2}
cursor={'pointer'}
onClick={onOpenModal}
>
<MyTooltip label={t('common.ui.textarea.Magnifying')}>
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
</MyTooltip>
</Box>
)}
</Box>
);
});

View File

@@ -1,9 +1,9 @@
import { Box, Flex, FlexProps } from '@chakra-ui/react';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import React from 'react';
import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
const DatasetTypeTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps) => {
const { t } = useTranslation();

View File

@@ -8,7 +8,7 @@ import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic';
import MyBox from '@/components/common/MyBox';
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constant';
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
@@ -102,6 +102,7 @@ const QuoteItem = ({
overflow={'hidden'}
fontSize={'sm'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
_hover={{ '& .hover-data': { display: 'flex' } }}
h={'100%'}
display={'flex'}
@@ -220,7 +221,6 @@ const QuoteItem = ({
display={['flex', 'none']}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}

View File

@@ -7,6 +7,7 @@ import { useTranslation } from 'next-i18next';
import { getFileAndOpen } from '@/web/core/dataset/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import MyIcon from '@fastgpt/web/components/common/Icon';
type Props = BoxProps & {
sourceName?: string;
@@ -52,7 +53,7 @@ const RawSourceBox = ({ sourceId, sourceName = '', canView = true, ...props }: P
: {})}
{...props}
>
<Image src={icon} alt="" w={['14px', '16px']} mr={2} />
<MyIcon name={icon as any} w={['14px', '16px']} mr={2} />
<Box
maxW={['200px', '300px']}
className={props.className ?? 'textEllipsis'}

View File

@@ -38,7 +38,7 @@ const DatasetSelectContainer = ({
parentId: path.parentId,
parentName: path.parentName
}))}
FirstPathDom={t('core.chat.Select Mark Kb')}
FirstPathDom={t('core.chat.Select dataset')}
onClick={(e) => {
setParentId(e);
}}

View File

@@ -26,7 +26,8 @@ import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d'
import type { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { getDocPath } from '@/web/common/system/doc';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { PickerMenuItemType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
@@ -35,13 +36,15 @@ const AIChatSettingsModal = ({
onClose,
onSuccess,
defaultData,
simpleModeTemplate = SimpleModeTemplate_FastGPT_Universal
simpleModeTemplate = SimpleModeTemplate_FastGPT_Universal,
pickerMenu = []
}: {
isAdEdit?: boolean;
onClose: () => void;
onSuccess: (e: AIChatModuleProps) => void;
defaultData: AIChatModuleProps;
simpleModeTemplate?: AppSimpleEditConfigTemplateType;
pickerMenu?: PickerMenuItemType[];
}) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
@@ -60,7 +63,44 @@ const AIChatSettingsModal = ({
chatModelList.find((item) => item.model === getValues(ModuleInputKeyEnum.aiModel))
?.maxResponse || 4000
);
}, [getValues, refresh]);
}, [getValues]);
const quoteTemplateVariables = (() => [
...pickerMenu,
{
key: 'q',
label: 'q',
icon: 'core/app/simpleMode/variable'
},
{
key: 'a',
label: 'a',
icon: 'core/app/simpleMode/variable'
},
{
key: 'source',
label: t('core.dataset.search.Source name'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'sourceId',
label: t('core.dataset.search.Source id'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'index',
label: t('core.dataset.search.Quote index'),
icon: 'core/app/simpleMode/variable'
}
])();
const quotePromptVariables = (() => [
...pickerMenu,
{
key: 'quote',
label: t('core.app.Quote templates'),
icon: 'core/app/simpleMode/variable'
}
])();
const LabelStyles: BoxProps = {
fontSize: ['sm', 'md']
@@ -76,7 +116,7 @@ const AIChatSettingsModal = ({
iconSrc="/imgs/module/AI.png"
title={
<>
{t('app.AI Advanced Settings')}
{t('common.More settings')}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/use-cases/ai_settings/')}
@@ -86,7 +126,7 @@ const AIChatSettingsModal = ({
fontWeight={'normal'}
fontSize={'md'}
>
{t('common.Read intro')}
</Link>
)}
</>
@@ -99,7 +139,7 @@ const AIChatSettingsModal = ({
{isAdEdit && (
<Flex alignItems={'center'}>
<Box {...LabelStyles} w={'80px'}>
AI内容
{t('core.app.Ai response')}
</Box>
<Box flex={1} ml={'10px'}>
<Switch
@@ -117,13 +157,13 @@ const AIChatSettingsModal = ({
{simpleModeTemplate?.systemForm?.aiSettings?.temperature && (
<Flex alignItems={'center'} mb={10} mt={isAdEdit ? 8 : 5}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '严谨', value: 0 },
{ label: '发散', value: 10 }
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
@@ -140,7 +180,7 @@ const AIChatSettingsModal = ({
{simpleModeTemplate?.systemForm?.aiSettings?.maxToken && (
<Flex alignItems={'center'} mt={12} mb={10}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Max tokens')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
@@ -165,7 +205,7 @@ const AIChatSettingsModal = ({
{simpleModeTemplate?.systemForm?.aiSettings?.quoteTemplate && (
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<MyTooltip
label={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
@@ -179,24 +219,24 @@ const AIChatSettingsModal = ({
{...selectTemplateBtn}
onClick={() =>
setSelectTemplateData({
title: '选择知识库提示词模板',
title: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptTextarea
bg={'myWhite.400'}
rows={8}
<PromptEditor
variables={quoteTemplateVariables}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
defaultValue={getValues(ModuleInputKeyEnum.aiChatQuoteTemplate)}
onBlur={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e.target.value);
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e);
setRefresh(!refresh);
}}
/>
@@ -205,7 +245,7 @@ const AIChatSettingsModal = ({
{simpleModeTemplate?.systemForm?.aiSettings?.quotePrompt && (
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
@@ -213,16 +253,16 @@ const AIChatSettingsModal = ({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptTextarea
bg={'myWhite.400'}
rows={11}
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={220}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
defaultValue={getValues(ModuleInputKeyEnum.aiChatQuotePrompt)}
onBlur={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e.target.value);
setRefresh(!refresh);
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e);
}}
/>
</Box>
@@ -230,10 +270,10 @@ const AIChatSettingsModal = ({
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('Cancel')}
{t('common.Close')}
</Button>
<Button ml={4} onClick={handleSubmit(onSuccess)}>
{t('Confirm')}
{t('common.Confirm')}
</Button>
</ModalFooter>
{!!selectTemplateData && (

View File

@@ -15,12 +15,12 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import { reRankModelList } from '@/web/common/system/staticData';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import MyRadio from '@/components/common/MyRadio';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -136,7 +136,7 @@ const DatasetParamsModal = ({
{limit !== undefined && (
<Box display={['block', 'flex']} py={8} mt={3}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Max Tokens')}
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
@@ -162,7 +162,7 @@ const DatasetParamsModal = ({
)}
{showSimilarity && (
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<Box flex={'0 0 120px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
@@ -189,7 +189,7 @@ const DatasetParamsModal = ({
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>
<Box flex={'0 0 120px'} mb={[2, 0]}>
{t('core.dataset.search.Empty result response')}
</Box>
<Box flex={1}>

View File

@@ -15,7 +15,7 @@ import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import { useToast } from '@/web/common/hooks/useToast';
import MyTooltip from '@/components/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';

View File

@@ -6,7 +6,6 @@ import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
import Avatar from '@/components/Avatar';
import { useTranslation } from 'next-i18next';
import { useLoading } from '@/web/common/hooks/useLoading';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useAppStore } from '@/web/core/app/store/useAppStore';
const SelectAppModal = ({
@@ -84,7 +83,7 @@ const SelectAppModal = ({
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('Cancel')}
{t('common.Close')}
</Button>
<Button
ml={2}
@@ -101,7 +100,7 @@ const SelectAppModal = ({
onClose();
}}
>
{t('Confirm')}
{t('common.Confirm')}
</Button>
</ModalFooter>
<Loading loading={isLoading} fixed={false} />

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
import { onDelConnect } from '../../FlowProvider';
import { Flex } from '@chakra-ui/react';
@@ -17,7 +17,7 @@ const ButtonEdge = (props: EdgeProps) => {
style = {}
} = props;
const [edgePath, labelX, labelY] = getBezierPath({
const [labelX, labelY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
@@ -26,19 +26,8 @@ const ButtonEdge = (props: EdgeProps) => {
targetPosition
});
const edgeStyle: React.CSSProperties = {
...style,
...(selected
? {
strokeWidth: 4,
stroke: '#3370ff'
}
: { strokeWidth: 2, stroke: '#BDC1C5' })
};
return (
<>
<BezierEdge {...props} style={edgeStyle} />
const memoEdgeLabel = useMemo(() => {
return (
<EdgeLabelRenderer>
<Flex
alignItems={'center'}
@@ -66,6 +55,27 @@ const ButtonEdge = (props: EdgeProps) => {
></MyIcon>
</Flex>
</EdgeLabelRenderer>
);
}, [id, labelX, labelY, selected]);
const memoBezierEdge = useMemo(() => {
const edgeStyle: React.CSSProperties = {
...style,
...(selected
? {
strokeWidth: 4,
stroke: '#3370ff'
}
: { strokeWidth: 2, stroke: '#BDC1C5' })
};
return <BezierEdge {...props} style={edgeStyle} />;
}, [props, selected, style]);
return (
<>
{memoBezierEdge}
{memoEdgeLabel}
</>
);
};

View File

@@ -9,7 +9,7 @@ const QGSwitch = (props: SwitchProps) => {
const { t } = useTranslation();
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/questionGuide'} mr={2} w={'16px'} />
<MyIcon name={'core/app/questionGuide'} mr={2} w={'20px'} />
<Box>{t('core.app.Next Step Guide')}</Box>
<MyTooltip label={t('core.app.Question Guide Tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />

View File

@@ -71,7 +71,7 @@ const TTSSelect = ({
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/tts'} mr={2} w={'16px'} />
<MyIcon name={'core/app/simpleMode/tts'} mr={2} w={'20px'} />
<Box>{t('core.app.TTS')}</Box>
<MyTooltip label={t('core.app.TTS Tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
@@ -93,7 +93,7 @@ const TTSSelect = ({
<MyModal
title={
<>
<MyIcon name={'core/app/tts'} mr={2} w={'20px'} />
<MyIcon name={'core/app/simpleMode/tts'} mr={2} w={'20px'} />
{t('core.app.TTS')}
</>
}

View File

@@ -38,6 +38,7 @@ import { variableTip } from '@fastgpt/global/core/module/template/tip';
import { useTranslation } from 'next-i18next';
import { useToast } from '@/web/common/hooks/useToast';
import MyRadio from '@/components/common/MyRadio';
import { formatVariablesIcon } from '@fastgpt/global/core/module/utils';
const VariableEdit = ({
variables,
@@ -101,16 +102,13 @@ const VariableEdit = ({
};
const formatVariables = useMemo(() => {
return variables.map((item) => ({
...item,
icon: VariableTypeList.find((type) => type.value === item.type)?.icon
}));
}, [VariableTypeList, variables]);
return formatVariablesIcon(variables);
}, [variables]);
return (
<Box>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
<MyIcon name={'core/app/simpleMode/variable'} w={'20px'} />
<Box ml={2} flex={1}>
{t('core.module.Variable')}
<MyTooltip label={variableTip} forceShow>
@@ -179,7 +177,7 @@ const VariableEdit = ({
</Box>
)}
<MyModal
iconSrc="/imgs/module/variable.png"
iconSrc="core/app/simpleMode/variable"
title={t('core.module.Variable Setting')}
isOpen={isOpenEdit}
onClose={onCloseEdit}
@@ -342,6 +340,6 @@ export const defaultVariable: VariableItemType = {
enums: [{ value: '' }]
};
export const addVariable = () => {
const newVariable = { ...defaultVariable, key: nanoid(), id: '' };
const newVariable = { ...defaultVariable, key: '', id: '' };
return newVariable;
};

View File

@@ -126,7 +126,7 @@ const NodeCQNode = React.memo(function NodeCQNode({ data }: { data: FlowModuleIt
});
}}
>
{t('core.module.Add question type')}
</Button>
</Box>
);

View File

@@ -1,23 +1,10 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useTransition } from 'react';
import { NodeProps } from 'reactflow';
import {
Box,
Flex,
Textarea,
useTheme,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Switch
} from '@chakra-ui/react';
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { welcomeTextTip, variableTip } from '@fastgpt/global/core/module/template/tip';
import { welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
import { onChangeNode } from '../../FlowProvider';
import VariableEdit from '../modules/VariableEdit';
@@ -29,6 +16,7 @@ import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
import { useTranslation } from 'next-i18next';
const NodeUserGuide = React.memo(function NodeUserGuide({ data }: { data: FlowModuleItemType }) {
const theme = useTheme();
@@ -56,19 +44,18 @@ export default function Node({ data }: NodeProps<FlowModuleItemType>) {
return <NodeUserGuide data={data} />;
}
export function WelcomeText({ data }: { data: FlowModuleItemType }) {
const { t } = useTranslation();
const { inputs, moduleId } = data;
const [, startTst] = useTransition();
const welcomeText = useMemo(
() => inputs.find((item) => item.key === ModuleInputKeyEnum.welcomeText),
[inputs]
);
const welcomeText = inputs.find((item) => item.key === ModuleInputKeyEnum.welcomeText);
return (
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'core/modules/welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<Box>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={t(welcomeTextTip)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
@@ -79,16 +66,18 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
placeholder={t(welcomeTextTip)}
onChange={(e) => {
onChangeNode({
moduleId,
key: ModuleInputKeyEnum.welcomeText,
type: 'updateInput',
value: {
...welcomeText,
value: e.target.value
}
startTst(() => {
onChangeNode({
moduleId,
key: ModuleInputKeyEnum.welcomeText,
type: 'updateInput',
value: {
...welcomeText,
value: e.target.value
}
});
});
}}
/>

View File

@@ -1,60 +0,0 @@
/* Abandon */
import React, { useCallback, useMemo, useState } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import NodeCard from '../../render/NodeCard';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import Container from '../../modules/Container';
import { VariableInputEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import VariableEditModal, { addVariable } from '../../modules/VariableEdit';
import { onChangeNode } from '../../../FlowProvider';
export const defaultVariable: VariableItemType = {
id: nanoid(),
key: 'key',
label: 'label',
type: VariableInputEnum.input,
required: true,
maxLen: 50,
enums: [{ value: '' }]
};
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId } = data;
const variables = useMemo(
() =>
(inputs.find((item) => item.key === ModuleInputKeyEnum.variables)
?.value as VariableItemType[]) || [],
[inputs]
);
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<VariableEditModal
variables={variables}
onChange={(e) =>
onChangeNode({
moduleId,
key: ModuleInputKeyEnum.variables,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === ModuleInputKeyEnum.variables),
value: e
}
})
}
/>
</Container>
</NodeCard>
</>
);
};
export default React.memo(NodeUserGuide);

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
import { Box } from '@chakra-ui/react';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
@@ -6,7 +6,7 @@ import dynamic from 'next/dynamic';
import InputLabel from './Label';
import type { RenderInputProps } from './type.d';
import { getFlowStore, type useFlowProviderStoreType } from '../../../FlowProvider';
import { useFlowProviderStore } from '../../../FlowProvider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const RenderList: {
@@ -77,8 +77,8 @@ type Props = {
moduleId: string;
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
};
const RenderInput = ({ flowInputList, moduleId, CustomComponent = {} }: Props) => {
const [mode, setMode] = useState<useFlowProviderStoreType['mode']>('app');
const RenderInput = ({ flowInputList, moduleId, CustomComponent }: Props) => {
const { mode } = useFlowProviderStore();
const sortInputs = useMemo(
() =>
@@ -109,48 +109,43 @@ const RenderInput = ({ flowInputList, moduleId, CustomComponent = {} }: Props) =
[mode, sortInputs]
);
useEffect(() => {
async () => {
const { mode } = await getFlowStore();
setMode(mode);
};
}, []);
const memoCustomComponent = useMemo(() => CustomComponent || {}, [CustomComponent]);
return (
<>
{filterInputs.map((input) => {
const RenderComponent = (() => {
if (input.type === FlowNodeInputTypeEnum.custom && CustomComponent[input.key]) {
return <>{CustomComponent[input.key]({ ...input })}</>;
}
const Component = RenderList.find((item) => item.types.includes(input.type))?.Component;
const Render = useMemo(() => {
return filterInputs.map((input) => {
const RenderComponent = (() => {
if (input.type === FlowNodeInputTypeEnum.custom && memoCustomComponent[input.key]) {
return <>{memoCustomComponent[input.key]({ ...input })}</>;
}
const Component = RenderList.find((item) => item.types.includes(input.type))?.Component;
if (!Component) return null;
return <Component inputs={filterInputs} item={input} moduleId={moduleId} />;
})();
if (!Component) return null;
return <Component inputs={filterInputs} item={input} moduleId={moduleId} />;
})();
return (
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
{input.key === ModuleInputKeyEnum.userChatInput && (
<UserChatInput inputs={filterInputs} item={input} moduleId={moduleId} />
)}
{input.type !== FlowNodeInputTypeEnum.hidden && (
<>
{!!input.label && (
<InputLabel moduleId={moduleId} inputKey={input.key} mode={mode} {...input} />
)}
{!!RenderComponent && (
<Box mt={2} className={'nodrag'}>
{RenderComponent}
</Box>
)}
</>
)}
</Box>
);
})}
</>
);
return (
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
{input.key === ModuleInputKeyEnum.userChatInput && (
<UserChatInput inputs={filterInputs} item={input} moduleId={moduleId} />
)}
{input.type !== FlowNodeInputTypeEnum.hidden && (
<>
{!!input.label && (
<InputLabel moduleId={moduleId} inputKey={input.key} mode={mode} {...input} />
)}
{!!RenderComponent && (
<Box mt={2} className={'nodrag'}>
{RenderComponent}
</Box>
)}
</>
)}
</Box>
);
});
}, [memoCustomComponent, filterInputs, mode, moduleId]);
return <>{Render}</>;
};
export default React.memo(RenderInput);

View File

@@ -1,20 +1,39 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { getFlowStore, onChangeNode, useFlowProviderStoreType } from '../../../../FlowProvider';
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@fastgpt/global/core/module/api';
import Avatar from '@/components/Avatar';
import DatasetSelectModal from '@/components/core/module/DatasetSelectModal';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { chatModelList } from '@/web/common/system/staticData';
const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
const SelectDatasetRender = ({ inputs = [], item, moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const theme = useTheme();
const [nodes, setNodes] = useState<useFlowProviderStoreType['nodes']>([]);
const [data, setData] = useState({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,
similarity: 0.5,
usingReRank: false
});
const { allDatasets, loadAllDatasets } = useDatasetStore();
const {
isOpen: isOpenKbSelect,
onOpen: onOpenKbSelect,
onClose: onCloseKbSelect
isOpen: isOpenDatasetSelect,
onOpen: onOpenDatasetSelect,
onClose: onCloseDatasetSelect
} = useDisclosure();
const selectedDatasets = useMemo(() => {
@@ -22,14 +41,68 @@ const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
}, [allDatasets, item.value]);
const tokenLimit = useMemo(() => {
let maxTokens = 3000;
nodes.forEach((item) => {
if (item.type === FlowNodeTypeEnum.chatNode) {
const model =
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
const quoteMaxToken =
chatModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
maxTokens = Math.max(maxTokens, quoteMaxToken);
}
});
return maxTokens;
}, [nodes]);
const {
isOpen: isOpenDatasetPrams,
onOpen: onOpenDatasetParams,
onClose: onCloseDatasetParams
} = useDisclosure();
useQuery(['loadAllDatasets'], loadAllDatasets);
useEffect(() => {
inputs.forEach((input) => {
// @ts-ignore
if (data[input.key] !== undefined) {
setData((state) => ({
...state,
[input.key]: input.value
}));
}
});
}, [inputs]);
useEffect(() => {
async () => {
const { nodes } = await getFlowStore();
setNodes(nodes);
};
}, []);
return (
<>
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
<Button h={'36px'} onClick={onOpenKbSelect}>
<Button
h={'36px'}
leftIcon={<MyIcon name={'common/selectLight'} w={'14px'} />}
onClick={onOpenDatasetSelect}
>
{t('common.Choose')}
</Button>
{/* <Button
h={'36px'}
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpenDatasetParams}
>
{t('core.dataset.search.Params Setting')}
</Button> */}
{selectedDatasets.map((item) => (
<Flex
key={item._id}
@@ -53,9 +126,9 @@ const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
</Flex>
))}
</Grid>
{isOpenKbSelect && (
{isOpenDatasetSelect && (
<DatasetSelectModal
isOpen={isOpenKbSelect}
isOpen={isOpenDatasetSelect}
defaultSelectedDatasets={item.value}
onChange={(e) => {
onChangeNode({
@@ -68,9 +141,32 @@ const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
}
});
}}
onClose={onCloseKbSelect}
onClose={onCloseDatasetSelect}
/>
)}
{/* {isOpenDatasetPrams && (
<DatasetParamsModal
{...data}
maxTokens={tokenLimit}
onClose={onCloseDatasetParams}
onSuccess={(e) => {
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...item,
//@ts-ignore
value: e[key]
}
});
}
}}
/>
)} */}
</>
);
};

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { getFlowStore, onChangeNode, useFlowProviderStoreType } from '../../../../FlowProvider';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import { Button, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { chatModelList } from '@/web/common/system/staticData';
@@ -11,7 +11,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import DatasetParamsModal from '@/components/core/module/DatasetParamsModal';
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
const [nodes, setNodes] = useState<useFlowProviderStoreType['nodes']>([]);
const { nodes } = useFlowProviderStore();
const { t } = useTranslation();
const [data, setData] = useState({
@@ -52,47 +52,44 @@ const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
});
}, [inputs]);
useEffect(() => {
async () => {
const { nodes } = await getFlowStore();
setNodes(nodes);
};
}, []);
const Render = useMemo(() => {
return (
<>
<Button
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpen}
>
{t('core.dataset.search.Params Setting')}
</Button>
{isOpen && (
<DatasetParamsModal
{...data}
maxTokens={tokenLimit}
onClose={onClose}
onSuccess={(e) => {
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...item,
//@ts-ignore
value: e[key]
}
});
}
}}
/>
)}
</>
);
}, [data, inputs, isOpen, moduleId, onClose, onOpen, t, tokenLimit]);
return (
<>
<Button
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpen}
>
{t('core.dataset.search.Params Setting')}
</Button>
{isOpen && (
<DatasetParamsModal
{...data}
maxTokens={tokenLimit}
onClose={onClose}
onSuccess={(e) => {
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...item,
//@ts-ignore
value: e[key]
}
});
}
}}
/>
)}
</>
);
return Render;
};
export default React.memo(SelectDatasetParam);

View File

@@ -1,38 +1,53 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo, useTransition } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useFlowProviderStore, onChangeNode } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import {
formatVariablesIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/module/utils';
const TextareaRender = ({ item, moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const [, startTst] = useTransition();
const { nodes } = useFlowProviderStore();
const update = useCallback(
(value: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value
}
// get variable
const variables = useMemo(
() =>
formatVariablesIcon(
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
),
[nodes]
);
const onChange = useCallback(
(e: string) => {
startTst(() => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
});
},
[item, moduleId]
);
return (
<PromptTextarea
<PromptEditor
variables={variables}
title={t(item.label)}
rows={5}
bg={'myWhite.400'}
h={150}
placeholder={t(item.placeholder || '')}
resize={'both'}
defaultValue={item.value}
onBlur={(e) => {
update(e.target.value);
}}
onChange={onChange}
/>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useMemo } from 'react';
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
@@ -15,7 +15,6 @@ import 'reactflow/dist/style.css';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.userGuide]: dynamic(() => import('./components/nodes/NodeUserGuide')),
[FlowNodeTypeEnum.variable]: dynamic(() => import('./components/nodes/abandon/NodeVariable')),
[FlowNodeTypeEnum.questionInput]: dynamic(() => import('./components/nodes/NodeQuestionInput')),
[FlowNodeTypeEnum.historyNode]: NodeSimple,
[FlowNodeTypeEnum.chatNode]: NodeSimple,
@@ -38,6 +37,16 @@ const Container = React.memo(function Container() {
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } =
useFlowProviderStore();
const memoRenderTools = useMemo(
() => (
<>
<Background />
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
</>
),
[]
);
return (
<ReactFlow
ref={reactFlowWrapper}
@@ -64,8 +73,7 @@ const Container = React.memo(function Container() {
});
}}
>
<Background />
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
{memoRenderTools}
</ReactFlow>
);
});
@@ -81,48 +89,54 @@ const Flow = ({
onClose: onCloseTemplate
} = useDisclosure();
const memoRenderContainer = useMemo(() => {
return (
<Box
minH={'400px'}
flex={'1 0 0'}
w={'100%'}
h={0}
position={'relative'}
onContextMenu={(e) => {
e.preventDefault();
return false;
}}
>
{/* open module template */}
<IconButton
position={'absolute'}
top={5}
left={5}
size={'mdSquare'}
borderRadius={'50%'}
icon={<SmallCloseIcon fontSize={'26px'} />}
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
transition={'0.2s ease'}
aria-label={''}
zIndex={1}
boxShadow={'2px 2px 6px #85b1ff'}
onClick={() => {
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
}}
/>
<Container {...data} />
<ModuleTemplateList
templates={templates}
isOpen={isOpenTemplate}
onClose={onCloseTemplate}
/>
</Box>
);
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate, templates]);
return (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<ReactFlowProvider>
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{Header}
<Box
minH={'400px'}
flex={'1 0 0'}
w={'100%'}
h={0}
position={'relative'}
onContextMenu={(e) => {
e.preventDefault();
return false;
}}
>
{/* open module template */}
<IconButton
position={'absolute'}
top={5}
left={5}
size={'mdSquare'}
borderRadius={'50%'}
icon={<SmallCloseIcon fontSize={'26px'} />}
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
transition={'0.2s ease'}
aria-label={''}
zIndex={1}
boxShadow={'2px 2px 6px #85b1ff'}
onClick={() => {
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
}}
/>
<Container {...data} />
<ModuleTemplateList
templates={templates}
isOpen={isOpenTemplate}
onClose={onCloseTemplate}
/>
</Box>
{memoRenderContainer}
</Flex>
</ReactFlowProvider>
</Box>

View File

@@ -81,7 +81,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
<Box flex={1}>
<Flex alignItems={'flex-end'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
API
{t('support.openapi.Api manager')}
</Box>
{feConfigs?.docUrl && (
<Link
@@ -90,7 +90,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
ml={1}
color={'primary.500'}
>
{t('common.Read document')}
</Link>
)}
</Flex>
@@ -106,10 +106,10 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
borderRadius={'md'}
cursor={'pointer'}
userSelect={'none'}
onClick={() => copyData(baseUrl, '已复制 API 地址')}
onClick={() => copyData(baseUrl, t('support.openapi.Copy success'))}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
API根地址
{t('support.openapi.Api baseurl')}
</Box>
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
{baseUrl}
@@ -127,7 +127,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
})
}
>
{t('common.New Create')}
</Button>
</Box>
</Box>
@@ -137,16 +137,16 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
<Tr>
<Th>{t('Name')}</Th>
<Th>Api Key</Th>
<Th>()</Th>
<Th>{t('support.openapi.Usage')}</Th>
{feConfigs?.isPlus && (
<>
<Th>()</Th>
<Th></Th>
<Th>{t('support.openapi.Max usage')}</Th>
<Th>{t('common.Expired Time')}</Th>
</>
)}
<Th></Th>
<Th>使</Th>
<Th>{t('common.Create Time')}</Th>
<Th>{t('common.Last use time')}</Th>
<Th />
</Tr>
</Thead>
@@ -158,7 +158,11 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
<Td>{usage}</Td>
{feConfigs?.isPlus && (
<>
<Td>{limit?.credit && limit?.credit > -1 ? `${limit?.credit}` : '无限制'}</Td>
<Td>
{limit?.credit && limit?.credit > -1
? `${limit?.credit}`
: t('common.Unlimited')}
</Td>
<Td whiteSpace={'pre-wrap'}>
{limit?.expiredTime
? dayjs(limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
@@ -168,7 +172,9 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
)}
<Td whiteSpace={'pre-wrap'}>{dayjs(createTime).format('YYYY/MM/DD\nHH:mm:ss')}</Td>
<Td whiteSpace={'pre-wrap'}>
{lastUsedTime ? dayjs(lastUsedTime).format('YYYY/MM/DD\nHH:mm:ss') : '没有使用过'}
{lastUsedTime
? dayjs(lastUsedTime).format('YYYY/MM/DD\nHH:mm:ss')
: t('common.Un used')}
</Td>
<Td>
<Menu autoSelect={false} isLazy>
@@ -229,10 +235,10 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
title={
<Box>
<Box fontWeight={'bold'} fontSize={'xl'}>
API
{t('support.openapi.New api key')}
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
~
{t('support.openapi.New api key tip')}
</Box>
</Box>
}
@@ -359,14 +365,14 @@ function EditKeyModal({
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('Cancel')}
{t('common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
>
{t('Confirm')}
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>

View File

@@ -33,20 +33,16 @@ ${chatModelList
md: `
| 模型 | 价格(¥) |
| --- | --- |
${vectorModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k tokens |`).join('\n')}
${vectorModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}
`
},
{
title: '文件预处理模型(QA 拆分)',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${qaModelList
?.map(
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}
| 模型 | 价格(¥) |
| --- | --- |
${qaModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}
`
},
{
@@ -84,14 +80,6 @@ ${qgModelList
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}`
},
{
title: '重排模型(增强检索 & 混合检索)',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${reRankModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}`
},
{
title: '语音播放',

View File

@@ -2,9 +2,9 @@ import {
TrainingModeEnum,
DatasetCollectionTypeEnum,
DatasetTypeEnum
} from '@fastgpt/global/core/dataset/constant';
} from '@fastgpt/global/core/dataset/constants';
import type { RequestPaging } from '@/types';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import type { SearchTestItemType } from '@/types/core/dataset';
import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';

View File

@@ -1,10 +1,10 @@
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateType = {
id: 'fastgpt-universal',
name: '通用模板',
desc: '通用模板\n可完全自行配置AI属性和知识库',
name: 'core.app.template.Common template',
desc: 'core.app.template.Common template tip',
systemForm: {
aiSettings: {
model: true,

View File

@@ -3,7 +3,7 @@ import {
DatasetSearchModeEnum,
DatasetTypeEnum,
TrainingModeEnum
} from '@fastgpt/global/core/dataset/constant';
} from '@fastgpt/global/core/dataset/constants';
import {
DatasetDataIndexItemType,
SearchDataResponseItemType

View File

@@ -53,8 +53,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 data 获取的知识。
- 保持答案与 data 中描述的一致。
- 避免提及你是从 <data></data> 获取的知识。
- 保持答案与 <data></data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
@@ -88,8 +88,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 data 获取的知识。
- 保持答案与 data 中描述的一致。
- 避免提及你是从 <data></data> 获取的知识。
- 保持答案与 <data></data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
import type { AppProps } from 'next/app';
import Script from 'next/script';
import Head from 'next/head';

View File

@@ -30,7 +30,7 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
hasTokens,
hasInputTokens,
hasOutputTokens,
hasTextLen,
hasCharsLen,
hasDuration,
hasDataLen
} = useMemo(() => {
@@ -38,7 +38,7 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
let hasTokens = false;
let hasInputTokens = false;
let hasOutputTokens = false;
let hasTextLen = false;
let hasCharsLen = false;
let hasDuration = false;
let hasDataLen = false;
@@ -46,24 +46,21 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
if (item.model !== undefined) {
hasModel = true;
}
if (item.tokenLen !== undefined) {
if (typeof item.tokenLen === 'number') {
hasTokens = true;
}
if (item.inputTokens !== undefined) {
if (typeof item.inputTokens === 'number') {
hasInputTokens = true;
}
if (item.outputTokens !== undefined) {
if (typeof item.outputTokens === 'number') {
hasOutputTokens = true;
}
if (item.textLen !== undefined) {
hasTextLen = true;
if (typeof item.charsLength === 'number') {
hasCharsLen = true;
}
if (item.duration !== undefined) {
if (typeof item.duration === 'number') {
hasDuration = true;
}
if (item.dataLen !== undefined) {
hasDataLen = true;
}
});
return {
@@ -71,7 +68,7 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
hasTokens,
hasInputTokens,
hasOutputTokens,
hasTextLen,
hasCharsLen,
hasDuration,
hasDataLen
};
@@ -123,9 +120,8 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
{hasTokens && <Th>{t('wallet.bill.Token Length')}</Th>}
{hasInputTokens && <Th>{t('wallet.bill.Input Token Length')}</Th>}
{hasOutputTokens && <Th>{t('wallet.bill.Output Token Length')}</Th>}
{hasTextLen && <Th>{t('wallet.bill.Text Length')}</Th>}
{hasCharsLen && <Th>{t('wallet.bill.Text Length')}</Th>}
{hasDuration && <Th>{t('wallet.bill.Duration')}</Th>}
{hasDataLen && <Th>{t('wallet.bill.Data Length')}</Th>}
<Th>()</Th>
</Tr>
</Thead>
@@ -137,10 +133,8 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
{hasTokens && <Td>{item.tokenLen ?? '-'}</Td>}
{hasInputTokens && <Td>{item.inputTokens ?? '-'}</Td>}
{hasOutputTokens && <Td>{item.outputTokens ?? '-'}</Td>}
{hasTextLen && <Td>{item.textLen ?? '-'}</Td>}
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
{hasDataLen && <Td>{item.dataLen ?? '-'}</Td>}
<Td>{formatStorePrice2Read(item.amount)}</Td>
</Tr>
))}

View File

@@ -108,12 +108,12 @@ const UserInfo = () => {
});
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
title: typeof err === 'string' ? err : t('common.error.Select avatar failed'),
status: 'warning'
});
}
},
[onclickSave, toast, userInfo]
[onclickSave, t, toast, userInfo]
);
useQuery(['init'], initUserInfo, {

View File

@@ -1,184 +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 { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { defaultQAModels } from '@fastgpt/global/core/ai/model';
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;
try {
await Promise.allSettled([
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN data_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN q DROP NOT NULL;`), // q can null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN a DROP NOT NULL;`), // a can null
PgClient.query(
`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN team_id TYPE VARCHAR(50) USING team_id::VARCHAR(50);`
), // team_id varchar
PgClient.query(
`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id TYPE VARCHAR(50) USING tmb_id::VARCHAR(50);`
), // tmb_id varchar
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN team_id SET NOT NULL;`), // team_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id SET NOT NULL;`), // tmb_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN dataset_id SET NOT NULL;`), // dataset_id not null
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN collection_id SET NOT NULL;`) // collection_id not null
]);
} catch (error) {}
try {
await initPgData();
} catch (error) {}
await MongoDataset.updateMany(
{},
{
agentModel: defaultQAModels[0].model
}
);
jsonRes(res, {
data: await init(limit),
message:
'初始化任务已开始,请注意日志进度。可通过 select count(id) from modeldata where data_id is null; 检查是否完全初始化,如果结果为 0 ,则完全初始化。'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
type PgItemType = {
id: string;
q: string;
a: string;
dataset_id: string;
collection_id: string;
team_id: string;
tmb_id: string;
};
async function initPgData() {
const limit = 10;
const { rows } = await PgClient.query<{ user_id: string }>(`
SELECT DISTINCT user_id FROM ${PgDatasetTableName} WHERE team_id='null';
`);
console.log('init pg', rows.length);
let success = 0;
for (let i = 0; i < limit; i++) {
init(i);
}
async function init(index: number): Promise<any> {
const userId = rows[index]?.user_id;
if (!userId) return;
try {
const tmb = await getUserDefaultTeam({ userId });
console.log(tmb);
// update pg
await PgClient.query(
`Update ${PgDatasetTableName} set team_id = '${String(tmb.teamId)}', tmb_id = '${String(
tmb.tmbId
)}' where user_id = '${userId}' AND team_id='null';`
);
console.log(++success);
init(index + limit);
} catch (error) {
if (error === 'default team not exist') {
return;
}
console.log(error);
await delay(1000);
return init(index);
}
}
}
async function init(limit: number): Promise<any> {
const { rows: idList } = await PgClient.query<{ id: string }>(
`SELECT id FROM ${PgDatasetTableName} WHERE data_id IS NULL`
);
console.log('totalCount', idList.length);
if (idList.length === 0) return;
for (let i = 0; i < limit; i++) {
initData(i);
}
async function initData(index: number): Promise<any> {
const dataId = idList[index]?.id;
if (!dataId) {
console.log('done');
return;
}
// get limit data where data_id is null
const { rows } = await PgClient.query<PgItemType>(
`SELECT id,q,a,dataset_id,collection_id,team_id,tmb_id FROM ${PgDatasetTableName} WHERE id=${dataId};`
);
const data = rows[0];
if (!data) {
console.log('done');
return;
}
let id = '';
try {
// create mongo data and update data_id
const { _id } = await MongoDatasetData.create({
teamId: data.team_id.trim(),
tmbId: data.tmb_id.trim(),
datasetId: data.dataset_id,
collectionId: data.collection_id,
q: data.q,
a: data.a,
fullTextToken: '',
indexes: [
{
defaultIndex: !data.a,
type: data.a ? DatasetDataIndexTypeEnum.qa : DatasetDataIndexTypeEnum.chunk,
dataId: data.id,
text: data.q
}
]
});
id = _id;
// update pg data_id
await PgClient.query(
`UPDATE ${PgDatasetTableName} SET data_id='${String(_id)}' WHERE id=${dataId};`
);
console.log(++success);
return initData(index + limit);
} catch (error) {
console.log(error);
console.log(data);
try {
if (id) {
await MongoDatasetData.findByIdAndDelete(id);
}
} catch (error) {}
await delay(500);
return initData(index);
}
}
}

View File

@@ -1,173 +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 { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { Types, connectionMongo } from '@fastgpt/service/common/mongo';
import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant';
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
import { getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller';
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 init(limit);
await initCollectionFileTeam(limit);
jsonRes(res, {});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
type PgItemType = {
id: string;
q: string;
a: string;
dataset_id: string;
collection_id: string;
data_id: string;
};
async function init(limit: number): Promise<any> {
const { rows } = await PgClient.query<{ id: string; data_id: string }>(
`SELECT id,data_id FROM ${PgDatasetTableName} WHERE team_id = tmb_id`
);
console.log('totalCount', rows.length);
await delay(2000);
if (rows.length === 0) return;
for (let i = 0; i < limit; i++) {
initData(i);
}
async function initData(index: number): Promise<any> {
const item = rows[index];
if (!item) {
console.log('done');
return;
}
// get mongo
const mongoData = await MongoDatasetData.findById(item.data_id, '_id teamId tmbId');
if (!mongoData) {
return initData(index + limit);
}
try {
// find team owner
const db = connectionMongo?.connection?.db;
const TeamMember = db.collection(TeamMemberCollectionName);
const tmb = await TeamMember.findOne({
teamId: new Types.ObjectId(mongoData.teamId),
role: 'owner'
});
if (!tmb) {
return initData(index + limit);
}
// update mongo and pg tmb_id
await MongoDatasetData.findByIdAndUpdate(item.data_id, {
tmbId: tmb._id
});
await PgClient.query(
`UPDATE ${PgDatasetTableName} SET tmb_id = '${String(tmb._id)}' WHERE id = '${item.id}'`
);
console.log(++success);
return initData(index + limit);
} catch (error) {
console.log(error);
await delay(500);
return initData(index);
}
}
}
async function initCollectionFileTeam(limit: number) {
/* init user default Team */
const DatasetFile = getGFSCollection('dataset');
const matchWhere = {
$or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }]
};
const uniqueUsersWithNoTeamId = await DatasetFile.aggregate([
{
$match: matchWhere
},
{
$group: {
_id: '$metadata.userId', // 按 metadata.userId 分组以去重
userId: { $first: '$metadata.userId' } // 保留第一个出现的 userId
}
},
{
$project: {
_id: 0, // 不显示 _id 字段
userId: 1 // 只显示 userId 字段
}
}
]).toArray();
const users = uniqueUsersWithNoTeamId;
console.log('un init total', users.length);
// limit 组一次
const userArr: any[][] = [];
for (let i = 0; i < users.length; i += limit) {
userArr.push(users.slice(i, i + limit));
}
let success = 0;
for await (const item of userArr) {
await Promise.all(item.map((item) => init(item.userId)));
success += limit;
console.log(success);
}
async function init(userId: string): Promise<any> {
try {
const tmb = await getUserDefaultTeam({
userId
});
await DatasetFile.updateMany(
{
'metadata.userId': String(userId),
...matchWhere
},
{
$set: {
'metadata.teamId': String(tmb.teamId),
'metadata.tmbId': String(tmb.tmbId)
}
}
);
} catch (error) {
if (error === 'team not exist' || error === 'tmbId or userId is required') {
return;
}
console.log(error);
await delay(1000);
return init(userId);
}
}
}

View File

@@ -1,330 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema';
import {
createDefaultTeam,
getUserDefaultTeam
} from '@fastgpt/service/support/user/team/controller';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { POST } from '@fastgpt/service/common/api/plusRequest';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50, maxSize = 3 } = req.body as { limit: number; maxSize: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
await initDefaultTeam(limit, maxSize);
await initMongoTeamId(limit);
await initDatasetAndApp();
await initCollectionFileTeam(limit);
if (FastGPTProUrl) {
POST('/admin/init46');
}
await initPgData();
jsonRes(res, {
data: {}
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
async function initDefaultTeam(limit: number, maxSize: number) {
/* init user default Team */
const users = await MongoUser.find({}, '_id balance');
console.log('init user default team', users.length);
// 100 组一次
const userArr: UserModelSchema[][] = [];
for (let i = 0; i < users.length; i += limit) {
userArr.push(users.slice(i, i + limit));
}
let success = 0;
for await (const users of userArr) {
await Promise.all(users.map(init));
success += limit;
console.log(success);
}
async function init(user: UserModelSchema): Promise<any> {
try {
await createDefaultTeam({
userId: user._id,
balance: user.balance,
maxSize
});
} catch (error) {
console.log(error);
await delay(1000);
return init(user);
}
}
}
async function initMongoTeamId(limit: number) {
const mongoSchema = [
{
label: 'MongoPlugin',
schema: MongoPlugin
},
{
label: 'MongoChat',
schema: MongoChat
},
{
label: 'MongoChatItem',
schema: MongoChatItem
},
{
label: 'MongoApp',
schema: MongoApp
},
{
label: 'MongoDataset',
schema: MongoDataset
},
{
label: 'MongoDatasetCollection',
schema: MongoDatasetCollection
},
{
label: 'MongoDatasetTraining',
schema: MongoDatasetTraining
},
{
label: 'MongoBill',
schema: MongoBill
},
{
label: 'MongoOutLink',
schema: MongoOutLink
},
{
label: 'MongoOpenApi',
schema: MongoOpenApi
}
];
/* init user default Team */
for await (const item of mongoSchema) {
console.log('start init', item.label);
await initTeamTmbId(item.schema);
console.log('finish init', item.label);
}
async function initTeamTmbId(schema: any) {
const emptyWhere = {
$or: [{ teamId: { $exists: false } }, { teamId: null }]
};
const uniqueUsersWithNoTeamId = await schema.aggregate([
{
$match: emptyWhere
},
{
$group: {
_id: '$userId', // 按 userId 分组以去重
userId: { $first: '$userId' } // 保留第一个出现的 userId
}
},
{
$project: {
_id: 0, // 不显示 _id 字段
userId: 1 // 只显示 userId 字段
}
}
]);
const users = uniqueUsersWithNoTeamId;
console.log('un init total', users.length);
// limit 组一次
const userArr: any[][] = [];
for (let i = 0; i < users.length; i += limit) {
userArr.push(users.slice(i, i + limit));
}
let success = 0;
for await (const users of userArr) {
await Promise.all(users.map((item) => init(item.userId)));
success += limit;
console.log(success);
}
async function init(userId: string): Promise<any> {
try {
const tmb = await getUserDefaultTeam({ userId });
await schema.updateMany(
{
userId,
...emptyWhere
},
{
teamId: tmb.teamId,
tmbId: tmb.tmbId
}
);
} catch (error) {
if (error === 'team not exist' || error === 'tmbId or userId is required') {
return;
}
console.log(error);
await delay(1000);
return init(userId);
}
}
}
}
async function initDatasetAndApp() {
await MongoDataset.updateMany(
{},
{
$set: {
permission: PermissionTypeEnum.private
}
}
);
await MongoApp.updateMany(
{},
{
$set: {
permission: PermissionTypeEnum.private
}
}
);
}
async function initCollectionFileTeam(limit: number) {
/* init user default Team */
const DatasetFile = getGFSCollection('dataset');
const matchWhere = {
$or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }]
};
const uniqueUsersWithNoTeamId = await DatasetFile.aggregate([
{
$match: matchWhere
},
{
$group: {
_id: '$metadata.userId', // 按 metadata.userId 分组以去重
userId: { $first: '$metadata.userId' } // 保留第一个出现的 userId
}
},
{
$project: {
_id: 0, // 不显示 _id 字段
userId: 1 // 只显示 userId 字段
}
}
]).toArray();
const users = uniqueUsersWithNoTeamId;
console.log('un init total', users.length);
// limit 组一次
const userArr: any[][] = [];
for (let i = 0; i < users.length; i += limit) {
userArr.push(users.slice(i, i + limit));
}
let success = 0;
for await (const item of userArr) {
await Promise.all(item.map((item) => init(item.userId)));
success += limit;
console.log(success);
}
async function init(userId: string): Promise<any> {
try {
const tmb = await getUserDefaultTeam({
userId
});
await DatasetFile.updateMany(
{
'metadata.userId': String(userId),
...matchWhere
},
{
$set: {
'metadata.teamId': String(tmb.teamId),
'metadata.tmbId': String(tmb.tmbId)
}
}
);
} catch (error) {
if (error === 'team not exist' || error === 'tmbId or userId is required') {
return;
}
console.log(error);
await delay(1000);
return init(userId);
}
}
}
async function initPgData() {
const limit = 10;
// add column
try {
await Promise.allSettled([
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id VARCHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`)
]);
} catch (error) {
console.log(error);
console.log('column exists');
}
const { rows } = await PgClient.query<{ user_id: string }>(`
SELECT DISTINCT user_id FROM ${PgDatasetTableName} WHERE team_id IS NULL;
`);
console.log('init pg', rows.length);
let success = 0;
for (let i = 0; i < limit; i++) {
init(i);
}
async function init(index: number): Promise<any> {
const userId = rows[index]?.user_id;
if (!userId) return;
try {
const tmb = await getUserDefaultTeam({ userId });
// update pg
await PgClient.query(
`Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id IS NULL;`
);
console.log(++success);
init(index + limit);
} catch (error) {
if (error === 'default team not exist') {
return;
}
console.log(error);
await delay(1000);
return init(index);
}
}
}

View File

@@ -4,7 +4,7 @@ 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/constant';
import { DatasetStatusEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
let success = 0;

View File

@@ -0,0 +1,106 @@
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 { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
let success = 0;
let deleteImg = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { test = false } = req.body as { test: boolean };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
deleteImg = 0;
// 取消 pg tmb_id 和 data_id 的null
await PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id DROP NOT NULL;`);
await PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN data_id DROP NOT NULL;`);
// 重新绑定 images 和 collections
const images = await MongoImage.find(
{ 'metadata.fileId': { $exists: true } },
'_id metadata'
).lean();
// 去除 fileId 相同的数据
const fileIdMap = new Map<string, MongoImageSchemaType>();
images.forEach((image) => {
// @ts-ignore
const fileId = image.metadata?.fileId;
if (!fileIdMap.has(fileId) && fileId) {
fileIdMap.set(fileId, image);
}
});
const images2 = Array.from(fileIdMap.values());
console.log('total image list', images2.length);
for await (const image of images2) {
await initImages(image, test);
}
jsonRes(res, {
data: success,
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export const initImages = async (image: MongoImageSchemaType, test: boolean): Promise<any> => {
try {
//@ts-ignore
const fileId = image.metadata.fileId as string;
if (!fileId) return;
// 找到集合
const collection = await MongoDatasetCollection.findOne({ fileId }, '_id metadata').lean();
if (!collection) {
deleteImg++;
console.log('deleteImg', deleteImg);
if (test) return;
return MongoImage.deleteOne({ _id: image._id });
}
const relatedImageId = getNanoid(24);
// update image
if (!test) {
await Promise.all([
MongoImage.updateMany(
{ 'metadata.fileId': fileId },
{ $set: { 'metadata.relatedId': relatedImageId } }
),
MongoDatasetCollection.findByIdAndUpdate(collection._id, {
$set: {
'metadata.relatedImgId': relatedImageId
}
})
]);
}
success++;
console.log('success', success);
} catch (error) {
console.log(error);
await delay(1000);
return initImages(image, test);
}
};

View File

@@ -1,95 +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 {
delFileByFileIdList,
getGFSCollection
} from '@fastgpt/service/common/file/gridfs/controller';
import { addLog } from '@fastgpt/service/common/system/log';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { delay } from '@fastgpt/global/common/system/utils';
/*
check dataset.files data. If there is no match in dataset.collections, delete it
*/
let deleteFileAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const {
startDay = 10,
endDay = 3,
limit = 30
} = req.body as { startDay?: number; endDay?: number; limit?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = new Date(Date.now() - startDay * 24 * 60 * 60 * 1000);
const end = new Date(Date.now() - endDay * 24 * 60 * 60 * 1000);
deleteFileAmount = 0;
checkFiles(start, end, limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
addLog.error(`check valid dataset files error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkFiles(start: Date, end: Date, limit: number) {
const collection = getGFSCollection('dataset');
const where = {
uploadDate: { $gte: start, $lte: end }
};
// 1. get all _id
const ids = await collection
.find(where, {
projection: {
_id: 1
}
})
.toArray();
console.log('total files', ids.length);
for (let i = 0; i < limit; i++) {
check(i);
}
async function check(index: number): Promise<any> {
const id = ids[index];
if (!id) {
console.log(`检测完成,共删除 ${deleteFileAmount} 个无效文件`);
return;
}
try {
const { _id } = id;
// 2. find fileId in dataset.collections
const hasCollection = await MongoDatasetCollection.countDocuments({ fileId: _id });
// 3. if not found, delete file
if (hasCollection === 0) {
await delFileByFileIdList({ bucketName: 'dataset', fileIdList: [String(_id)] });
console.log('delete file', _id);
deleteFileAmount++;
}
index % 100 === 0 && console.log(index);
return check(index + limit);
} catch (error) {
console.log(error);
await delay(2000);
return check(index);
}
}
}

View File

@@ -18,32 +18,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
try {
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
const { files, bucketName, metadata } = await upload.doUpload(req, res);
filePaths = files.map((file) => file.path);
const { file, bucketName, metadata } = await upload.doUpload(req, res);
filePaths = [file.path];
await connectToDatabase();
if (!bucketName) {
throw new Error('bucketName is empty');
}
const upLoadResults = await Promise.all(
files.map((file) =>
uploadFile({
teamId,
tmbId,
bucketName,
path: file.path,
filename: file.originalname,
metadata: {
...metadata,
contentType: file.mimetype,
userId
}
})
)
);
const upLoadResults = await uploadFile({
teamId,
tmbId,
bucketName,
path: file.path,
filename: file.originalname,
contentType: file.mimetype,
metadata: metadata
});
jsonRes(res, {
data: upLoadResults

View File

@@ -55,7 +55,8 @@ const defaultFeConfigs: FastGPTFeConfigsType = {
websiteSyncLimitMinuted: 0
},
scripts: [],
favicon: '/favicon.ico'
favicon: '/favicon.ico',
uploadFileMaxSize: 500
};
export async function getInitConfig() {

View File

@@ -7,7 +7,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { getExtractModel } from '@/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -37,7 +37,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
@@ -49,7 +49,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -58,7 +58,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
outputs: [
{
key: 'userChatInput',
label: '用户问题',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
@@ -93,7 +93,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
@@ -189,7 +189,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
@@ -268,7 +268,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
@@ -280,7 +280,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -289,17 +289,13 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
outputs: [
{
key: 'userChatInput',
label: '用户问题',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'vuc92c',
key: 'userChatInput'
},
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
@@ -307,7 +303,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
},
{
moduleId: 'datasetSearch',
name: '知识库搜索',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
@@ -447,6 +443,18 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
@@ -473,7 +481,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
@@ -569,7 +577,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:

View File

@@ -33,7 +33,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
@@ -45,7 +45,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -54,7 +54,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
outputs: [
{
key: 'userChatInput',
label: '用户问题',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
@@ -89,7 +89,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
@@ -185,7 +185,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
@@ -264,7 +264,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
@@ -276,7 +276,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
@@ -285,17 +285,13 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
outputs: [
{
key: 'userChatInput',
label: '用户问题',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'vuc92c',
key: 'userChatInput'
},
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
@@ -303,7 +299,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
},
{
moduleId: 'datasetSearch',
name: '知识库搜索',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
@@ -457,6 +453,18 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
@@ -483,7 +491,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
@@ -579,7 +587,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:

View File

@@ -8,6 +8,7 @@ import { Types } from '@fastgpt/service/common/mongo';
import { addDays } from 'date-fns';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -28,8 +29,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' });
const where = {
appId: new Types.ObjectId(appId),
teamId: new Types.ObjectId(teamId),
appId: new Types.ObjectId(appId),
updateTime: {
$gte: new Date(dateStart),
$lte: new Date(dateEnd)
@@ -41,18 +42,26 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{ $match: where },
{
$lookup: {
from: 'chatitems',
let: { chat_id: '$chatId' },
from: ChatItemCollectionName,
let: { chatId: '$chatId' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$chatId', '$$chat_id'] },
{ $eq: ['$appId', new Types.ObjectId(appId)] }
{ $eq: ['$appId', new Types.ObjectId(appId)] },
{ $eq: ['$chatId', '$$chatId'] }
]
}
}
},
{
$project: {
userGoodFeedback: 1,
userBadFeedback: 1,
customFeedbacks: 1,
adminFeedback: 1
}
}
],
as: 'chatitems'

View File

@@ -35,16 +35,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return Promise.reject('Param are error');
})();
console.log(match);
// find chatIds
const list = await MongoChat.find(match, 'chatId').lean();
const idList = list.map((item) => item.chatId);
await MongoChatItem.deleteMany({
appId,
chatId: { $in: idList }
});
await MongoChat.deleteMany({
appId,
chatId: { $in: idList }
});

View File

@@ -23,9 +23,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
await MongoChatItem.deleteMany({
appId,
chatId
});
await MongoChat.findOneAndRemove({
appId,
chatId
});

View File

@@ -27,6 +27,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await MongoChatItem.findOneAndUpdate(
{
appId,
chatId,
dataId: chatItemId
},
{

View File

@@ -2,14 +2,11 @@ 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 type {
AdminUpdateFeedbackParams,
CloseCustomFeedbackParams
} from '@/global/core/chat/api.d';
import type { CloseCustomFeedbackParams } from '@/global/core/chat/api.d';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
/* 初始化我的聊天框,需要身份验证 */
/* remove custom feedback */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
@@ -29,13 +26,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await authCert({ req, authToken: true });
await MongoChatItem.findOneAndUpdate(
{ dataId: chatItemId },
{ appId, chatId, dataId: chatItemId },
{ $unset: { [`customFeedbacks.${index}`]: 1 } }
);
await MongoChatItem.findOneAndUpdate(
{ dataId: chatItemId },
{ $pull: { customFeedbacks: null } }
);
jsonRes(res);
} catch (err) {

View File

@@ -29,6 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await MongoChatItem.findOneAndUpdate(
{
appId,
chatId,
dataId: chatItemId
},

View File

@@ -31,8 +31,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });
return {
appId,
tmbId,
appId,
source: ChatSourceEnum.online
};
}

View File

@@ -31,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
appId,
per: 'r'
}),
chatId ? MongoChat.findOne({ chatId }) : undefined
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
]);
// auth chat permission
@@ -41,6 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${

View File

@@ -25,8 +25,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
await MongoChatItem.deleteOne({
dataId: contentId,
chatId
appId,
chatId,
dataId: contentId
});
jsonRes(res);

View File

@@ -56,7 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try {
pushAudioSpeechBill({
model: model,
textLen: input.length,
charsLength: input.length,
tmbId,
teamId,
source: authType2BillSource({ authType })

View File

@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// auth app permission
const [tmb, chat, app] = await Promise.all([
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
MongoChat.findOne({ chatId, shareId }).lean(),
MongoChat.findOne({ appId, chatId, shareId }).lean(),
MongoApp.findById(appId).lean()
]);
@@ -40,6 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const { history } = await getChatItems({
appId: app._id,
chatId,
limit: 30,
field: `dataId obj value userGoodFeedback userBadFeedback ${

View File

@@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
await MongoChat.findOneAndUpdate(
{ chatId },
{ appId, chatId },
{
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })

View File

@@ -6,7 +6,7 @@ import { getVectorModel } from '@/service/core/ai/model';
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
/* get all dataset by teamId or tmbId */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {

View File

@@ -0,0 +1,85 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { uploadFile } from '@fastgpt/service/common/file/gridfs/controller';
import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { FileCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
/**
* Creates the multer uploader
*/
const upload = getUploadModel({
maxSize: 500 * 1024 * 1024
});
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let filePaths: string[] = [];
const { datasetId } = req.query as { datasetId: string };
try {
await connectToDatabase();
const { teamId, tmbId } = await authDataset({
req,
authToken: true,
authApiKey: true,
per: 'w',
datasetId
});
const { file, bucketName, data } = await upload.doUpload<FileCreateDatasetCollectionParams>(
req,
res
);
filePaths = [file.path];
if (!file || !bucketName) {
throw new Error('file is empty');
}
const { fileMetadata, collectionMetadata, ...collectionData } = data;
// upload file and create collection
const fileId = await uploadFile({
teamId,
tmbId,
bucketName,
path: file.path,
filename: file.originalname,
contentType: file.mimetype,
metadata: fileMetadata
});
// create collection
const collectionId = await createOneCollection({
...collectionData,
metadata: collectionMetadata,
teamId,
tmbId,
type: DatasetCollectionTypeEnum.file,
fileId
});
jsonRes(res, {
data: collectionId
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
removeFilesByPaths(filePaths);
}
export const config = {
api: {
bodyParser: false
}
};

View File

@@ -7,7 +7,10 @@ import { connectToDatabase } from '@/service/mongo';
import type { LinkCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
import { TrainingModeEnum, DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import {
TrainingModeEnum,
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';

View File

@@ -7,11 +7,14 @@ import { connectToDatabase } from '@/service/mongo';
import type { TextCreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
import { TrainingModeEnum, DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import {
TrainingModeEnum,
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToDatasetCollection } from '@/service/core/dataset/data/controller';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { hashStr } from '@fastgpt/global/common/string/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -39,8 +42,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
text,
chunkLen: chunkSize,
overlapRatio: trainingType === TrainingModeEnum.chunk ? 0.2 : 0,
customReg: chunkSplitter ? [chunkSplitter] : [],
countTokens: false
customReg: chunkSplitter ? [chunkSplitter] : []
});
// 2. check dataset limit
@@ -67,7 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// 4. push chunks to training queue
const insertResults = await pushDataToDatasetCollection({
const insertResults = await pushDataToTrainingQueue({
teamId,
tmbId,
collectionId,

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { findCollectionAndChild } from '@fastgpt/service/core/dataset/collection/utils';
import { delCollectionRelevantData } from '@fastgpt/service/core/dataset/data/controller';
import { delCollectionAndRelatedSources } from '@fastgpt/service/core/dataset/collection/controller';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('CollectionIdId is required');
}
await authDatasetCollection({
const { teamId, collection } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
@@ -24,13 +24,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// find all delete id
const collections = await findCollectionAndChild(collectionId, '_id fileId');
const delIdList = collections.map((item) => item._id);
const collections = await findCollectionAndChild({
teamId,
datasetId: collection.datasetId._id,
collectionId,
fields: '_id teamId fileId metadata'
});
// delete
await delCollectionRelevantData({
collectionIds: delIdList,
fileIds: collections.map((item) => item?.fileId || '').filter(Boolean)
await delCollectionAndRelatedSources({
collections
});
jsonRes(res);

View File

@@ -7,7 +7,7 @@ import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
@@ -87,7 +87,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
$match: {
$expr: {
$eq: ['$collectionId', '$$id']
$and: [{ $eq: ['$teamId', match.teamId] }, { $eq: ['$collectionId', '$$id'] }]
}
}
},
@@ -105,7 +105,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
$match: {
$expr: {
$eq: ['$collectionId', '$$id']
$and: [
{ $eq: ['$teamId', match.teamId] },
{ $eq: ['$datasetId', match.datasetId] },
{ $eq: ['$collectionId', '$$id'] }
]
}
}
},

View File

@@ -6,11 +6,11 @@ import {
getCollectionAndRawText,
reloadCollectionChunks
} from '@fastgpt/service/core/dataset/collection/utils';
import { delCollectionRelevantData } from '@fastgpt/service/core/dataset/data/controller';
import { delCollectionAndRelatedSources } from '@fastgpt/service/core/dataset/collection/controller';
import {
DatasetCollectionSyncResultEnum,
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constant';
} from '@fastgpt/global/core/dataset/constants';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
@@ -27,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('CollectionIdId is required');
}
const { collection, tmbId } = await authDatasetCollection({
const { collection, teamId, tmbId } = await authDatasetCollection({
req,
authToken: true,
collectionId,
@@ -87,9 +87,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// delete old collection
await delCollectionRelevantData({
collectionIds: [collection._id],
fileIds: collection.fileId ? [collection.fileId] : []
await delCollectionAndRelatedSources({
collections: [collection]
});
jsonRes(res, {

View File

@@ -5,7 +5,7 @@ import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import { createDefaultCollection } from '@fastgpt/service/core/dataset/collection/controller';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {

View File

@@ -3,7 +3,8 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { connectToDatabase } from '@/service/mongo';
import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { delDatasetDataByDataId } from '@fastgpt/service/core/dataset/data/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -17,7 +18,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
// 凭证校验
const { datasetData } = await authDatasetData({
const { teamId, datasetData } = await authDatasetData({
req,
authToken: true,
authApiKey: true,
@@ -25,11 +26,20 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
per: 'w'
});
await delDatasetDataByDataId({
collectionId: datasetData.collectionId,
mongoDataId: dataId
// update mongo data update time
await MongoDatasetData.findByIdAndUpdate(dataId, {
updateTime: new Date()
});
// delete vector data
await deleteDatasetDataVector({
teamId,
idList: datasetData.indexes.map((item) => item.dataId)
});
// delete mongo data
await MongoDatasetData.findByIdAndDelete(dataId);
jsonRes(res, {
data: 'success'
});

View File

@@ -71,12 +71,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// Duplicate data check
await hasSameValue({
teamId,
collectionId,
q: formatQ,
a: formatA
});
const { insertId, tokens } = await insertData2Dataset({
const { insertId, charsLength } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
@@ -91,7 +92,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorBill({
teamId,
tmbId,
tokens,
charsLength,
model: vectorModelData.model
});

View File

@@ -20,11 +20,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
pageSize = Math.min(pageSize, 30);
// 凭证校验
await authDatasetCollection({ req, authToken: true, authApiKey: true, collectionId, per: 'r' });
const { teamId, collection } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId,
per: 'r'
});
searchText = searchText.replace(/'/g, '');
const match = {
teamId,
datasetId: collection.datasetId._id,
collectionId,
...(searchText
? {

View File

@@ -3,13 +3,12 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { TrainingModeEnum, TrainingTypeMap } from '@fastgpt/global/core/dataset/constant';
import type { PushDataResponse } from '@/global/core/api/datasetRes.d';
import type { PushDatasetDataProps } from '@/global/core/dataset/api.d';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToDatasetCollection } from '@/service/core/dataset/data/controller';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -41,7 +40,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
jsonRes<PushDataResponse>(res, {
data: await pushDataToDatasetCollection({
data: await pushDataToTrainingQueue({
...req.body,
teamId,
tmbId

View File

@@ -31,7 +31,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// auth team balance
await authTeamBalance(teamId);
const { tokens } = await updateData2Dataset({
const { charsLength } = await updateData2Dataset({
dataId: id,
q,
a,
@@ -42,7 +42,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorBill({
teamId,
tmbId,
tokens,
charsLength,
model: vectorModel
});

View File

@@ -2,32 +2,35 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { delDatasetRelevantData } from '@fastgpt/service/core/dataset/data/controller';
import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller';
import { delDatasetRelevantData } from '@fastgpt/service/core/dataset/controller';
import { findDatasetAndAllChildren } from '@fastgpt/service/core/dataset/controller';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id } = req.query as {
const { id: datasetId } = req.query as {
id: string;
};
if (!id) {
if (!datasetId) {
throw new Error('缺少参数');
}
// auth owner
await authDataset({ req, authToken: true, datasetId: id, per: 'owner' });
const { teamId } = await authDataset({ req, authToken: true, datasetId, per: 'owner' });
const deletedIds = await findDatasetIdTreeByTopDatasetId(id);
const datasets = await findDatasetAndAllChildren({
teamId,
datasetId
});
// delete all dataset.data and pg data
await delDatasetRelevantData({ datasetIds: deletedIds });
await delDatasetRelevantData({ datasets });
// delete dataset data
await MongoDataset.deleteMany({
_id: { $in: deletedIds }
_id: { $in: datasets.map((d) => d._id) }
});
jsonRes(res);

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { addLog } from '@fastgpt/service/common/system/log';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller';
import { findDatasetAndAllChildren } from '@fastgpt/service/core/dataset/controller';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import {
checkExportDatasetLimit,
@@ -30,7 +30,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
limitMinutes: global.feConfigs?.limit?.exportDatasetLimitMinutes
});
const exportIds = await findDatasetIdTreeByTopDatasetId(datasetId);
const datasets = await findDatasetAndAllChildren({
teamId,
datasetId,
fields: '_id'
});
res.setHeader('Content-Type', 'text/csv; charset=utf-8;');
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv; ');
@@ -42,7 +46,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
a: string;
}>(
{
datasetId: { $in: exportIds }
teamId,
datasetId: { $in: datasets.map((d) => d._id) }
},
'q a'
)

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';

View File

@@ -47,7 +47,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// model: global.chatModels[0].model
// });
const { searchRes, tokens, ...result } = await searchDatasetData({
const { searchRes, charsLength, ...result } = await searchDatasetData({
teamId,
rawQuery: text,
queries: [text],
model: dataset.vectorModel,
@@ -62,7 +63,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokens,
charsLength,
model: dataset.vectorModel,
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt
});

View File

@@ -12,6 +12,7 @@ type Props = HttpBodyType<{
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
appId,
chatId,
responseChatItemId: chatItemId,
data: { defaultFeedback, customFeedback }
@@ -30,6 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// wait the chat finish
setTimeout(() => {
addCustomFeedbacks({
appId,
chatId,
chatItemId,
feedbacks: [feedback]

View File

@@ -10,7 +10,7 @@ import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { username, password, tmbId = '' } = req.body as PostLoginProps;
const { username, password } = req.body as PostLoginProps;
if (!username || !password) {
throw new Error('缺少参数');
@@ -40,7 +40,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error('密码错误');
}
const userDetail = await getUserDetail({ tmbId, userId: user._id });
const userDetail = await getUserDetail({
tmbId: user?.lastLoginTmbId,
userId: user._id
});
MongoUser.findByIdAndUpdate(user._id, {
lastLoginTmbId: userDetail.team.tmbId
});
const token = createJWT(userDetail);
setCookie(res, token);

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 { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { size } = req.query as {
size: string;
};
// 凭证校验
const { teamId } = await authCert({ req, authToken: true });
if (!size) {
return jsonRes(res);
}
const numberSize = Number(size);
await checkDatasetLimit({
teamId,
freeSize: global.feConfigs?.subscription?.datasetStoreFreeSize,
insertLen: numberSize
});
jsonRes(res);
} catch (err) {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,29 +1,32 @@
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 { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { CreateTrainingBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
import { getQAModel, getVectorModel } from '@/service/core/ai/model';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { name, vectorModel, agentModel } = req.body as CreateTrainingBillProps;
const { name, datasetId } = req.body as CreateTrainingBillProps;
const { teamId, tmbId } = await authCert({ req, authToken: true, authApiKey: true });
const vectorModelData = getVectorModel(vectorModel);
const agentModelData = getQAModel(agentModel);
const { teamId, tmbId, dataset } = await authDataset({
req,
authToken: true,
authApiKey: true,
datasetId,
per: 'w'
});
const { billId } = await createTrainingBill({
teamId,
tmbId,
appName: name,
billSource: BillSourceEnum.training,
vectorModel: vectorModelData.name,
agentModel: agentModelData.name
vectorModel: getVectorModel(dataset.vectorModel).name,
agentModel: getQAModel(dataset.agentModel).name
});
jsonRes<string>(res, {

View File

@@ -17,11 +17,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try {
const {
files,
metadata: { duration, shareId }
file,
data: { duration }
} = await upload.doUpload<{ duration: number; shareId?: string }>(req, res);
filePaths = files.map((file) => file.path);
filePaths = [file.path];
const { teamId, tmbId } = await authCert({ req, authToken: true });
@@ -29,8 +29,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('whisper model not found');
}
const file = files[0];
if (!file) {
throw new Error('file not found');
}

View File

@@ -195,7 +195,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
// get and concat history
const { history } = await getChatItems({ chatId, limit: 30, field: `dataId obj value` });
const { history } = await getChatItems({
appId: app._id,
chatId,
limit: 30,
field: `dataId obj value`
});
const concatHistories = history.concat(chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;

View File

@@ -33,7 +33,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await authTeamBalance(teamId);
const { tokens, vectors } = await getVectorsByText({ input: query, model });
const { charsLength, vectors } = await getVectorsByText({ input: query, model });
res.json({
object: 'list',
@@ -44,15 +44,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})),
model,
usage: {
prompt_tokens: tokens,
total_tokens: tokens
prompt_tokens: charsLength,
total_tokens: charsLength
}
});
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokens,
charsLength,
model,
billId,
source: getBillSourceByAuthType({ authType })

View File

@@ -62,7 +62,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
});
if (unconnected) {
const msg = `${t(item.name)}】存在未填或未连接参数`;
const msg = t('core.module.Unlink tip', { name: t(item.name) });
toast({
status: 'warning',
@@ -82,8 +82,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
permission: undefined
});
},
successToast: '保存配置成功',
errorToast: '保存配置异常',
successToast: t('common.Save Success'),
errorToast: t('common.Save Failed'),
onSuccess() {
ChatTestRef.current?.resetChatTest();
}
@@ -98,22 +98,23 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
alignItems={'center'}
userSelect={'none'}
>
<MyTooltip label={t('common.Back')} offset={[10, 10]}>
<IconButton
size={'smSquare'}
icon={<MyIcon name={'common/backLight'} w={'14px'} />}
borderColor={'myGray.300'}
variant={'whiteBase'}
aria-label={''}
onClick={openConfirmOut(async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
}
onClose();
}, onClose)}
/>
</MyTooltip>
<IconButton
size={'smSquare'}
icon={<MyIcon name={'common/backFill'} w={'14px'} />}
borderRadius={'50%'}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'whiteBase'}
aria-label={''}
onClick={openConfirmOut(async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
}
onClose();
}, onClose)}
/>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
</Box>
@@ -154,7 +155,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
onClick={() => setTestModules(undefined)}
/>
) : (
<MyTooltip label={'测试对话'}>
<MyTooltip label={t('core.Chat test')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
@@ -171,9 +172,9 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
</MyTooltip>
)}
<MyTooltip label={'保存配置'}>
<MyTooltip label={t('common.Save')}>
<IconButton
icon={<MyIcon name={'save'} w={['14px', '16px']} />}
icon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
size={'smSquare'}
isLoading={isLoading}
aria-label={'save'}

View File

@@ -44,12 +44,18 @@ const Render = ({ app, onClose }: Props) => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
return <Flow templates={moduleTemplates} Header={<Header app={app} onClose={onClose} />} />;
const memoRender = useMemo(() => {
return <Flow templates={moduleTemplates} Header={<Header app={app} onClose={onClose} />} />;
}, [app, moduleTemplates.length, onClose]);
return memoRender;
};
export default React.memo(function FlowEdit(props: Props) {
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
return (
<FlowProvider mode={'app'} filterAppIds={[props.app._id]}>
<FlowProvider mode={'app'} filterAppIds={filterAppIds}>
<Render {...props} />
</FlowProvider>
);

View File

@@ -52,7 +52,7 @@ const InfoModal = ({
});
const [refresh, setRefresh] = useState(false);
// 提交保存模型修改
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async (data: AppSchema) => {
await updateAppDetail(data._id, {
@@ -66,18 +66,17 @@ const InfoModal = ({
onSuccess && onSuccess();
onClose();
toast({
title: '更新成功',
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: '更新失败'
errorToast: t('common.Update Failed')
});
// 提交保存表单失败
const saveSubmitError = useCallback(() => {
// deep search message
const deepSearch = (obj: any): string => {
if (!obj) return '提交表单错误';
if (!obj) return t('common.Submit failed');
if (!!obj.message) {
return obj.message;
}
@@ -89,7 +88,7 @@ const InfoModal = ({
duration: 4000,
isClosable: true
});
}, [errors, toast]);
}, [errors, t, toast]);
const saveUpdateModel = useCallback(
() => handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)(),
@@ -111,12 +110,12 @@ const InfoModal = ({
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, '头像选择异常'),
title: getErrText(err, t('common.error.Select avatar failed')),
status: 'warning'
});
}
},
[setValue, toast]
[setValue, t, toast]
);
return (
@@ -127,7 +126,7 @@ const InfoModal = ({
title={t('core.app.setting')}
>
<ModalBody>
<Box> & </Box>
<Box>{t('core.app.Name and avatar')}</Box>
<Flex mt={2} alignItems={'center'}>
<Avatar
src={getValues('avatar')}
@@ -136,21 +135,21 @@ const InfoModal = ({
cursor={'pointer'}
borderRadius={'md'}
mr={4}
title={'点击切换头像'}
title={t('common.Set Avatar')}
onClick={() => onOpenSelectFile()}
/>
<FormControl>
<Input
bg={'myWhite.600'}
placeholder={'给应用设置一个名称'}
placeholder={t('core.app.Set a name for your app')}
{...register('name', {
required: '展示名称不能为空'
required: true
})}
></Input>
</FormControl>
</Flex>
<Box mt={4} mb={1}>
{t('core.app.App intro')}
</Box>
{/* <Box color={'myGray.500'} mb={2} fontSize={'sm'}>
该介绍主要用于记忆和在应用市场展示
@@ -158,7 +157,7 @@ const InfoModal = ({
<Textarea
rows={4}
maxLength={500}
placeholder={'给你的 AI 应用一个介绍'}
placeholder={t('core.app.Make a brief introduction of your app')}
bg={'myWhite.600'}
{...register('intro')}
/>
@@ -176,10 +175,10 @@ const InfoModal = ({
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={saveUpdateModel}>
{t('common.Save')}
</Button>
</ModalFooter>

View File

@@ -67,11 +67,8 @@ const Share = ({ appId }: { appId: string }) => {
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']}>
<MyTooltip
forceShow
label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!"
>
{t('core.app.Share link')}
<MyTooltip forceShow label={t('core.app.Share link desc detail')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
@@ -82,29 +79,29 @@ const Share = ({ appId }: { appId: string }) => {
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
title: t('core.app.share.Amount limit tip')
}
: {})}
onClick={() => setEditLinkData(defaultOutLinkForm)}
>
{t('core.app.share.Create link')}
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th>{t('common.Name')}</Th>
<Th>{t('common.Price used')}</Th>
<Th>{t('core.app.share.Is response quote')}</Th>
{feConfigs?.isPlus && (
<>
<Th>IP限流/</Th>
<Th></Th>
<Th></Th>
<Th>{t('core.app.share.Ip limit title')}</Th>
<Th>{t('common.Expired Time')}</Th>
<Th>{t('core.app.share.Role check')}</Th>
</>
)}
<Th>使</Th>
<Th>{t('common.Last use time')}</Th>
<Th></Th>
</Tr>
</Thead>
@@ -117,8 +114,8 @@ const Share = ({ appId }: { appId: string }) => {
{feConfigs?.isPlus
? `${
item.limit && item.limit.credit > -1
? ` / ${item.limit.credit}`
: ' / 无限制'
? ` / ${item.limit.credit}`
: ` / ${t('common.Unlimited')}`
}`
: ''}
</Td>
@@ -134,7 +131,9 @@ const Share = ({ appId }: { appId: string }) => {
<Th>{item?.limit?.hookUrl ? '✔' : '✖'}</Th>
</>
)}
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td>
{item.lastTime ? t(formatTimeToChatTime(item.lastTime)) : t('common.Un used')}
</Td>
<Td display={'flex'} alignItems={'center'}>
<Menu autoSelect={false} isLazy>
<MenuButton
@@ -197,7 +196,7 @@ const Share = ({ appId }: { appId: string }) => {
<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>
)}
@@ -208,7 +207,7 @@ const Share = ({ appId }: { appId: string }) => {
defaultData={editLinkData}
onCreate={(id) => {
const url = `${location.origin}/chat/share?shareId=${id}`;
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
copyData(url, t('core.app.share.Create link tip'));
refetchShareChatList();
setEditLinkData(undefined);
}}
@@ -268,14 +267,14 @@ function EditLinkModal({
appId,
type
}),
errorToast: '创建链接异常',
errorToast: t('common.Create Failed'),
onSuccess: onCreate
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: (e: OutLinkEditType) => {
return putShareChat(e);
},
errorToast: '更新链接异常',
errorToast: t('common.Update Failed'),
onSuccess: onEdit
});
@@ -384,14 +383,13 @@ function EditLinkModal({
<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>

View File

@@ -6,9 +6,11 @@ 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);
@@ -16,7 +18,7 @@ const OutLink = ({ appId }: { appId: string }) => {
return (
<Box pt={[1, 5]}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
使
{t('core.app.External using')}
</Box>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
@@ -25,14 +27,14 @@ const OutLink = ({ appId }: { appId: string }) => {
list={[
{
icon: '/imgs/modal/shareFill.svg',
title: '免登录窗口',
desc: '分享链接给其他用户,无需登录即可直接进行使用',
title: t('core.app.Share link'),
desc: t('core.app.Share link desc'),
value: OutLinkTypeEnum.share
},
{
icon: 'support/outlink/apikeyFill',
title: 'API 访问',
desc: '通过 API 接入到已有系统中,或企微、飞书等',
title: t('core.app.Api request'),
desc: t('core.app.Api request desc'),
value: OutLinkTypeEnum.apikey
}
// {

View File

@@ -96,7 +96,7 @@ const AppCard = ({ appId }: { appId: string }) => {
wordBreak={'break-all'}
color={'myGray.600'}
>
{appDetail.intro || '快来给应用一个介绍~'}
{appDetail.intro || t('core.app.tip.Add a intro to app')}
</Box>
<Flex>
<Button
@@ -105,7 +105,7 @@ const AppCard = ({ appId }: { appId: string }) => {
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
onClick={() => router.push(`/chat?appId=${appId}`)}
>
{t('core.Chat')}
</Button>
<Button
mx={3}
@@ -121,7 +121,7 @@ const AppCard = ({ appId }: { appId: string }) => {
});
}}
>
{t('core.app.navbar.External')}
</Button>
{appDetail.isOwner && (
<Button
@@ -130,7 +130,7 @@ const AppCard = ({ appId }: { appId: string }) => {
leftIcon={<MyIcon name={'common/settingLight'} w={'16px'} />}
onClick={() => setSettingAppInfo(appDetail)}
>
{t('common.Setting')}
</Button>
)}
</Flex>

View File

@@ -1,9 +1,9 @@
import React, { useMemo, useState } from 'react';
import React, { useCallback, useState, useTransition } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
import { Box, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
@@ -18,6 +18,7 @@ const CfrEditModal = ({
}) => {
const { t } = useTranslation();
const [value, setValue] = useState(defaultValue);
const [, startTst] = useTransition();
return (
<MyModal
@@ -32,17 +33,19 @@ const CfrEditModal = ({
<MyTooltip label={t('core.app.edit.cfr background tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<PromptTextarea
mt={1}
flex={1}
bg={'myWhite.400'}
rows={5}
placeholder={t('core.module.input.placeholder.cfr background')}
defaultValue={value}
onBlur={(e) => {
setValue(e.target.value || '');
}}
/>
<Box mt={1} flex={1}>
<PromptEditor
h={200}
showOpenModal={false}
placeholder={t('core.module.input.placeholder.cfr background')}
defaultValue={value}
onChange={useCallback((e: string) => {
startTst(() => {
setValue(e);
});
}, [])}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button

View File

@@ -114,7 +114,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
right={0}
left={0}
bottom={0}
bg={'rgba(255,255,255,0.6)'}
bg={'rgba(255,255,255,0.7)'}
alignItems={'center'}
justifyContent={'center'}
flexDirection={'column'}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useMemo, useState, useTransition } from 'react';
import {
Box,
Flex,
@@ -24,7 +24,6 @@ import { useTranslation } from 'next-i18next';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { postForm2Modules } from '@/web/core/app/utils';
import dynamic from 'next/dynamic';
@@ -34,9 +33,11 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea/index';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
import MyTextarea from '@/components/common/Textarea/MyTextarea/index';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import SelectAiModel from '@/components/Select/SelectAiModel';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { formatVariablesIcon } from '@fastgpt/global/core/module/utils';
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
@@ -61,8 +62,9 @@ const EditForm = ({
const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const [, startTst] = useTransition();
const { register, setValue, getValues, reset, handleSubmit, control } =
const { setValue, getValues, reset, handleSubmit, control, watch } =
useForm<AppSimpleEditFormType>({
defaultValues: getDefaultAppForm()
});
@@ -97,24 +99,25 @@ const EditForm = ({
content: t('core.app.edit.Confirm Save App Tip')
});
const chatModelSelectList = useMemo(() => {
return chatModelList.map((item) => ({
const variables = watch('userGuide.variables');
const formatVariables = useMemo(() => formatVariablesIcon(variables), [variables]);
const aiSystemPrompt = watch('aiSettings.systemPrompt');
const searchMode = watch('dataset.searchMode');
const chatModelSelectList = (() =>
chatModelList.map((item) => ({
value: item.model,
label: item.name
}));
}, [refresh]);
})))();
const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
[allDatasets, datasets]
);
const selectSimpleTemplate = useMemo(
() =>
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
SimpleModeTemplate_FastGPT_Universal,
[getValues, refresh]
);
const selectSimpleTemplate = (() =>
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
SimpleModeTemplate_FastGPT_Universal)();
const tokenLimit = useMemo(() => {
return (
@@ -124,10 +127,9 @@ const EditForm = ({
}, [getValues, refresh]);
const datasetSearchMode = useMemo(() => {
const mode = getValues('dataset.searchMode');
if (!mode) return '';
return t(DatasetSearchModeMap[mode]?.title);
}, [getValues, t, refresh]);
if (!searchMode) return '';
return t(DatasetSearchModeMap[searchMode]?.title);
}, [searchMode, t]);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
@@ -144,21 +146,21 @@ const EditForm = ({
errorToast: t('common.Save Failed')
});
const appModule2Form = useCallback(() => {
const formVal = appModules2Form({
templateId: appDetail.simpleTemplateId,
modules: appDetail.modules
});
reset(formVal);
setTimeout(() => {
setRefresh((state) => !state);
}, 100);
}, [appDetail.modules, appDetail.simpleTemplateId, reset]);
useEffect(() => {
appModule2Form();
}, [appModule2Form]);
const { isSuccess: isInitd } = useQuery(
['init', appDetail],
() => {
const formatVal = appModules2Form({
templateId: appDetail.simpleTemplateId,
modules: appDetail.modules
});
reset(formatVal);
setRefresh(!refresh);
return formatVal;
},
{
enabled: !!appDetail._id
}
);
useQuery(['loadAllDatasets'], loadAllDatasets);
const BoxStyles: BoxProps = {
@@ -229,15 +231,15 @@ const EditForm = ({
{/* simple mode select */}
<Flex {...BoxStyles}>
<Flex alignItems={'center'} flex={'1 0 0'}>
<Image alt={''} src={'/imgs/module/templates.png'} w={'18px'} />
<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: item.name,
label: item.desc,
alias: t(item.name),
label: t(item.desc),
value: item.id
})) || []
}
@@ -253,7 +255,7 @@ const EditForm = ({
{selectSimpleTemplate?.systemForm?.aiSettings && (
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/AI.png'} w={'18px'} />
<MyIcon name={'core/app/simpleMode/ai'} w={'20px'} />
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
@@ -263,7 +265,7 @@ const EditForm = ({
selectSimpleTemplate.systemForm.aiSettings.quotePrompt) && (
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'common/settingLight'} w={'14px'} />
{t('app.Open AI Advanced Settings')}
{t('common.More settings')}
</Flex>
)}
</Flex>
@@ -293,20 +295,23 @@ const EditForm = ({
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={chatNodeSystemPromptTip} forceShow>
<MyTooltip label={t(chatNodeSystemPromptTip)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<PromptTextarea
flex={1}
bg={'myWhite.400'}
rows={5}
placeholder={chatNodeSystemPromptTip}
defaultValue={getValues('aiSettings.systemPrompt')}
onBlur={(e) => {
setValue('aiSettings.systemPrompt', e.target.value || '');
}}
/>
{isInitd && (
<PromptEditor
defaultValue={aiSystemPrompt}
onChange={(text) => {
startTst(() => {
setValue('aiSettings.systemPrompt', text);
});
}}
variables={formatVariables}
placeholder={t('core.app.tip.chatNodeSystemPromptTip')}
title={t('core.ai.Prompt')}
/>
)}
</Flex>
)}
</Box>
@@ -317,7 +322,7 @@ const EditForm = ({
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<Image alt={''} src={'/imgs/module/db.png'} w={'18px'} />
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
{selectSimpleTemplate.systemForm.dataset.datasets && (
@@ -409,7 +414,7 @@ const EditForm = ({
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
<Box {...BoxStyles}>
<VariableEdit
variables={getValues('userGuide.variables')}
variables={variables}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
@@ -422,17 +427,17 @@ const EditForm = ({
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
<MyIcon name={'core/app/simpleMode/chat'} w={'20px'} />
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={welcomeTextTip} forceShow>
<MyTooltip label={t(welcomeTextTip)} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<PromptTextarea
<MyTextarea
mt={2}
bg={'myWhite.400'}
rows={5}
placeholder={welcomeTextTip}
placeholder={t(welcomeTextTip)}
defaultValue={getValues('userGuide.welcomeText')}
onBlur={(e) => {
setValue('userGuide.welcomeText', e.target.value || '');
@@ -481,6 +486,7 @@ const EditForm = ({
}}
defaultData={getValues('aiSettings')}
simpleModeTemplate={selectSimpleTemplate}
pickerMenu={formatVariables}
/>
)}
{isOpenDatasetSelect && (

View File

@@ -16,6 +16,7 @@ import SimpleEdit from './components/SimpleEdit';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
loading: () => <Loading />
@@ -32,6 +33,7 @@ enum TabEnum {
}
const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation();
const router = useRouter();
const theme = useTheme();
const { toast } = useToast();
@@ -52,23 +54,39 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const tabList = useMemo(
() => [
{ label: '简易配置', id: TabEnum.simpleEdit, icon: 'common/overviewLight' },
{
label: t('core.app.navbar.Simple mode'),
id: TabEnum.simpleEdit,
icon: 'common/overviewLight'
},
...(feConfigs?.hide_app_flow
? []
: [{ label: '高级编排', id: TabEnum.adEdit, icon: 'common/settingLight' }]),
{ label: '外部使用', id: TabEnum.outLink, icon: 'support/outlink/shareLight' },
{ label: '对话日志', id: TabEnum.logs, icon: 'core/app/logsLight' },
{ label: '立即对话', id: TabEnum.startChat, icon: 'core/chat/chatLight' }
: [
{
label: t('core.app.navbar.Flow mode'),
id: TabEnum.adEdit,
icon: 'core/modules/flowLight'
}
]),
{
label: t('core.app.navbar.External'),
id: TabEnum.outLink,
icon: 'support/outlink/shareLight'
},
{ label: t('app.Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' },
{ label: t('core.Start chat'), id: TabEnum.startChat, icon: 'core/chat/chatLight' }
],
[]
[t]
);
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
useEffect(() => {
const listen =
process.env.NODE_ENV === 'production'
? (e: any) => {
e.preventDefault();
e.returnValue = '内容已修改,确认离开页面吗?';
e.returnValue = t('core.common.tip.leave page');
}
: () => {};
window.addEventListener('beforeunload', listen);
@@ -82,7 +100,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
useQuery([appId], () => loadAppDetail(appId, true), {
onError(err: any) {
toast({
title: err?.message || '获取应用异常',
title: err?.message || t('core.app.error.Get app failed'),
status: 'error'
});
router.replace('/app/list');
@@ -146,7 +164,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
borderRadius={'50%'}
aria-label={''}
/>
{t('app.My Apps')}
</Flex>
</Box>
{/* phone tab */}
@@ -173,7 +191,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
{currentTab === TabEnum.adEdit && appDetail && (
<FlowEdit app={appDetail} onClose={() => setCurrentTab(TabEnum.simpleEdit)} />
<FlowEdit app={appDetail} onClose={onCloseFlowEdit} />
)}
{currentTab === TabEnum.logs && <Logs appId={appId} />}
{currentTab === TabEnum.outLink && <OutLink appId={appId} />}

View File

@@ -3,7 +3,6 @@ import {
Box,
Flex,
Button,
ModalHeader,
ModalFooter,
ModalBody,
Input,
@@ -69,19 +68,19 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, '头像选择异常'),
title: getErrText(err, t('common.error.Select avatar failed')),
status: 'warning'
});
}
},
[setValue, toast]
[setValue, t, toast]
);
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (data: FormType) => {
const template = appTemplates.find((item) => item.id === data.templateId);
if (!template) {
return Promise.reject('模板不存在');
return Promise.reject(t('core.dataset.error.Template does not exist'));
}
return postCreateApp({
avatar: data.avatar,
@@ -95,8 +94,8 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
onSuccess();
onClose();
},
successToast: '创建成功',
errorToast: '创建应用异常'
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
return (
@@ -109,10 +108,10 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('common.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={'点击设置头像'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
@@ -129,14 +128,14 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
autoFocus
bg={'myWhite.600'}
{...register('name', {
required: '应用名不能为空~'
required: t('core.app.error.App name can not be empty')
})}
/>
</Flex>
{!feConfigs?.hide_app_flow && (
<>
<Box mt={[4, 7]} mb={[0, 3]} color={'myGray.800'} fontWeight={'bold'}>
{t('core.app.Select app from template')}
</Box>
<Grid
userSelect={'none'}
@@ -168,11 +167,11 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
<Flex alignItems={'center'}>
<Avatar src={item.avatar} borderRadius={'md'} w={'20px'} />
<Box ml={3} fontWeight={'bold'}>
{item.name}
{t(item.name)}
</Box>
</Flex>
<Box fontSize={'sm'} mt={4}>
{item.intro}
{t(item.intro)}
</Box>
</Card>
))}
@@ -183,10 +182,10 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={creating} onClick={handleSubmit((data) => onclickCreate(data))}>
{t('common.Confirm Create')}
</Button>
</ModalFooter>

View File

@@ -348,7 +348,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
userGuideModule={chatData.app?.userGuideModule}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })}
appId={appId}

Some files were not shown because too many files have changed in this diff Show More