This commit is contained in:
Archer
2023-11-09 09:46:57 +08:00
committed by GitHub
parent 661ee79943
commit 8bb5588305
402 changed files with 9899 additions and 5967 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Image } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react';
import { LOGO_ICON } from '@/constants/chat';
import { LOGO_ICON } from '@fastgpt/global/core/chat/constants';
const Avatar = ({ w = '30px', ...props }: ImageProps) => {
return (

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import { ChatItemType } from '@/types/chat';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import MyModal from '../MyModal';
const ContextModal = ({

View File

@@ -16,6 +16,7 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty
import MyTooltip from '../MyTooltip';
import NextLink from 'next/link';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useUserStore } from '@/web/support/user/useUserStore';
const QuoteModal = ({
rawSearch = [],
@@ -28,14 +29,15 @@ const QuoteModal = ({
const { isPc } = useSystemStore();
const theme = useTheme();
const router = useRouter();
const { userInfo } = useUserStore();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editInputData, setEditInputData] = useState<InputDataType>();
const [editInputData, setEditInputData] = useState<InputDataType & { datasetId: string }>();
const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]);
/**
* click edit, get new kbDataItem
* click edit, get new DataItem
*/
const onclickEdit = useCallback(
async (item: InputDataType) => {
@@ -181,6 +183,7 @@ const QuoteModal = ({
</MyModal>
{editInputData && editInputData.id && (
<InputDataModal
canWrite={userInfo?.team?.canWrite || false}
onClose={() => setEditInputData(undefined)}
onSuccess={() => {
console.log('更新引用成功');

View File

@@ -1,5 +1,6 @@
import React, { useMemo, useState } from 'react';
import { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';

View File

@@ -6,7 +6,7 @@ import MyIcon from '@/components/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
import dynamic from 'next/dynamic';
import { MarkDataType } from '@/global/core/dataset/type';
import { AdminFbkType } from '@fastgpt/global/core/chat/type.d';
import SelectCollections from '@/web/core/dataset/components/SelectCollections';
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
@@ -28,7 +28,7 @@ const SelectMarkCollection = ({
adminMarkData: AdminMarkType;
setAdminMarkData: (e: AdminMarkType) => void;
onClose: () => void;
onSuccess: (adminFeedback: MarkDataType) => void;
onSuccess: (adminFeedback: AdminFbkType) => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
@@ -166,12 +166,12 @@ const SelectMarkCollection = ({
datasetId={adminMarkData.datasetId}
defaultValues={{
id: adminMarkData.dataId,
datasetId: adminMarkData.datasetId,
collectionId: adminMarkData.collectionId,
sourceName: '手动标注',
q: adminMarkData.q,
a: adminMarkData.a
}}
canWrite
onSuccess={(data) => {
if (!data.q || !adminMarkData.datasetId || !adminMarkData.collectionId || !data.id) {
return onClose();

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@/types/chat';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { useTranslation } from 'react-i18next';
import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate';
import Tabs from '../Tabs';
@@ -8,7 +8,7 @@ import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
function Row({ label, value }: { label: string; value?: string | number | React.ReactNode }) {
const theme = useTheme();

View File

@@ -10,12 +10,9 @@ import React, {
} from 'react';
import Script from 'next/script';
import { throttle } from 'lodash';
import {
ChatHistoryItemResType,
ChatItemType,
ChatSiteItemType,
ExportChatType
} from '@/types/chat';
import type { ExportChatType } from '@/types/chat.d';
import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { useToast } from '@/web/common/hooks/useToast';
import { useAudioPlay } from '@/web/common/utils/voice';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -38,12 +35,12 @@ import { useMarkdown } from '@/web/common/hooks/useMarkdown';
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { VariableInputEnum } from '@/constants/app';
import { useForm } from 'react-hook-form';
import type { MessageItemType } from '@/types/core/chat/type';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { fileDownload } from '@/web/common/file/utils';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { useTranslation } from 'react-i18next';
import { customAlphabet } from 'nanoid';
import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api';
@@ -64,6 +61,7 @@ const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection'));
import styles from './index.module.scss';
import { postQuestionGuide } from '@/web/core/ai/api';
import { splitGuideModule } from '@/global/core/app/modules/utils';
import { AppTTSConfigType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -73,7 +71,7 @@ type generatingMessageProps = { text?: string; name?: string; status?: 'running'
export type StartChatFnProps = {
chatList: ChatSiteItemType[];
messages: MessageItemType[];
messages: ChatMessageItemType[];
controller: AbortController;
variables: Record<string, any>;
generatingMessage: (e: generatingMessageProps) => void;
@@ -158,7 +156,7 @@ const ChatBox = (
[chatHistory]
);
const { welcomeText, variableModules, questionGuide } = useMemo(
const { welcomeText, variableModules, questionGuide, ttsConfig } = useMemo(
() => splitGuideModule(userGuideModule),
[userGuideModule]
);
@@ -206,32 +204,28 @@ const ChatBox = (
[]
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const generatingMessage = useCallback(
// concat text to end of message
({ text = '', status, name }: generatingMessageProps) => {
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
...(text
? {
value: item.value + text
}
: {}),
...(status && name
? {
status,
moduleName: name
}
: {})
};
})
);
generatingScroll();
},
[generatingScroll, setChatHistory]
);
const generatingMessage = ({ text = '', status, name }: generatingMessageProps) => {
setChatHistory((state) =>
state.map((item, index) => {
if (index !== state.length - 1) return item;
return {
...item,
...(text
? {
value: item.value + text
}
: {}),
...(status && name
? {
status,
moduleName: name
}
: {})
};
})
);
generatingScroll();
};
// 重置输入内容
const resetInputVal = useCallback((val: string) => {
@@ -489,7 +483,7 @@ const ChatBox = (
return {
bg: colorMap[chatContent.status] || colorMap.loading,
name: t(chatContent.moduleName || 'common.Loading')
name: chatContent.moduleName || t('common.Loading')
};
}, [chatHistory, isChatting, t]);
/* style end */
@@ -654,8 +648,10 @@ const ChatBox = (
<ChatController
ml={2}
chat={item}
setChatHistory={setChatHistory}
display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
showVoiceIcon={showVoiceIcon}
ttsConfig={ttsConfig}
onDelete={
onDelMessage
? () => {
@@ -801,13 +797,20 @@ const ChatBox = (
</Box>
{/* message input */}
{onStartChat && variableIsFinish && active ? (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
<Box
py={'18px'}
position={'relative'}
boxShadow={`0 0 10px rgba(0,0,0,0.2)`}
borderTop={['1px solid', 0]}
borderTopColor={'myGray.200'}
{...(isPc
? {
border: '1px solid',
borderColor: 'rgba(0,0,0,0.12)'
}
: {
borderTop: '1px solid',
borderTopColor: 'rgba(0,0,0,0.15)'
})}
borderRadius={['none', 'md']}
backgroundColor={'white'}
>
@@ -836,6 +839,7 @@ const ChatBox = (
const textarea = e.target;
textarea.style.height = textareaMinH;
textarea.style.height = `${textarea.scrollHeight}px`;
setRefresh((state) => !state);
}}
onKeyDown={(e) => {
// enter send.(pc or iframe && enter and unPress shift)
@@ -852,11 +856,14 @@ const ChatBox = (
<Flex
alignItems={'center'}
justifyContent={'center'}
h={'25px'}
w={'25px'}
h={['26px', '32px']}
w={['26px', '32px']}
position={'absolute'}
right={['12px', '20px']}
bottom={'15px'}
right={['12px', '14px']}
bottom={['15px', '13px']}
borderRadius={'md'}
bg={TextareaDom.current?.value ? 'myBlue.600' : ''}
lineHeight={1}
>
{isChatting ? (
<MyIcon
@@ -869,16 +876,18 @@ const ChatBox = (
onClick={() => chatController.current?.abort('stop')}
/>
) : (
<MyIcon
name={'chatSend'}
width={['18px', '20px']}
height={['18px', '20px']}
cursor={'pointer'}
color={'gray.500'}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
<MyTooltip label={t('core.chat.Send Message')}>
<MyIcon
name={'core/chat/sendFill'}
width={'16px'}
height={'16px'}
cursor={'pointer'}
color={TextareaDom.current?.value ? 'white' : 'myBlue.600'}
onClick={() => {
handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))();
}}
/>
</MyTooltip>
)}
</Flex>
</Box>
@@ -1106,8 +1115,10 @@ function Empty() {
function ChatController({
chat,
setChatHistory,
display,
showVoiceIcon,
ttsConfig,
onReadFeedback,
onMark,
onRetry,
@@ -1117,7 +1128,9 @@ function ChatController({
mr
}: {
chat: ChatSiteItemType;
setChatHistory?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
showVoiceIcon?: boolean;
ttsConfig?: AppTTSConfigType;
onRetry?: () => void;
onDelete?: () => void;
onMark?: () => void;
@@ -1127,7 +1140,9 @@ function ChatController({
const theme = useTheme();
const { t } = useTranslation();
const { copyData } = useCopyData();
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({});
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
ttsConfig
});
const controlIconStyle = {
w: '14px',
cursor: 'pointer',
@@ -1198,7 +1213,24 @@ function ChatController({
{...controlIconStyle}
name={'voice'}
_hover={{ color: '#E74694' }}
onClick={() => playAudio(chat.value)}
onClick={async () => {
const buffer = await playAudio({
buffer: chat.ttsBuffer,
chatItemId: chat.dataId,
text: chat.value
});
if (!setChatHistory) return;
setChatHistory((state) =>
state.map((item) =>
item.dataId === chat.dataId
? {
...item,
ttsBuffer: buffer
}
: item
)
);
}}
/>
</MyTooltip>
))}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698673802976"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2726"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M496.68187246 465.60374835c121.95586149 0 220.9345317-98.9786702 220.9345317-220.9345317s-98.9786702-220.9345317-220.9345317-220.93453167-220.9345317 98.9786702-220.93453167 220.93453167 98.9786702 220.9345317 220.93453167 220.9345317z m-125.04894492-345.98347662c33.43475913-33.28746944 77.76895517-51.69868042 125.04894492-51.69868042 47.27998978 0 91.61418581 18.41121097 125.04894495 51.69868042 33.43475913 33.43475913 51.69868042 77.76895517 51.69868041 125.04894492 0 47.27998978-18.41121097 91.61418581-51.69868041 125.04894495-33.43475913 33.43475913-77.76895517 51.69868042-125.04894495 51.69868042-47.27998978 0-91.61418581-18.41121097-125.04894492-51.69868042-33.43475913-33.43475913-51.69868042-77.76895517-51.69868041-125.04894495 0-47.27998978 18.41121097-91.61418581 51.69868041-125.04894492zM511.41084125 892.59655326h-213.5700473c-81.15661796 0-147.2896878-66.13306981-147.28968778-147.2896878 0-39.32634663 15.31812754-76.29605827 43.1558785-104.13380927 27.83775099-27.83775099 64.80746262-43.15587853 104.13380928-43.15587853h397.68215704c52.14054948 0 100.89343614 27.98504069 127.25829025 73.05568516 6.1861669 10.60485751 19.73681815 14.13981003 30.19438599 7.95364313 10.60485751-6.1861669 14.13981003-19.73681815 7.95364314-30.19438599-34.31849726-58.47400606-97.65306301-94.85455893-165.40631938-94.85455893h-397.68215704c-51.10952167 0-99.12595989 19.88410785-135.35922308 56.11737104-36.2332632 36.0859735-56.11737105 84.24970142-56.11737105 135.35922308 0 105.60670615 85.86988799 191.47659413 191.47659413 191.47659413h213.5700473c12.22504409 0 22.09345317-10.01569877 22.09345316-22.24074285s-9.86840908-22.09345317-22.09345316-22.09345317z"
p-id="2727"></path>
<path
d="M873.74347321 830.88217408h-103.10278144v-103.10278145c0-12.22504409-9.86840908-22.09345317-22.09345318-22.09345318s-22.09345317 9.86840908-22.09345316 22.09345318v103.10278145h-103.10278146c-12.22504409 0-22.09345317 9.86840908-22.09345318 22.09345315s9.86840908 22.09345317 22.09345318 22.09345318h103.10278146v103.10278146c0 12.22504409 9.86840908 22.09345317 22.09345316 22.09345315s22.09345317-9.86840908 22.09345318-22.09345315v-103.10278146h103.10278144c12.22504409 0 22.09345317-9.86840908 22.09345318-22.09345318s-9.86840908-22.09345317-22.09345318-22.09345315z"
p-id="2728"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699444463613" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2759" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 58.02666667c250.31111111 0 453.97333333 203.66222222 453.97333333 453.97333333S762.31111111 965.97333333 512 965.97333333 58.02666667 762.31111111 58.02666667 512s203.66222222-453.97333333 453.97333333-453.97333333m0-47.78666667C234.83733333 10.24 10.24 234.83733333 10.24 512s224.59733333 501.76 501.76 501.76 501.76-224.59733333 501.76-501.76S789.16266667 10.24 512 10.24z" p-id="2760"></path><path d="M430.42133333 723.17155555c-34.01955555 0-61.78133333-27.648-61.78133333-61.78133333V362.496c0-34.01955555 27.76177778-61.78133333 61.78133333-61.78133333 10.69511111 0 21.27644445 2.84444445 30.72 8.30577778L720.09955555 458.52444445c19.34222222 11.15022222 30.83377778 31.17511111 30.83377778 53.47555555s-11.49155555 42.32533333-30.83377778 53.47555555L461.25511111 714.86577778c-9.55733333 5.46133333-20.13866667 8.30577778-30.83377778 8.30577777z m0-374.55644444c-6.71288889 0-13.99466667 5.34755555-13.99466666 13.99466667v298.89422222c0 11.71911111 12.40177778 16.95288889 20.93511111 12.06044445l258.84444444-149.504c6.25777778-3.64088889 6.94044445-9.55733333 6.94044445-12.06044445s-0.68266667-8.41955555-6.94044445-12.06044445L437.36177778 350.43555555c-2.27555555-1.25155555-4.55111111-1.82044445-6.94044445-1.82044444z" p-id="2761"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698673301556" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4065" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z" p-id="4066"></path></svg>

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699434372634" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4041" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M189.184 349.952c14.336 0 27.904 5.632 38.144 15.872 9.984 9.984 15.872 23.808 15.872 38.144v215.04c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S135.68 638.208 135.68 619.008v-215.04c0-14.336 5.632-27.904 15.872-38.144 9.728-10.24 23.552-15.872 37.632-15.872zM404.48 134.656c29.696 0 53.76 24.064 53.76 53.76v645.376c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S350.72 852.992 350.72 833.792V188.416c0-14.336 5.632-27.904 15.872-38.144 9.984-9.984 23.552-15.616 37.888-15.616z m215.04 134.4c29.696 0 53.76 24.064 53.76 53.76v376.576c0 19.2-10.24 36.864-26.88 46.592-16.64 9.728-37.12 9.728-53.76 0S565.76 718.592 565.76 699.392v-376.32c0-29.696 24.064-54.016 53.76-54.016z m215.296 80.896c29.696 0 53.76 24.064 53.76 53.76v242.176c0 29.696-24.064 53.76-53.76 53.76s-53.76-24.064-53.76-53.76v-242.176c0-29.696 24.064-53.76 53.76-53.76z m0 0" fill="#E67E22" p-id="4042"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" class="icon-sm m-1 md:m-0">
<path
d="M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z"
fill="currentColor"></path>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -1 +1,8 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691412437336" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2312" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 14.8973037a497.1026963 497.1026963 0 1 0 497.1026963 497.1026963A497.1026963 497.1026963 0 0 0 512 14.8973037z m165.70089876 581.8863224l-207.12612345 124.27567408L346.29910124 796.45320912V255.16360691l125.38034629 75.39390894 207.12612347 124.27567407 118.47614261 70.97521873z" p-id="2313"></path></svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691412437336"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2312"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M512 14.8973037a497.1026963 497.1026963 0 1 0 497.1026963 497.1026963A497.1026963 497.1026963 0 0 0 512 14.8973037z m165.70089876 581.8863224l-207.12612345 124.27567408L346.29910124 796.45320912V255.16360691l125.38034629 75.39390894 207.12612347 124.27567407 118.47614261 70.97521873z"
p-id="2313"></path>
</svg>

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698892598531"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4968"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M696.917333 85.333333H327.04C182.101333 85.333333 85.333333 189.141333 85.333333 337.749333v348.501334C85.333333 835.029333 181.845333 938.666667 327.04 938.666667h369.834667C842.154667 938.666667 938.666667 834.986667 938.666667 686.250667V337.749333C938.666667 189.013333 842.154667 85.333333 696.917333 85.333333zM327.04 149.333333h369.92c108.8 0 177.706667 74.026667 177.706667 188.416v348.501334c0 114.389333-68.906667 188.416-177.792 188.416H327.04c-108.8 0-177.706667-73.984-177.706667-188.416V337.749333C149.333333 223.573333 218.453333 149.333333 327.04 149.333333zM377.173333 400.981333a111.018667 111.018667 0 1 0 106.325334 143.018667h89.6v47.018667l0.256 4.309333a32 32 0 0 0 63.744-4.309333V544h56.661333v47.018667l0.298667 4.352a32 32 0 0 0 63.701333-4.352V512l-0.298667-4.309333a32 32 0 0 0-31.701333-27.690667h-119.466667a36.138667 36.138667 0 0 0-2.389333 0h-120.405333A111.104 111.104 0 0 0 377.173333 400.981333z m0 64a47.018667 47.018667 0 1 1 0 94.08 47.018667 47.018667 0 0 1 0-94.08z"
p-id="4969"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698895784986" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6347" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M655.39072 491.8784l-19.01568-7.552 16.128-12.58496c55.53152-43.34592 87.38304-108.11904 87.38304-177.70496 0-124.6464-102.1184-226.05312-227.6352-226.05312-125.52192 0-227.64032 101.40672-227.64032 226.05312 0 69.58592 31.85664 134.35904 87.39328 177.70496l16.128 12.58496-19.01568 7.552c-148.26496 58.85952-244.06528 198.97856-244.06528 356.98176v79.24736c0 15.39072 12.63104 27.91424 28.16 27.91424h718.08c15.52384 0 28.16-12.52352 28.16-27.91424v-79.24736c0-157.99808-95.80032-298.12736-244.06016-356.98176zM335.49824 293.72416c0-96.85504 79.29344-175.65696 176.75264-175.65696 97.46944 0 176.75776 78.80192 176.75776 175.65696s-79.28832 175.65696-176.75776 175.65696c-97.4592 0-176.75264-78.80192-176.75264-175.65696z m514.45248 611.10784H174.52032V852.224c0-187.84768 148.69504-340.66944 337.76128-340.66944 189.01504 0 337.66912 152.82176 337.66912 340.66944v52.608z" p-id="6348"></path><path d="M920.54528 837.1456c-0.5376-17.92512-2.28352-32.57856-5.3248-50.08384h54.63552v-45.52192c0-122.48576-77.85472-228.96128-189.33248-258.93888l-7.36768-1.90464c-12.09344-4.50048-20.18816-15.6416-20.18816-27.85792 0-11.97568 7.59808-22.784 19.35872-27.53024l0.68608-0.30208c46.87872-22.66112 77.16864-72.67328 77.16864-127.41632 0-59.74016-52.08064-110.87872-103.06048-130.72384a271.58528 271.58528 0 0 0-33.82272-68.69504c97.9456 6.61504 175.72352 93.54752 175.72352 199.45472 0 49.45408-17.34144 97.0496-48.82944 134.00064l-10.33728 12.12928 13.62944 7.51104c101.92896 56.2176 165.21216 167.43936 165.15584 290.25792v65.70496c0 16.49152-12.63616 29.91104-28.15488 29.91104h-59.93984zM38.4 837.14048c-15.52384 0-28.16-13.40928-28.16-29.88544v-65.61792c0-122.69568 63.3088-233.79456 165.2224-289.94048l13.62944-7.50592-10.33216-12.11392c-31.488-36.91008-48.83456-84.44928-48.83456-133.85728 0-105.75872 77.77792-192.57856 175.7184-199.1936a272.31232 272.31232 0 0 0-33.8432 68.62336c-48.8192 19.17952-96.41984 68.90496-96.41984 130.57024 0 54.66624 30.28992 104.62208 77.16864 127.26784l0.68096 0.30208c11.75552 4.75648 19.3536 15.54944 19.3536 27.50464 0 12.2112-8.0896 23.34208-20.1728 27.83744l-7.26016 1.85856c-111.60064 29.952-189.45536 136.30464-189.45536 258.65216v45.48096h47.9744c-3.0464 17.56672-4.79744 32.21504-5.33504 50.02752H38.4z" p-id="6349"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698724877512" class="icon" viewBox="0 0 1252 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4699" xmlns:xlink="http://www.w3.org/1999/xlink" width="156.5" height="128"><path d="M1164.136052 546.068492h-143.393656a199.609195 199.609195 0 0 1-70.816241-4.544965 36.927843 36.927843 0 0 1-16.305063-19.884224l-1.619144-8.52181a35.166669 35.166669 0 0 1 29.400245-37.495963c16.049409 0 32.070411 0 48.11982-0.284061h159.698719c41.955711 0 74.395401-5.681207 80.729946 31.246637a31.587509 31.587509 0 0 1-4.033657 20.168283c-13.095181 25.56543-44.767908 19.316103-81.780969 19.316103z m60.135572-199.126293a125.838727 125.838727 0 0 1-29.655899 1.136242H878.71223a100.670982 100.670982 0 0 1-33.405495-2.840604 36.586971 36.586971 0 0 1-18.463921-19.600163c-0.710151-2.556543-1.420302-5.397146-2.130453-8.237749a34.996233 34.996233 0 0 1 28.406033-39.200326 131.179061 131.179061 0 0 1 28.065161-0.568121h337.293238a35.393917 35.393917 0 0 1 5.823237 69.310721z m-618.228906 233.213533A437.736971 437.736971 0 0 1 874.905822 982.669222a41.330778 41.330778 0 0 1-82.633151 0 354.223234 354.223234 0 0 0-344.621994-353.371053h-20.395532A354.223234 354.223234 0 0 0 82.633151 982.669222a41.330778 41.330778 0 0 1-82.633151 0 437.736971 437.736971 0 0 1 267.755269-402.51349 314.710442 314.710442 0 1 1 338.287449 0zM434.015781 83.902332a231.622795 231.622795 0 0 0-6.760636 463.018341h20.395532a231.622795 231.622795 0 0 0-13.634896-463.018341zM1040.797056 682.985572a162.596134 162.596134 0 0 1 66.81099-4.829026h66.697365c40.535409 0 69.594781-3.976845 75.645267 31.246637a32.922592 32.922592 0 0 1-4.033657 20.452343c-12.157782 23.292947-37.80843 19.032042-72.435385 19.032043h-79.224426a158.647695 158.647695 0 0 1-50.534333-3.124664 35.59276 35.59276 0 0 1-18.151455-19.316103l-2.158859-8.521809A35.905226 35.905226 0 0 1 1040.797056 682.985572z" fill="#333333" p-id="4700"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -98,7 +98,15 @@ const iconPaths = {
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
'core/module/previewLight': () => import('./icons/core/module/previewLight.svg'),
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg')
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'),
'common/tickFill': () => import('./icons/common/tickFill.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'),
'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'),
'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'),
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
'common/playLight': () => import('./icons/common/playLight.svg'),
'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg')
};
export type IconName = keyof typeof iconPaths;

View File

@@ -4,12 +4,19 @@ import { useRouter } from 'next/router';
import { useLoading } from '@/web/common/hooks/useLoading';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { throttle } from 'lodash';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/inform/api';
import { feConfigs } from '@/web/common/system/staticData';
import dynamic from 'next/dynamic';
import Auth from './auth';
import Navbar from './navbar';
import NavbarPhone from './navbarPhone';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/api';
const UpdateInviteModal = dynamic(
() => import('@/components/support/user/team/UpdateInviteModal'),
{ ssr: false }
);
const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
@@ -60,7 +67,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
}, [loadGitStar, setScreenWidth]);
const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, {
enabled: !!userInfo,
enabled: !!userInfo && feConfigs.isPlus,
refetchInterval: 10000
});
@@ -103,6 +110,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
)}
</Box>
<Loading loading={loading} zIndex={9999} />
{!!userInfo && <UpdateInviteModal />}
</>
);
};

View File

@@ -3,7 +3,7 @@ import { Box, Flex, Link } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import { HUMAN_ICON } from '@/constants/chat';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { feConfigs } from '@/web/common/system/staticData';
import NextLink from 'next/link';
import Badge from '../Badge';

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Box, useTheme } from '@chakra-ui/react';
import { getFileAndOpen } from '@/web/common/file/utils';
import { getFileAndOpen } from '@/web/core/dataset/utils';
import { useToast } from '@/web/common/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';

View File

@@ -91,7 +91,7 @@ const MermaidBlock = ({ code }: { code: string }) => {
a.download = 'mermaid.jpg';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
document.body?.removeChild(a);
};
img.onerror = (e) => {
console.log(e);

View File

@@ -41,7 +41,8 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
e.stopPropagation();
item.onClick && item.onClick();
}}
color={item.isActive ? 'hover.blue' : ''}
color={item.isActive ? 'myBlue.600' : ''}
whiteSpace={'pre-wrap'}
>
{item.child}
</MenuItem>

View File

@@ -10,6 +10,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
const { isPc } = useSystemStore();
return isPc || forceShow ? (
<Tooltip
className="tooltip"
bg={'white'}
arrowShadowColor={' rgba(0,0,0,0.05)'}
hasArrow

View File

@@ -69,7 +69,7 @@ const MyRadio = ({
onClick={() => onChange(item.value)}
>
{!!item.icon && <MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />}
<Box>
<Box pr={2}>
<Box>{item.title}</Box>
{!!item.desc && (
<Box fontSize={'sm'} color={'myGray.500'}>

View File

@@ -1,4 +1,4 @@
import React, { useRef, forwardRef } from 'react';
import React, { useRef, forwardRef, useMemo } from 'react';
import {
Menu,
Box,
@@ -14,10 +14,11 @@ interface Props extends ButtonProps {
value?: string;
placeholder?: string;
list: {
label: string;
alias?: string;
label: string | React.ReactNode;
value: string;
}[];
onchange?: (val: string) => void;
onchange?: (val: any) => void;
}
const MySelect = (
@@ -36,6 +37,7 @@ const MySelect = (
}
};
const { isOpen, onOpen, onClose } = useDisclosure();
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
useOutsideClick({
ref: SelectRef,
@@ -72,7 +74,7 @@ const MySelect = (
: {})}
{...props}
>
{list.find((item) => item.value === value)?.label || placeholder}
{selectItem?.alias || selectItem?.label || placeholder}
<Box flex={1} />
<ChevronDownIcon />
</Button>
@@ -103,7 +105,8 @@ const MySelect = (
{...menuItemStyles}
{...(value === item.value
? {
color: 'myBlue.600'
color: 'myBlue.600',
bg: 'myWhite.300'
}
: {})}
onClick={() => {
@@ -111,6 +114,7 @@ const MySelect = (
onchange(item.value);
}
}}
whiteSpace={'pre-wrap'}
>
{item.label}
</MenuItem>

View File

@@ -0,0 +1,99 @@
import React, { useCallback, useRef, useState } from 'react';
import {
Box,
BoxProps,
Flex,
Input,
Tag,
TagCloseButton,
TagLabel,
useTheme
} from '@chakra-ui/react';
import { useToast } from '@/web/common/hooks/useToast';
import { useTranslation } from 'react-i18next';
type Props = BoxProps & { defaultValues: string[]; onUpdate: (e: string[]) => void };
const TagTextarea = ({ defaultValues, onUpdate, ...props }: Props) => {
const theme = useTheme();
const InputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();
const { toast } = useToast();
const [focus, setFocus] = useState(false);
const [tags, setTags] = useState<string[]>(defaultValues);
const onUpdateValue = useCallback(
(value?: string) => {
setFocus(false);
if (!value || !InputRef.current?.value) {
return;
}
if (tags.includes(value)) {
toast({
status: 'warning',
title: t('common.input.Repeat Value')
});
}
setTags([...tags, value]);
onUpdate([...tags, value]);
InputRef.current.value = '';
},
[onUpdate, t, tags, toast]
);
return (
<Box
w={'100%'}
minH={'200px'}
borderRadius={'md'}
border={theme.borders.base}
p={2}
fontSize={'sm'}
bg={'myWhite.600'}
{...(focus && {
boxShadow: '0px 0px 4px #A8DBFF',
borderColor: 'myBlue.600'
})}
{...props}
onClick={() => {
if (!focus) {
InputRef.current?.focus();
setFocus(true);
}
}}
>
<Flex alignItems={'center'} gap={2} flexWrap={'wrap'}>
{tags.map((tag, i) => (
<Tag key={tag} colorScheme="blue" onClick={(e) => e.stopPropagation()}>
<TagLabel>{tag}</TagLabel>
<TagCloseButton
onClick={() => {
const val = tags.filter((_, index) => index !== i);
setTags(val);
onUpdate(val);
}}
/>
</Tag>
))}
<Input
ref={InputRef}
variant={'unstyled'}
display={'inline-block'}
w="auto"
onBlur={(e) => {
const value = e.target.value;
onUpdateValue(value);
}}
onKeyDown={(e) => {
if (e.keyCode === 13) {
e.preventDefault();
onUpdateValue(InputRef.current?.value);
}
}}
/>
</Flex>
</Box>
);
};
export default TagTextarea;

View File

@@ -49,7 +49,7 @@ const AIChatSettingsModal = ({
}>();
const tokenLimit = useMemo(() => {
return chatModelList.find((item) => item.model === getValues('model'))?.maxToken || 4000;
return chatModelList.find((item) => item.model === getValues('model'))?.maxResponse || 4000;
}, [getValues, refresh]);
const LabelStyles: BoxProps = {

View File

@@ -1,5 +1,5 @@
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { AppSchema } from '@/types/mongoSchema';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import React, {
useMemo,
useCallback,
@@ -47,7 +47,7 @@ const ChatTest = (
// 流请求,获取数据
const { responseText, responseData } = await streamFetch({
url: '/api/chat/chatTest',
url: '/api/core/chat/chatTest',
data: {
history,
prompt: chatList[chatList.length - 2].value,

View File

@@ -7,6 +7,7 @@ import Avatar from '@/components/Avatar';
import { useTranslation } from 'react-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 = ({
defaultApps = [],
@@ -26,7 +27,7 @@ const SelectAppModal = ({
const theme = useTheme();
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
/* 加载模型 */
const { myApps, loadMyApps } = useUserStore();
const { myApps, loadMyApps } = useAppStore();
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
const apps = useMemo(

View File

@@ -9,7 +9,7 @@ import {
Switch,
Input
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType } from '@/types/app';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { useForm } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);

View File

@@ -6,7 +6,7 @@ import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
import MyIcon from '@/components/Icon';

View File

@@ -8,7 +8,7 @@ import Container from '../modules/Container';
import { AddIcon } from '@chakra-ui/icons';
import RenderInput from '../render/RenderInput';
import Divider from '../modules/Divider';
import { ContextExtractAgentItemType } from '@/types/app';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import RenderOutput from '../render/RenderOutput';
import MyIcon from '@/components/Icon';
import ExtractFieldModal from '../modules/ExtractFieldModal';

View File

@@ -15,9 +15,9 @@ import {
Switch
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { SystemInputEnum } from '@/constants/app';
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate';
import { onChangeNode } from '../../FlowProvider';
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
@@ -26,18 +26,24 @@ import MyTooltip from '@/components/MyTooltip';
import Container from '../modules/Container';
import NodeCard from '../modules/NodeCard';
import { VariableItemType } from '@/types/app';
import QGSwitch from '@/pages/app/detail/components/QGSwitch';
import TTSSelect from '@/pages/app/detail/components/TTSSelect';
import { splitGuideModule } from '@/global/core/app/modules/utils';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const theme = useTheme();
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<Container className="nodrag" borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<WelcomeText data={data} />
<Box pt={4} pb={2}>
<ChatStartVariable data={data} />
</Box>
<Box pt={3} borderTop={theme.borders.base}>
<TTSGuide data={data} />
</Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
<QuestionGuide data={data} />
</Box>
</Container>
@@ -215,29 +221,43 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
);
return (
<Flex alignItems={'center'}>
<MyIcon name={'questionGuide'} mr={2} w={'16px'} />
<Box></Box>
<MyTooltip label={questionGuideTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Switch
isChecked={questionGuide}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
onChangeNode({
moduleId,
key: SystemInputEnum.questionGuide,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
value
}
});
}}
/>
</Flex>
<QGSwitch
isChecked={questionGuide}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
onChangeNode({
moduleId,
key: SystemInputEnum.questionGuide,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
value
}
});
}}
/>
);
}
function TTSGuide({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId } = data;
const { ttsConfig } = splitGuideModule({ inputs } as ModuleItemType);
return (
<TTSSelect
value={ttsConfig}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.tts,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === SystemInputEnum.tts),
value: e
}
});
}}
/>
);
}

View File

@@ -29,11 +29,11 @@ import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
import { AIChatProps } from '@/types/core/aiChat';
import { chatModelList } from '@/web/common/system/staticData';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@/types/core/dataset';
import { useQuery } from '@tanstack/react-query';
import { LLMModelItemType } from '@/types/model';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
@@ -406,7 +406,7 @@ var MaxTokenRender = React.memo(function MaxTokenRender({
}: RenderProps) {
const model = inputs.find((item) => item.key === 'model')?.value;
const modelData = chatModelList.find((item) => item.model === model);
const maxToken = modelData ? modelData.maxToken : 4000;
const maxToken = modelData ? modelData.maxResponse : 4000;
const markList = [
{ label: '100', value: 100 },
{ label: `${maxToken}`, value: maxToken }
@@ -468,10 +468,10 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
...inputs.find((input) => input.key === 'maxToken'),
markList: [
{ label: '100', value: 100 },
{ label: `${model.maxToken}`, value: model.maxToken }
{ label: `${model.maxResponse}`, value: model.maxResponse }
],
max: model.maxToken,
value: model.maxToken / 2
max: model.maxResponse,
value: model.maxResponse / 2
}
});
}

View File

@@ -26,7 +26,7 @@ import {
delOpenApiById,
putOpenApiKey
} from '@/web/support/openapi/api';
import type { EditApiKeyProps } from '@/global/support/api/openapiReq';
import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@/web/common/hooks/useLoading';
import dayjs from 'dayjs';

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
import { Box, Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
const PermissionIconText = ({
permission,
...props
}: { permission: `${PermissionTypeEnum}` } & FlexProps) => {
const { t } = useTranslation();
return PermissionTypeMap[permission] ? (
<Flex alignItems={'center'} {...props}>
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
<Box ml={'1px'}>{t(PermissionTypeMap[permission]?.label)}</Box>
</Flex>
) : null;
};
export default PermissionIconText;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import MyRadio from '@/components/Radio';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { useTranslation } from 'react-i18next';
const PermissionRadio = ({
value,
onChange
}: {
value: `${PermissionTypeEnum}`;
onChange: (e: `${PermissionTypeEnum}`) => void;
}) => {
const { t } = useTranslation();
return (
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
list={[
{
icon: 'support/permission/privateLight',
title: t('permission.Private'),
desc: t('permission.Private Tip'),
value: PermissionTypeEnum.private
},
{
icon: 'support/permission/publicLight',
title: t('permission.Public'),
desc: t('permission.Public Tip'),
value: PermissionTypeEnum.public
}
]}
value={value}
onChange={(e) => onChange(e as `${PermissionTypeEnum}`)}
/>
);
};
export default PermissionRadio;

View File

@@ -0,0 +1,159 @@
import React, { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgAndUpload } from '@/web/common/file/controller';
import { useToast } from '@/web/common/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest } from '@/web/common/hooks/useRequest';
import MyModal from '@/components/MyModal';
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';
import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api';
import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d';
export type FormDataType = CreateTeamProps & {
id?: string;
};
export const defaultForm = {
name: '',
avatar: '/icon/logo.svg'
};
function EditModal({
defaultData = defaultForm,
onClose,
onSuccess
}: {
defaultData?: FormDataType;
onClose: () => void;
onSuccess: () => void;
}) {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const { register, setValue, getValues, handleSubmit } = useForm<CreateTeamProps>({
defaultValues: defaultData
});
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png,.svg',
multiple: false
});
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file) return;
try {
const src = await compressImgAndUpload({
file,
maxW: 100,
maxH: 100
});
setValue('avatar', src);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, t('common.Select File Failed')),
status: 'warning'
});
}
},
[setValue, t, toast]
);
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (data: CreateTeamProps) => {
return postCreateTeam(data);
},
onSuccess() {
onSuccess();
onClose();
},
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: async (data: FormDataType) => {
if (!data.id) return Promise.resolve('');
return putUpdateTeam({
teamId: data.id,
name: data.name,
avatar: data.avatar
});
},
onSuccess() {
onSuccess();
onClose();
},
successToast: t('common.Update Success'),
errorToast: t('common.Update Failed')
});
return (
<MyModal
isOpen
onClose={onClose}
title={defaultData.id ? t('user.team.Update Team') : t('user.team.Create Team')}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('user.team.Set Name')}
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
autoFocus
bg={'myWhite.600'}
maxLength={20}
placeholder={t('user.team.Team Name')}
{...register('name', {
required: t('common.Please Input Name')
})}
/>
</Flex>
</ModalBody>
<ModalFooter>
{!!defaultData.id ? (
<>
<Box flex={1} />
<Button variant={'base'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={updating} onClick={handleSubmit((data) => onclickUpdate(data))}>
{t('common.Confirm Update')}
</Button>
</>
) : (
<Button
w={'100%'}
isLoading={creating}
onClick={handleSubmit((data) => onclickCreate(data))}
>
{t('common.Confirm Create')}
</Button>
)}
</ModalFooter>
<File onSelect={onSelectFile} />
</MyModal>
);
}
export default React.memo(EditModal);

View File

@@ -0,0 +1,106 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
import TagTextarea from '@/components/common/Textarea/TagTextarea';
import MySelect from '@/components/Select';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useRequest } from '@/web/common/hooks/useRequest';
import { postInviteTeamMember } from '@/web/support/user/team/api';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import type { InviteMemberResponse } from '@fastgpt/global/support/user/team/controller.d';
const InviteModal = ({
teamId,
onClose,
onSuccess
}: {
teamId: string;
onClose: () => void;
onSuccess: () => void;
}) => {
const { t } = useTranslation();
const { ConfirmModal, openConfirm } = useConfirm({
title: t('user.team.Invite Member Result Tip'),
showCancel: false
});
const [inviteUsernames, setInviteUsernames] = useState<string[]>([]);
const inviteTypes = useMemo(
() => [
{
alias: t('user.team.Invite Role Visitor Alias'),
label: t('user.team.Invite Role Visitor Tip'),
value: TeamMemberRoleEnum.visitor
},
{
alias: t('user.team.Invite Role Admin Alias'),
label: t('user.team.Invite Role Admin Tip'),
value: TeamMemberRoleEnum.admin
}
],
[t]
);
const [selectedInviteType, setSelectInviteType] = useState(inviteTypes[0].value);
const { mutate: onInvite, isLoading } = useRequest({
mutationFn: () => {
return postInviteTeamMember({
teamId,
usernames: inviteUsernames,
role: selectedInviteType
});
},
onSuccess(res: InviteMemberResponse) {
onSuccess();
openConfirm(
() => onClose(),
undefined,
t('user.team.Invite Member Success Tip', {
success: res.invite.length,
inValid: res.inValid.join(', '),
inTeam: res.inTeam.join(', ')
})
)();
},
errorToast: t('user.team.Invite Member Failed Tip')
});
return (
<MyModal
isOpen
title={
<>
<Box>{t('user.team.Invite Member')}</Box>
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
{t('user.team.Invite Member Tips')}
</Box>
</>
}
maxW={['90vw', '400px']}
overflow={'unset'}
>
<ModalCloseButton onClick={onClose} />
<ModalBody>
<Box mb={2}>{t('common.Username')}</Box>
<TagTextarea defaultValues={inviteUsernames} onUpdate={setInviteUsernames} />
<Box mt={4}>
<MySelect list={inviteTypes} value={selectedInviteType} onchange={setSelectInviteType} />
</Box>
</ModalBody>
<ModalFooter>
<Button
w={'100%'}
h={'34px'}
isDisabled={inviteUsernames.length === 0}
isLoading={isLoading}
onClick={onInvite}
>
{t('user.team.Confirm Invite')}
</Button>
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default InviteModal;

View File

@@ -0,0 +1,436 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import {
getTeamList,
getTeamMembers,
putSwitchTeam,
putUpdateMember,
delRemoveMember,
delLeaveTeam
} from '@/web/support/user/team/api';
import {
Box,
Button,
Flex,
IconButton,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
useTheme,
useDisclosure,
MenuButton
} from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import { useUserStore } from '@/web/support/user/useUserStore';
import {
TeamMemberRoleEnum,
TeamMemberRoleMap,
TeamMemberStatusEnum,
TeamMemberStatusMap
} from '@fastgpt/global/support/user/team/constant';
import dynamic from 'next/dynamic';
import { useRequest } from '@/web/common/hooks/useRequest';
import { setToken } from '@/web/support/user/auth';
import { useLoading } from '@/web/common/hooks/useLoading';
import { FormDataType, defaultForm } from './EditModal';
import MyMenu from '@/components/MyMenu';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useToast } from '@/web/common/hooks/useToast';
const EditModal = dynamic(() => import('./EditModal'));
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const theme = useTheme();
const { t } = useTranslation();
const { Loading } = useLoading();
const { toast } = useToast();
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
content: t('user.team.member.Confirm Leave')
});
const { userInfo, initUserInfo } = useUserStore();
const [editTeamData, setEditTeamData] = useState<FormDataType>();
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
const {
data: myTeams = [],
isLoading: isLoadingTeams,
refetch: refetchTeam
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
const defaultTeam = useMemo(
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
[myTeams]
);
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
mutationFn: async (teamId: string) => {
const token = await putSwitchTeam(teamId);
setToken(token);
return initUserInfo();
},
errorToast: t('user.team.Switch Team Failed')
});
// member action
const { data: members = [], refetch: refetchMembers } = useQuery(
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
}
);
const { mutate: onUpdateMember, isLoading: isLoadingUpdateMember } = useRequest({
mutationFn: putUpdateMember,
onSuccess() {
refetchMembers();
}
});
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
mutationFn: delRemoveMember,
onSuccess() {
refetchMembers();
}
});
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
mutationFn: async (teamId?: string) => {
if (!teamId) return;
// change to personal team
await onSwitchTeam(defaultTeam.teamId);
return delLeaveTeam(teamId);
},
onSuccess() {
refetchTeam();
},
errorToast: t('user.team.Leave Team Failed')
});
return !!userInfo?.team ? (
<>
<MyModal
isOpen
onClose={onClose}
maxW={['90vw', '1000px']}
w={'100%'}
h={'550px'}
isCentered
bg={'myWhite.600'}
overflow={'hidden'}
>
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
{/* teams */}
<Flex
flexDirection={'column'}
w={['auto', '270px']}
h={['auto', '100%']}
pt={3}
px={5}
mb={[2, 0]}
>
<Flex
alignItems={'center'}
py={2}
h={'40px'}
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
>
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('common.Team')}
</Box>
{myTeams.length < 1 && (
<IconButton
variant={'ghost'}
border={'none'}
icon={
<MyIcon
name={'addCircle'}
w={['16px', '18px']}
color={'myBlue.600'}
cursor={'pointer'}
/>
}
aria-label={''}
onClick={() => setEditTeamData(defaultForm)}
/>
)}
</Flex>
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
{myTeams.map((team) => (
<Flex
key={team.teamId}
alignItems={'center'}
mt={3}
borderRadius={'md'}
p={3}
cursor={'default'}
gap={3}
{...(userInfo?.team?.teamId === team.teamId
? {
bg: 'myBlue.300'
}
: {
_hover: {
bg: 'myGray.100'
}
})}
>
<Avatar src={team.avatar} w={['18px', '22px']} />
<Box
flex={'1 0 0'}
w={0}
{...(team.role === TeamMemberRoleEnum.owner
? {
fontWeight: 'bold'
}
: {})}
>
{team.teamName}
</Box>
{userInfo?.team?.teamId === team.teamId ? (
<MyIcon name={'common/tickFill'} w={'16px'} color={'myBlue.600'} />
) : (
<Button size={'xs'} variant={'base'} onClick={() => onSwitchTeam(team.teamId)}>
{t('user.team.Check Team')}
</Button>
)}
</Flex>
))}
</Box>
</Flex>
{/* team card */}
<Flex
flexDirection={'column'}
flex={'1'}
h={['auto', '100%']}
bg={'white'}
minH={['50vh', 'auto']}
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
>
<Flex
alignItems={'center'}
px={5}
py={4}
borderBottom={'1.5px solid'}
borderBottomColor={'myGray.100'}
mb={3}
>
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
{userInfo.team.teamName}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<MyIcon
name="edit"
w={'14px'}
ml={2}
cursor={'pointer'}
_hover={{
color: 'myBlue.600'
}}
onClick={() => {
if (!userInfo?.team) return;
setEditTeamData({
id: userInfo.team.teamId,
name: userInfo.team.teamName,
avatar: userInfo.team.avatar
});
}}
/>
)}
</Flex>
<Flex px={5} alignItems={'center'}>
<MyIcon name="support/team/memberLight" w={'14px'} />
<Box ml={1}>{t('user.team.Member')}</Box>
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
{members.length}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<Button
variant={'base'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'myBlue.600'} />}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
});
} else {
onOpenInvite();
}
}}
>
{t('user.team.Invite Member')}
</Button>
)}
<Box flex={1} />
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
<Button
variant={'base'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'loginoutLight'} w={'14px'} color={'myBlue.600'} />}
onClick={() => {
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
}}
>
{t('user.team.Leave Team')}
</Button>
)}
</Flex>
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
<TableContainer overflow={'unset'}>
<Table overflow={'unset'}>
<Thead bg={'myWhite.400'}>
<Tr>
<Th>{t('common.Username')}</Th>
<Th>{t('user.team.Role')}</Th>
<Th>{t('common.Status')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{members.map((item) => (
<Tr key={item.userId} overflow={'unset'}>
<Td display={'flex'} alignItems={'center'}>
<Avatar src={item.avatar} w={['18px', '22px']} />
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
{item.memberUsername}
</Box>
</Td>
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
<Td color={TeamMemberStatusMap[item.status].color}>
{t(TeamMemberStatusMap[item.status]?.label || '')}
</Td>
<Td>
{userInfo?.team?.role === TeamMemberRoleEnum.owner &&
item.role !== TeamMemberRoleEnum.owner && (
<MyMenu
width={20}
Button={
<MenuButton
_hover={{
bg: 'myWhite.600'
}}
px={2}
py={1}
lineHeight={1}
>
<MyIcon
name={'edit'}
cursor={'pointer'}
w="14px"
_hover={{ color: 'myBlue.600' }}
/>
</MenuButton>
}
menuList={[
{
isActive: item.role === TeamMemberRoleEnum.visitor,
child: t('user.team.Invite Role Visitor Tip'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
role: TeamMemberRoleEnum.visitor
});
}
},
{
isActive: item.role === TeamMemberRoleEnum.admin,
child: t('user.team.Invite Role Admin Tip'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
role: TeamMemberRoleEnum.admin
});
}
},
...(item.status === TeamMemberStatusEnum.reject
? [
{
child: t('user.team.Reinvite'),
onClick: () => {
onUpdateMember({
teamId: item.teamId,
memberId: item.tmbId,
status: TeamMemberStatusEnum.waiting
});
}
}
]
: []),
{
child: t('user.team.Remove Member Tip'),
onClick: () =>
openRemoveMember(
() =>
onRemoveMember({
teamId: item.teamId,
memberId: item.tmbId
}),
undefined,
t('user.team.Remove Member Confirm Tip', {
username: item.memberUsername
})
)()
}
]}
/>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Flex>
<Loading
loading={
isSwitchTeam ||
isLoadingTeams ||
isLoadingUpdateMember ||
isLoadingRemoveMember ||
isLoadingLeaveTeam
}
fixed={false}
/>
</Box>
</MyModal>
{!!editTeamData && (
<EditModal
defaultData={editTeamData}
onClose={() => setEditTeamData(undefined)}
onSuccess={() => {
refetchTeam();
initUserInfo();
}}
/>
)}
{isOpenInvite && userInfo?.team?.teamId && (
<InviteModal
teamId={userInfo.team.teamId}
onClose={onCloseInvite}
onSuccess={refetchMembers}
/>
)}
<ConfirmRemoveMemberModal />
<ConfirmLeaveTeamModal />
</>
) : null;
};
export default React.memo(TeamManageModal);

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'react-i18next';
import MyTooltip from '@/components/MyTooltip';
import dynamic from 'next/dynamic';
import { feConfigs } from '@/web/common/system/staticData';
import { useToast } from '@/web/common/hooks/useToast';
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
const TeamMenu = () => {
const theme = useTheme();
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { toast } = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Button
variant={'base'}
userSelect={'none'}
w={'100%'}
display={'block'}
h={'34px'}
px={3}
css={{
'& span': {
display: 'block'
}
}}
transform={'none !important'}
onClick={() => {
if (feConfigs.isPlus) {
onOpen();
} else {
toast({
status: 'warning',
title: t('common.Business edition features')
});
}
}}
>
<MyTooltip label={t('user.team.Select Team')}>
<Flex w={'100%'} alignItems={'center'}>
{userInfo?.team ? (
<>
<Image src={userInfo.team.avatar} alt={''} w={'16px'} />
<Box ml={2}>{userInfo.team.teamName}</Box>
</>
) : (
<>
<Box w={'8px'} h={'8px'} mr={3} borderRadius={'50%'} bg={'#67c13b'} />
{t('user.team.Personal Team')}
</>
)}
</Flex>
</MyTooltip>
{isOpen && <TeamManageModal onClose={onClose} />}
</Button>
);
};
export default TeamMenu;

View File

@@ -0,0 +1,133 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import MyModal from '@/components/MyModal';
import {
Button,
ModalFooter,
useDisclosure,
ModalBody,
Flex,
Box,
useTheme
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getTeamList, updateInviteResult } from '@/web/support/user/team/api';
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
import Avatar from '@/components/Avatar';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useToast } from '@/web/common/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { feConfigs } from '@/web/common/system/staticData';
const UpdateInviteModal = () => {
const { t } = useTranslation();
const theme = useTheme();
const { toast } = useToast();
const { ConfirmModal, openConfirm } = useConfirm({});
const { data: inviteList = [], refetch } = useQuery(['getInviteList'], () =>
feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []
);
const { mutate: onAccept, isLoading: isLoadingAccept } = useRequest({
mutationFn: updateInviteResult,
onSuccess() {
toast({
status: 'success',
title: t('user.team.invite.Accepted')
});
refetch();
}
});
const { mutate: onReject, isLoading: isLoadingReject } = useRequest({
mutationFn: updateInviteResult,
onSuccess() {
toast({
status: 'success',
title: t('user.team.invite.Reject')
});
refetch();
}
});
return (
<MyModal
isOpen={inviteList.length > 0}
title={
<>
<Box>{t('user.team.Processing invitations')}</Box>
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
{t('user.team.Processing invitations Tips', { amount: inviteList.length })}
</Box>
</>
}
maxW={['90vw', '500px']}
>
<ModalBody>
{inviteList.map((item) => (
<Flex
key={item.teamId}
alignItems={'center'}
border={theme.borders.base}
borderRadius={'md'}
px={3}
py={2}
_notFirst={{
mt: 3
}}
>
<Avatar src={item.avatar} w={['16px', '23px']} />
<Box mx={2}>{item.teamName}</Box>
<Box flex={1} />
<Button
size="sm"
variant={'solid'}
colorScheme="green"
isLoading={isLoadingAccept}
onClick={() => {
openConfirm(
() =>
onAccept({
tmbId: item.tmbId,
status: TeamMemberStatusEnum.active
}),
undefined,
t('user.team.invite.Accept Confirm')
)();
}}
>
{t('user.team.invite.accept')}
</Button>
<Button
size="sm"
ml={2}
variant={'solid'}
colorScheme="red"
isLoading={isLoadingReject}
onClick={() => {
openConfirm(
() =>
onReject({
tmbId: item.tmbId,
status: TeamMemberStatusEnum.reject
}),
undefined,
t('user.team.invite.Reject Confirm')
)();
}}
>
{t('user.team.invite.reject')}
</Button>
</Flex>
))}
</ModalBody>
<ModalFooter justifyContent={'center'}>
<Box>{t('user.team.invite.Deal Width Footer Tip')}</Box>
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default React.memo(UpdateInviteModal);

View File

@@ -1,4 +1,32 @@
/* app */
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
export const defaultApp: AppDetailType = {
_id: '',
userId: 'userId',
name: '模型加载中',
type: 'basic',
avatar: '/icon/logo.svg',
intro: '',
updateTime: Date.now(),
modules: [],
teamId: '',
tmbId: '',
permission: 'private',
isOwner: false,
canWrite: false
};
export const defaultOutLinkForm: OutLinkEditType = {
name: '',
responseDetail: false,
limit: {
QPM: 100,
credit: -1
}
};
/* module special */
export enum SystemInputEnum {
'welcomeText' = 'welcomeText',
'variables' = 'variables',
@@ -6,6 +34,7 @@ export enum SystemInputEnum {
'history' = 'history',
'userChatInput' = 'userChatInput',
'questionGuide' = 'questionGuide',
'tts' = 'tts',
isResponseAnswerText = 'isResponseAnswerText'
}
export enum SystemOutputEnum {
@@ -17,7 +46,8 @@ export enum VariableInputEnum {
select = 'select'
}
export enum AppTypeEnum {
basic = 'basic',
advanced = 'advanced'
export enum TTSTypeEnum {
none = 'none',
web = 'web',
model = 'model'
}

View File

@@ -1,57 +0,0 @@
import dayjs from 'dayjs';
export enum sseResponseEventEnum {
error = 'error',
answer = 'answer',
moduleStatus = 'moduleStatus',
appStreamResponse = 'appStreamResponse' // sse response request
}
export enum ChatRoleEnum {
System = 'System',
Human = 'Human',
AI = 'AI'
}
export enum TaskResponseKeyEnum {
'answerText' = 'answerText', // answer module text key
'responseData' = 'responseData',
'history' = 'history'
}
export const ChatRoleMap = {
[ChatRoleEnum.System]: {
name: '系统提示词'
},
[ChatRoleEnum.Human]: {
name: '用户'
},
[ChatRoleEnum.AI]: {
name: 'AI'
}
};
export enum ChatSourceEnum {
test = 'test',
online = 'online',
share = 'share',
api = 'api'
}
export const ChatSourceMap = {
[ChatSourceEnum.test]: {
name: 'chat.logs.test'
},
[ChatSourceEnum.online]: {
name: 'chat.logs.online'
},
[ChatSourceEnum.share]: {
name: 'chat.logs.share'
},
[ChatSourceEnum.api]: {
name: 'chat.logs.api'
}
};
export const HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`;

View File

@@ -1,11 +1,19 @@
import type { DatasetItemType } from '@/types/core/dataset';
export const defaultKbDetail: DatasetItemType = {
export const defaultDatasetDetail: DatasetItemType = {
_id: '',
parentId: '',
userId: '',
teamId: '',
tmbId: '',
updateTime: new Date(),
type: 'dataset',
avatar: '/icon/logo.svg',
name: '',
tags: '',
permission: 'private',
isOwner: false,
canWrite: false,
vectorModel: {
model: 'text-embedding-ada-002',
name: 'Embedding-2',

View File

@@ -1,5 +1,6 @@
import { AppTypeEnum, SystemInputEnum } from '../app';
import { TaskResponseKeyEnum } from '../chat';
import { SystemInputEnum } from '../app';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import {
FlowNodeTypeEnum,
FlowNodeInputTypeEnum,
@@ -27,7 +28,6 @@ export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
export const variableTip =
'可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等';
export const questionGuideTip = `对话结束后,会为生成 3 个引导性问题。`;
export const VariableModule: FlowModuleTemplateType = {
id: FlowNodeTypeEnum.variable,
@@ -69,6 +69,11 @@ export const UserGuideModule: FlowModuleTemplateType = {
key: SystemInputEnum.questionGuide,
type: FlowNodeInputTypeEnum.switch,
label: '问题引导'
},
{
key: SystemInputEnum.tts,
type: FlowNodeInputTypeEnum.hidden,
label: '语音播报'
}
],
outputs: []
@@ -162,15 +167,15 @@ export const ChatModule: FlowModuleTemplateType = {
key: 'maxToken',
type: FlowNodeInputTypeEnum.hidden,
label: '回复上限',
value: chatModelList?.[0] ? chatModelList[0].maxToken / 2 : 2000,
value: chatModelList?.[0] ? chatModelList[0].maxResponse / 2 : 2000,
min: 100,
max: chatModelList?.[0]?.maxToken || 4000,
max: chatModelList?.[0]?.maxResponse || 4000,
step: 50,
markList: [
{ label: '100', value: 100 },
{
label: `${chatModelList?.[0]?.maxToken || 4000}`,
value: chatModelList?.[0]?.maxToken || 4000
label: `${chatModelList?.[0]?.maxResponse || 4000}`,
value: chatModelList?.[0]?.maxResponse || 4000
}
]
},

View File

@@ -1,120 +0,0 @@
import type { AppSchema } from '@/types/mongoSchema';
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import type {
LLMModelItemType,
ChatModelItemType,
FunctionModelItemType,
VectorModelItemType
} from '@/types/model';
export const defaultChatModels: ChatModelItemType[] = [
{
model: 'gpt-3.5-turbo',
name: 'GPT35-4k',
price: 0,
maxToken: 4000,
quoteMaxToken: 2000,
maxTemperature: 1.2,
censor: false,
defaultSystemChatPrompt: ''
},
{
model: 'gpt-3.5-turbo-16k',
name: 'GPT35-16k',
maxToken: 16000,
price: 0,
quoteMaxToken: 8000,
maxTemperature: 1.2,
censor: false,
defaultSystemChatPrompt: ''
},
{
model: 'gpt-4',
name: 'GPT4-8k',
maxToken: 8000,
price: 0,
quoteMaxToken: 4000,
maxTemperature: 1.2,
censor: false,
defaultSystemChatPrompt: ''
}
];
export const defaultQAModels: LLMModelItemType[] = [
{
model: 'gpt-3.5-turbo-16k',
name: 'GPT35-16k',
maxToken: 16000,
price: 0
}
];
export const defaultCQModels: FunctionModelItemType[] = [
{
model: 'gpt-3.5-turbo-16k',
name: 'GPT35-16k',
maxToken: 16000,
price: 0,
functionCall: true,
functionPrompt: ''
},
{
model: 'gpt-4',
name: 'GPT4-8k',
maxToken: 8000,
price: 0,
functionCall: true,
functionPrompt: ''
}
];
export const defaultExtractModels: FunctionModelItemType[] = [
{
model: 'gpt-3.5-turbo-16k',
name: 'GPT35-16k',
maxToken: 16000,
price: 0,
functionCall: true,
functionPrompt: ''
}
];
export const defaultQGModels: LLMModelItemType[] = [
{
model: 'gpt-3.5-turbo',
name: 'GPT35-4K',
maxToken: 4000,
price: 0
}
];
export const defaultVectorModels: VectorModelItemType[] = [
{
model: 'text-embedding-ada-002',
name: 'Embedding-2',
price: 0,
defaultToken: 500,
maxToken: 3000
}
];
export const defaultApp: AppSchema = {
_id: '',
userId: 'userId',
name: '模型加载中',
type: 'basic',
avatar: '/icon/logo.svg',
intro: '',
updateTime: Date.now(),
share: {
isShare: false,
isShareDetail: false,
collection: 0
},
modules: []
};
export const defaultOutLinkForm: OutLinkEditType = {
name: '',
responseDetail: false,
limit: {
QPM: 100,
credit: -1
}
};

View File

@@ -1 +0,0 @@
export const PgDatasetTableName = 'modeldata';

View File

@@ -1,26 +1,9 @@
export enum OAuthEnum {
github = 'github',
google = 'google'
}
export enum BillSourceEnum {
fastgpt = 'fastgpt',
api = 'api',
shareLink = 'shareLink',
training = 'training'
}
export enum PageTypeEnum {
login = 'login',
register = 'register',
forgetPassword = 'forgetPassword'
}
export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
[BillSourceEnum.fastgpt]: '在线使用',
[BillSourceEnum.api]: 'Api',
[BillSourceEnum.shareLink]: '免登录链接',
[BillSourceEnum.training]: '数据训练'
};
export enum PromotionEnum {
register = 'register',
pay = 'pay'

View File

@@ -3,7 +3,8 @@ import type {
FunctionModelItemType,
LLMModelItemType,
VectorModelItemType
} from '@/types/model';
} from '@fastgpt/global/core/ai/model.d';
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
export type InitDateResponse = {

View File

@@ -1,5 +1,5 @@
/* Only the token of gpt-3.5-turbo is used */
import { ChatItemType } from '@/types/chat';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Tiktoken } from 'js-tiktoken/lite';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';

View File

@@ -0,0 +1,6 @@
import { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
export type CreateQuestionGuideParams = {
messages: ChatMessageItemType[];
shareId?: string;
};

View File

@@ -1,6 +0,0 @@
import { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d';
export type CreateQuestionGuideParams = {
messages: ChatCompletionRequestMessage[];
shareId?: string;
};

View File

@@ -1,6 +0,0 @@
import { AppListItemType } from '@/types/app';
export type AppListResponse = {
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
};

View File

@@ -1,5 +0,0 @@
import { MarkDataType } from '../dataset/type';
export type AdminUpdateFeedbackParams = MarkDataType & {
chatItemId: string;
};

View File

@@ -1,20 +0,0 @@
import type { AppSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat';
import { VariableItemType } from '@/types/app';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
export type InitChatResponse = {
chatId: string;
appId: string;
app: {
userGuideModule?: ModuleItemType;
chatModels?: string[];
name: string;
avatar: string;
intro: string;
canUse?: boolean;
};
title: string;
variables: Record<string, any>;
history: ChatItemType[];
};

View File

@@ -4,6 +4,7 @@ import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import type { SearchTestItemType } from '@/types/core/dataset';
import { DatasetChunkItemType, UploadChunkItemType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
/* ===== dataset ===== */
export type DatasetUpdateParams = {
@@ -12,11 +13,12 @@ export type DatasetUpdateParams = {
tags?: string;
name?: string;
avatar?: string;
permission?: `${PermissionTypeEnum}`;
};
export type CreateDatasetParams = {
parentId?: string;
name: string;
tags: string[];
tags: string;
avatar: string;
vectorModel?: string;
type: `${DatasetTypeEnum}`;
@@ -25,6 +27,7 @@ export type CreateDatasetParams = {
export type SearchTestProps = {
datasetId: string;
text: string;
limit?: number;
};
/* ======= collections =========== */
@@ -53,7 +56,6 @@ export type UpdateDatasetCollectionParams = {
/* ==== data ===== */
export type SetOneDatasetDataProps = {
id?: string;
datasetId: string;
collectionId: string;
q?: string; // embedding content
a?: string; // bonus content

View File

@@ -1,7 +1,6 @@
import type { RequestPaging } from '@/types';
import { TrainingModeEnum } from '@/constants/plugin';
import type { SearchTestItemType } from '@/types/core/dataset';
import { DatasetDataItemType } from '@/types/core/dataset/data';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
/* ===== dataset ===== */

View File

@@ -1,6 +1,6 @@
import { SystemInputEnum } from '@/constants/app';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { VariableItemType } from '@/types/app';
import { AppTTSConfigType, VariableItemType } from '@/types/app';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
export const getGuideModule = (modules: ModuleItemType[]) =>
@@ -17,9 +17,14 @@ export const splitGuideModule = (guideModules?: ModuleItemType) => {
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.questionGuide)?.value ||
false;
const ttsConfig: AppTTSConfigType = guideModules?.inputs?.find(
(item) => item.key === SystemInputEnum.tts
)?.value || { type: 'web' };
return {
welcomeText,
variableModules,
questionGuide
questionGuide,
ttsConfig
};
};

View File

@@ -0,0 +1,7 @@
import type { AppTTSConfigType } from '@/types/app';
export type GetChatSpeechProps = {
chatItemId?: string;
ttsConfig: AppTTSConfigType;
input: string;
};

View File

@@ -7,12 +7,14 @@ import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d
export type DatasetCollectionsListItemType = {
_id: string;
parentId?: string;
tmbId: string;
name: string;
type: DatasetCollectionSchemaType['type'];
updateTime: Date;
dataAmount?: number;
trainingAmount: number;
metadata: DatasetCollectionSchemaType['metadata'];
canWrite: boolean;
};
/* ================= data ===================== */

View File

@@ -1,7 +0,0 @@
export type MarkDataType = {
dataId: string;
datasetId: string;
collectionId: string;
q: string;
a?: string;
};

View File

@@ -1,6 +0,0 @@
import type { InitChatResponse } from '@/global/core/api/chatRes.d';
export type InitShareChatResponse = {
userAvatar: string;
app: InitChatResponse['app'];
};

View File

@@ -1,4 +1,4 @@
import type { UserType } from '@/types/user';
import type { UserTypee } from '@fastgpt/global/support/user/type.d';
import type { PromotionRecordSchema } from '@fastgpt/global/support/activity/type.d';
export interface ResLogin {
user: UserType;

View File

@@ -11,14 +11,14 @@ import {
Td,
TableContainer
} from '@chakra-ui/react';
import { UserBillType } from '@/types/user';
import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d';
import dayjs from 'dayjs';
import { BillSourceMap } from '@/constants/user';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void }) => {
const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => {
const { t } = useTranslation();
const filterBillList = useMemo(
() => bill.list.filter((item) => item && item.moduleName),
@@ -28,6 +28,10 @@ const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void
return (
<MyModal isOpen={true} onClose={onClose} title={t('user.Bill Detail')}>
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box>{bill.username}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box>{bill.id}</Box>
@@ -65,7 +69,7 @@ const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void
<Tbody>
{filterBillList.map((item, i) => (
<Tr key={i}>
<Td>{item.moduleName}</Td>
<Td>{t(item.moduleName)}</Td>
<Td>{item.model}</Td>
<Td>{item.tokenLen}</Td>
<Td>{formatPrice(item.amount)}</Td>

View File

@@ -11,9 +11,9 @@ import {
Box,
Button
} from '@chakra-ui/react';
import { BillSourceMap } from '@/constants/user';
import { getUserBills } from '@/web/common/bill/api';
import type { UserBillType } from '@/types/user';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { getUserBills } from '@/web/support/wallet/bill/api';
import type { BillItemType } from '@fastgpt/global/support/wallet/bill/type';
import { usePagination } from '@/web/common/hooks/usePagination';
import { useLoading } from '@/web/common/hooks/useLoading';
import dayjs from 'dayjs';
@@ -33,14 +33,14 @@ const BillTable = () => {
to: new Date()
});
const { isPc } = useSystemStore();
const [billDetail, setBillDetail] = useState<UserBillType>();
const [billDetail, setBillDetail] = useState<BillItemType>();
const {
data: bills,
isLoading,
Pagination,
getData
} = usePagination<UserBillType>({
} = usePagination<BillItemType>({
api: getUserBills,
pageSize: isPc ? 20 : 10,
params: {
@@ -55,6 +55,7 @@ const BillTable = () => {
<Table>
<Thead>
<Tr>
<Th>{t('wallet.bill.bill username')}</Th>
<Th>{t('user.Time')}</Th>
<Th>{t('user.Source')}</Th>
<Th>{t('user.Application Name')}</Th>
@@ -65,6 +66,7 @@ const BillTable = () => {
<Tbody fontSize={'sm'}>
{bills.map((item) => (
<Tr key={item.id}>
<Td>{item.username}</Td>
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>{BillSourceMap[item.source]}</Td>
<Td>{t(item.appName) || '-'}</Td>

View File

@@ -1,26 +1,14 @@
import React, { useCallback, useRef, useState } from 'react';
import {
Box,
Flex,
Button,
useDisclosure,
useTheme,
Divider,
Select,
Menu,
MenuButton,
MenuList,
MenuItem
} from '@chakra-ui/react';
import { Box, Flex, Button, useDisclosure, useTheme, Divider, Select } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { useToast } from '@/web/common/hooks/useToast';
import { useUserStore } from '@/web/support/user/useUserStore';
import { UserType } from '@/types/user';
import type { UserType } from '@fastgpt/global/support/user/type.d';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImg } from '@/web/common/file/utils';
import { compressImgAndUpload } from '@/web/common/file/controller';
import { feConfigs, systemVersion } from '@/web/common/system/staticData';
import { useTranslation } from 'next-i18next';
import { timezoneList } from '@fastgpt/global/common/time/timezone';
@@ -30,9 +18,10 @@ import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { getLangStore, LangEnum, langMap, setLangStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import MyMenu from '@/components/MyMenu';
import MySelect from '@/components/Select';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'), {
loading: () => <Loading fixed={false} />,
ssr: false
@@ -97,7 +86,7 @@ const UserInfo = () => {
const file = e[0];
if (!file || !userInfo) return;
try {
const src = await compressImg({
const src = await compressImgAndUpload({
file,
maxW: 100,
maxH: 100
@@ -168,6 +157,12 @@ const UserInfo = () => {
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Team')}:&nbsp;</Box>
<Box flex={1}>
<TeamMenu />
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
@@ -212,11 +207,13 @@ const UserInfo = () => {
</Flex>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>{t('user.Balance')}:&nbsp;</Box>
<Box flex={1}>
<strong>{userInfo?.balance.toFixed(3)}</strong>
<Box flex={'0 0 80px'} fontSize={'md'}>
{t('user.team.Balance')}:&nbsp;
</Box>
{feConfigs?.show_pay && (
<Box flex={1}>
<strong>{formatPrice(userInfo?.team?.balance).toFixed(3)}</strong>
</Box>
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { getInforms, readInform } from '@/web/support/user/api';
import { getInforms, readInform } from '@/web/support/user/inform/api';
import { usePagination } from '@/web/common/hooks/usePagination';
import { useLoading } from '@/web/common/hooks/useLoading';
import type { UserInformSchema } from '@fastgpt/global/support/user/type';
import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type';
import { formatTimeToChatTime } from '@/utils/tools';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@/components/Icon';
@@ -67,15 +67,16 @@ const BillTable = () => {
)}
</Box>
))}
{!isLoading && informs.length === 0 && (
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
)}
</Box>
{!isLoading && informs.length === 0 && (
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'} pt={'-48px'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
)}
{total > pageSize && (
<Flex w={'100%'} mt={4} px={[3, 8]} justifyContent={'flex-end'}>
<Pagination />

View File

@@ -4,7 +4,7 @@ import MyModal from '@/components/MyModal';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';
import { UserType } from '@/types/user';
import type { UserType } from '@fastgpt/global/support/user/type.d';
const OpenAIAccountModal = ({
defaultData,

View File

@@ -1,6 +1,6 @@
import React, { useState, useCallback } from 'react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
import { getPayCode, checkPayResult } from '@/web/common/bill/api';
import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api';
import { useToast } from '@/web/common/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';

View File

@@ -11,11 +11,11 @@ import {
Flex,
Box
} from '@chakra-ui/react';
import { getPayOrders, checkPayResult } from '@/web/common/bill/api';
import type { PaySchema } from '@fastgpt/global/support/wallet/type.d';
import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import dayjs from 'dayjs';
import { useQuery } from '@tanstack/react-query';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { useToast } from '@/web/common/hooks/useToast';
import { useLoading } from '@/web/common/hooks/useLoading';
import MyIcon from '@/components/Icon';

View File

@@ -16,7 +16,7 @@ import {
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { getPromotionInitData, getPromotionRecords } from '@/web/support/user/api';
import { getPromotionInitData, getPromotionRecords } from '@/web/support/activity/promotion/api';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useLoading } from '@/web/common/hooks/useLoading';
@@ -42,7 +42,8 @@ const Promotion = () => {
pageSize,
Pagination
} = usePagination<PromotionRecordType>({
api: getPromotionRecords
api: getPromotionRecords,
pageSize: 20
});
const { data: { invitedAmount = 0, earningsAmount = 0 } = {} } = useQuery(

View File

@@ -32,7 +32,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
onSuccess() {
onClose();
},
successToast: t('user.Update password succseful'),
successToast: t('user.Update password successful'),
errorToast: t('user.Update password failed')
});

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
@@ -33,52 +33,69 @@ enum TabEnum {
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation();
const tabList = [
{
icon: 'meLight',
label: t('user.Personal Information'),
id: TabEnum.info
},
const { userInfo, setUserInfo } = useUserStore();
{
icon: 'billRecordLight',
label: t('user.Usage Record'),
id: TabEnum.bill
},
...(feConfigs?.show_promotion
? [
{
icon: 'promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
}
]
: []),
...(feConfigs?.show_pay
? [
{
icon: 'payRecordLight',
label: t('user.Recharge Record'),
id: TabEnum.pay
}
]
: []),
{
icon: 'apikey',
label: t('user.apikey.key'),
id: TabEnum.apikey
},
{
icon: 'informLight',
label: t('user.Notice'),
id: TabEnum.inform
},
{
icon: 'loginoutLight',
label: t('user.Sign Out'),
id: TabEnum.loginout
}
];
const tabList = useMemo(
() => [
{
icon: 'meLight',
label: t('user.Personal Information'),
id: TabEnum.info
},
...(feConfigs?.isPlus
? [
{
icon: 'billRecordLight',
label: t('user.Usage Record'),
id: TabEnum.bill
}
]
: []),
...(feConfigs?.show_promotion
? [
{
icon: 'promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
}
]
: []),
...(feConfigs?.show_pay && userInfo?.team.canWrite
? [
{
icon: 'payRecordLight',
label: t('user.Recharge Record'),
id: TabEnum.pay
}
]
: []),
...(userInfo?.team.canWrite
? [
{
icon: 'apikey',
label: t('user.apikey.key'),
id: TabEnum.apikey
}
]
: []),
...(feConfigs.isPlus
? [
{
icon: 'informLight',
label: t('user.Notice'),
id: TabEnum.inform
}
]
: []),
{
icon: 'loginoutLight',
label: t('user.Sign Out'),
id: TabEnum.loginout
}
],
[t, userInfo?.team.canWrite]
);
const { openConfirm, ConfirmModal } = useConfirm({
content: '确认退出登录?'
@@ -87,7 +104,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const router = useRouter();
const theme = useTheme();
const { isPc } = useSystemStore();
const { setUserInfo } = useUserStore();
const setCurrentTab = useCallback(
(tab: string) => {

View File

@@ -1,42 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@fastgpt/service/support/user/auth';
import { connectToDatabase } from '@/service/mongo';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
await MongoDataset.updateMany(
{
type: { $exists: false }
},
{
$set: {
type: DatasetTypeEnum.dataset,
parentId: null
}
}
);
const response = await PgClient.update(PgDatasetTableName, {
where: [['file_id', 'undefined']],
values: [{ key: 'file_id', value: '' }]
});
jsonRes(res, {
data: response.rowCount
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -1,35 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@fastgpt/service/support/user/auth';
import { connectToDatabase } from '@/service/mongo';
import mongoose from '@fastgpt/service/common/mongo';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
const data = await mongoose.connection.db
.collection('dataset.files')
.updateMany({}, { $set: { 'metadata.datasetUsed': true } });
// update pg data
const pg = await PgClient.query(`UPDATE ${PgDatasetTableName}
SET file_id = ''
WHERE (file_id = 'undefined' OR LENGTH(file_id) < 20) AND file_id != '';`);
jsonRes(res, {
data: {
data,
pg
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -1,27 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@fastgpt/service/support/user/auth';
import { connectToDatabase, Bill } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
try {
await Bill.collection.dropIndex('time_1');
} catch (error) {}
try {
await Bill.collection.createIndex({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 });
} catch (error) {}
jsonRes(res, {
data: {}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -1,104 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@fastgpt/service/support/user/auth';
import { connectToDatabase, App } from '@/service/mongo';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { SystemInputEnum } from '@/constants/app';
const limit = 300;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
const totalApps = await App.countDocuments();
// init app
await App.updateMany({}, { $set: { inited: false } });
for (let i = 0; i < totalApps; i += limit) {
await initVariable();
console.log(i + limit);
}
jsonRes(res, {
data: {
total: totalApps
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function initVariable(): Promise<any> {
try {
const apps = await App.find({ inited: false }).limit(limit);
await Promise.all(
apps.map(async (app) => {
const jsonAPP = app.toObject();
// @ts-ignore
app.inited = true;
const modules = jsonAPP.modules;
// 找到 variable
const variable = modules.find((item) => item.flowType === FlowNodeTypeEnum.variable);
if (!variable) return await app.save();
// 找到 guide 模块
const userGuideModule = modules.find(
(item) => item.flowType === FlowNodeTypeEnum.userGuide
);
if (userGuideModule) {
userGuideModule.inputs = [
userGuideModule.inputs[0],
{
key: SystemInputEnum.variables,
type: FlowNodeInputTypeEnum.systemInput,
label: '对话框变量',
value: variable.inputs[0]?.value
}
];
} else {
modules.unshift({
moduleId: 'userGuide',
flowType: FlowNodeTypeEnum.userGuide,
name: '用户引导',
position: {
x: 447.98520778293346,
y: 721.4016845336229
},
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowNodeInputTypeEnum.input,
label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowNodeInputTypeEnum.systemInput,
label: '对话框变量',
value: variable.inputs[0]?.value
}
],
outputs: []
});
}
jsonAPP.modules = jsonAPP.modules.filter(
(item) => item.flowType !== FlowNodeTypeEnum.variable
);
app.modules = JSON.parse(JSON.stringify(jsonAPP.modules));
await app.save();
})
);
} catch (error) {
return initVariable();
}
}

View File

@@ -1,93 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@fastgpt/service/support/user/auth';
import { connectToDatabase } from '@/service/mongo';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
import { DatasetSpecialIdEnum } from '@fastgpt/global/core/dataset/constant';
import { Types, connectionMongo } from '@fastgpt/service/common/mongo';
import { delay } from '@/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
let initFileIds: string[] = [];
try {
const { limit = 100 } = req.body;
await connectToDatabase();
await authUser({ req, authRoot: true });
console.log('count rows');
// 去重获取 fileId
const { rows } = await PgClient.query(`SELECT DISTINCT file_id
FROM ${PgDatasetTableName} WHERE file_id IS NOT NULL AND file_id != '';
`);
console.log('count rows success', rows.length);
console.log('start filter');
for (let i = 0; i < rows.length; i += limit) {
await init(rows.slice(i, i + limit), initFileIds);
console.log(i);
}
for (let i = 0; i < initFileIds.length; i++) {
await PgClient.query(`UPDATE ${PgDatasetTableName}
SET file_id = '${DatasetSpecialIdEnum.manual}'
WHERE file_id = '${initFileIds[i]}'`);
console.log('update: ', initFileIds[i]);
}
const { rows: emptyIds } = await PgClient.query(
`SELECT id FROM ${PgDatasetTableName} WHERE file_id IS NULL OR file_id=''`
);
console.log('filter success');
console.log(emptyIds.length);
await delay(5000);
console.log('start update');
async function start(start: number) {
for (let i = start; i < emptyIds.length; i += limit) {
await PgClient.query(`UPDATE ${PgDatasetTableName}
SET file_id = '${DatasetSpecialIdEnum.manual}'
WHERE id = '${emptyIds[i].id}'`);
console.log('update: ', i, emptyIds[i].id);
}
}
for (let i = 0; i < limit; i++) {
start(i);
}
console.log('update success');
jsonRes(res, {
data: {
empty: emptyIds.length
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(rows: any[], initFileIds: string[]) {
const collection = connectionMongo.connection.db.collection(`dataset.files`);
/* 遍历所有的 fileId去找有没有对应的文件没有的话则改成manual */
const updateResult = await Promise.allSettled(
rows.map(async (item) => {
// 找下是否有对应的文件
const file = await collection.findOne({
_id: new Types.ObjectId(item.file_id)
});
if (file) return '';
// 没有文件的改成manual
initFileIds.push(item.file_id);
return item.file_id;
})
);
// @ts-ignore
console.log(updateResult.filter((item) => item?.value).length);
}

View File

@@ -1,344 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { App, connectToDatabase } from '@/service/mongo';
import { PgClient } from '@/service/pg';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { PgDatasetTableName } from '@/constants/plugin';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { delay } from '@/utils/tools';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { strIsLink } from '@fastgpt/global/common/string/tools';
import { GridFSStorage } from '@/service/lib/gridfs';
import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await connectToDatabase();
console.log('rename');
await rename();
console.log('init mongo data');
await initMongo(limit);
console.log('create collection');
await createCollection();
console.log('update pg collectionId');
await updatePgCollection();
console.log('init done');
jsonRes(res, {
data: {}
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
async function rename() {
// rename mongo kbs -> datasets
try {
const collections = await connectionMongo.connection.db
.listCollections({ name: 'kbs' })
.toArray();
if (collections.length > 0) {
const kbCollection = connectionMongo.connection.db.collection('kbs');
await kbCollection.rename('datasets', { dropTarget: true });
console.log('success rename kbs -> datasets');
}
} catch (error) {
console.log('error rename kbs -> datasets', error);
}
// rename pg: kb_id -> dataset_id
try {
const { rows } = await PgClient.query(`SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = '${PgDatasetTableName}'
AND column_name = 'kb_id'
);`);
if (rows[0].exists) {
await PgClient.query(`ALTER TABLE ${PgDatasetTableName} RENAME COLUMN kb_id TO dataset_id`);
console.log('success rename kb_id -> dataset_id');
}
} catch (error) {
console.log('error rename kb_id -> dataset_id', error);
}
// rename pg: file_id -> collection_id
try {
const { rows } = await PgClient.query(`SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = '${PgDatasetTableName}'
AND column_name = 'file_id'
);`);
if (rows[0].exists) {
await PgClient.query(
`ALTER TABLE ${PgDatasetTableName} RENAME COLUMN file_id TO collection_id`
);
console.log('success rename file_id -> collection_id');
}
} catch (error) {
console.log('error rename file_id -> collection_id', error);
}
}
async function initMongo(limit: number) {
let success = 0;
async function initApp(limit = 100): Promise<any> {
// 遍历所有 app更新 app modules 里的 FlowNodeTypeEnum.kbSearchNode
const apps = await App.find({ inited: false }).limit(limit);
if (apps.length === 0) return;
try {
await Promise.all(
apps.map(async (app) => {
const modules = app.toObject().modules;
// @ts-ignore
app.inited = true;
modules.forEach((module) => {
// @ts-ignore
if (module.flowType === 'kbSearchNode') {
module.flowType = FlowNodeTypeEnum.datasetSearchNode;
module.inputs.forEach((input) => {
if (input.key === 'kbList') {
input.key = 'datasets';
input.value?.forEach((item: any) => {
item.datasetId = item.kbId;
});
}
});
}
});
app.modules = JSON.parse(JSON.stringify(modules));
await app.save();
})
);
success += limit;
console.log('mongo init:', success);
return initApp(limit);
} catch (error) {
return initApp(limit);
}
}
// init app
await App.updateMany(
{},
{
$set: {
inited: false
}
}
);
const totalApp = await App.countDocuments();
console.log(`total app: ${totalApp}`);
await delay(2000);
console.log('start init app');
await initApp(limit);
console.log('init mongo success');
}
type RowType = { user_id: string; dataset_id: string; collection_id: string };
async function createCollection() {
let success = 0;
const { rows, rowCount } = await PgClient.query(`SELECT user_id,dataset_id,collection_id
FROM ${PgDatasetTableName}
GROUP BY user_id,collection_id, dataset_id
ORDER BY dataset_id`);
if (rowCount === 0) {
console.log('pg done');
return;
}
// init dataset collection
console.log(`total collection: ${rowCount}`);
// collectionId 的类型manual, mark, httpLink, fileId
async function initCollection(row: RowType): Promise<any> {
try {
{
const userId = row.user_id;
const datasetId = row.dataset_id;
const collectionId = row.collection_id;
const count = await MongoDatasetCollection.countDocuments({
datasetId,
userId,
['metadata.pgCollectionId']: collectionId
});
if (count > 0) {
console.log('collection already exist');
return;
}
if (collectionId === 'manual') {
await MongoDatasetCollection.create({
parentId: null,
datasetId,
userId,
name: '手动录入',
type: DatasetCollectionTypeEnum.virtual,
updateTime: new Date('2099'),
metadata: {
pgCollectionId: collectionId
}
});
} else if (collectionId === 'mark') {
await MongoDatasetCollection.create({
parentId: null,
datasetId,
userId,
name: '手动标注',
type: DatasetCollectionTypeEnum.virtual,
updateTime: new Date('2099'),
metadata: {
pgCollectionId: collectionId
}
});
} else if (strIsLink(collectionId)) {
await MongoDatasetCollection.create({
parentId: null,
datasetId,
userId,
name: collectionId,
type: DatasetCollectionTypeEnum.link,
metadata: {
rawLink: collectionId,
pgCollectionId: collectionId
}
});
} else {
// find file
const gridFs = new GridFSStorage('dataset', userId);
const collection = gridFs.Collection();
const file = await collection.findOne({
_id: new Types.ObjectId(collectionId)
});
if (file) {
await MongoDatasetCollection.create({
parentId: null,
datasetId,
userId,
name: file.filename,
type: DatasetCollectionTypeEnum.file,
metadata: {
fileId: file._id,
pgCollectionId: collectionId
}
});
} else {
// no file
await MongoDatasetCollection.create({
parentId: null,
datasetId,
userId,
name: '未知文件',
type: DatasetCollectionTypeEnum.virtual,
metadata: {
pgCollectionId: collectionId
}
});
}
}
console.log('create collection success');
}
} catch (error) {
console.log(error);
await delay(2000);
return initCollection(row);
}
}
for await (const row of rows) {
await initCollection(row);
console.log('init collection success: ', ++success);
}
}
async function updatePgCollection(): Promise<any> {
let success = 0;
const limit = 10;
const collections = await MongoDatasetCollection.find({
'metadata.pgCollectionId': { $exists: true, $ne: '' }
}).lean();
console.log('total:', collections.length);
async function update(i: number): Promise<any> {
const item = collections[i];
if (!item) return;
try {
console.log('start', item.name, item.datasetId, item.metadata.pgCollectionId);
const time = Date.now();
if (item.metadata.pgCollectionId) {
const { rows } = await PgClient.select(PgDatasetTableName, {
fields: ['id'],
where: [
['dataset_id', String(item.datasetId)],
'AND',
['collection_id', String(item.metadata.pgCollectionId)]
],
limit: 999999
});
console.log('update date total', rows.length, 'time:', Date.now() - time);
await PgClient.query(`
update ${PgDatasetTableName} set collection_id = '${item._id}' where dataset_id = '${String(
item.datasetId
)}' AND collection_id = '${String(item.metadata.pgCollectionId)}'
`);
console.log('pg update time', Date.now() - time);
}
// 更新 file id
if (item.type === 'file' && item.metadata.fileId) {
const collection = connectionMongo.connection.db.collection(`dataset.files`);
await collection.findOneAndUpdate({ _id: new Types.ObjectId(item.metadata.fileId) }, [
{
$set: {
'metadata.datasetId': item.datasetId,
'metadata.collectionId': item._id
}
}
]);
}
await MongoDatasetCollection.findByIdAndUpdate(item._id, {
$unset: { 'metadata.pgCollectionId': '' }
});
console.log('success', ++success);
return update(i + limit);
} catch (error) {
console.log(error);
await delay(5000);
return update(i);
}
}
const arr = new Array(limit).fill(0);
return Promise.all(arr.map((_, i) => update(i)));
}

View File

@@ -0,0 +1,335 @@
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,
getTeamInfoByTmbId
} 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 '@/utils/tools';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import {
DatasetCollectionSchemaType,
DatasetSchemaType,
DatasetTrainingSchemaType
} from '@fastgpt/global/core/dataset/type';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { Types } from 'mongoose';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { PgClient } from '@fastgpt/service/common/pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
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';
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 (global.systemEnv.pluginBaseUrl) {
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 getTeamInfoByTmbId({ 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 = connectionMongo.connection.db.collection(`dataset.files`);
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 getTeamInfoByTmbId({
userId
});
await DatasetFile.updateMany(
{
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.all([
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id CHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id CHAR(50);`),
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`)
]);
} catch (error) {
console.log(error);
console.log('column exits');
}
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 getTeamInfoByTmbId({ 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

@@ -1,32 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, App } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { AppListItemType } from '@/types/app';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
// 根据 userId 获取模型信息
const myApps = await App.find(
{
userId
},
'_id avatar name intro'
).sort({
updateTime: -1
});
jsonRes<AppListItemType[]>(res, {
data: myApps
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,37 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
export type Props = {
chatId: string;
customTitle?: string;
top?: boolean;
};
/* 更新聊天标题 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, customTitle, top } = req.body as Props;
const { userId } = await authUser({ req, authToken: true });
await Chat.findOneAndUpdate(
{
chatId,
userId
},
{
...(customTitle ? { customTitle } : {}),
...(top ? { top } : { top: null })
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,110 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, ChatItem, connectToDatabase } from '@/service/mongo';
import type { InitChatResponse } from '@/global/core/api/chatRes.d';
import { authUser } from '@fastgpt/service/support/user/auth';
import { ChatItemType } from '@/types/chat';
import { authApp } from '@/service/utils/auth';
import type { ChatSchema } from '@/types/mongoSchema';
import { getGuideModule } from '@/global/core/app/modules/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { TaskResponseKeyEnum } from '@/constants/chat';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
let { appId, chatId } = req.query as {
appId: '' | string;
chatId: '' | string;
};
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
}
// 校验使用权限
const app = (
await authApp({
appId,
userId,
authUser: false,
authOwner: false
})
).app;
// get app and history
const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } =
await (async () => {
if (chatId) {
// auth chatId
const [chat, history] = await Promise.all([
Chat.findOne(
{
chatId,
userId,
appId
},
'title variables'
),
ChatItem.find(
{
chatId,
userId,
appId
},
`dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}`
)
.sort({ _id: -1 })
.limit(30)
]);
if (!chat) {
throw new Error('聊天框不存在');
}
history.reverse();
return { app, history, chat };
}
return {};
})();
if (!app) {
throw new Error('Auth App Error');
}
const isOwner = String(app.userId) === userId;
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro,
canUse: app.share.isShare || isOwner
},
title: chat?.title || '新对话',
variables: chat?.variables || {},
history
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -1,56 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat, ChatItem } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { ChatSourceEnum } from '@/constants/chat';
type Props = {
chatId?: string;
appId?: string;
};
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, appId } = req.query as Props;
const { userId } = await authUser({ req, authToken: true });
if (chatId) {
await Promise.all([
Chat.findOneAndRemove({
chatId,
userId
}),
ChatItem.deleteMany({
userId,
chatId
})
]);
}
if (appId) {
const chats = await Chat.find({
appId,
userId,
source: ChatSourceEnum.online
}).select('_id');
const chatIds = chats.map((chat) => chat._id);
await Promise.all([
Chat.deleteMany({
_id: { $in: chatIds }
}),
ChatItem.deleteMany({
chatId: { $in: chatIds }
})
]);
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,9 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { GridFSStorage } from '@/service/lib/gridfs';
import { authFileToken } from './readUrl';
import { authFileToken } from '@fastgpt/service/support/permission/controller';
import jschardet from 'jschardet';
import { getDownloadBuf, getFileById } from '@fastgpt/service/common/file/gridfs/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -11,17 +11,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { token } = req.query as { token: string };
const { fileId, userId } = await authFileToken(token);
const { fileId, teamId, tmbId, bucketName } = await authFileToken(token);
if (!fileId) {
throw new Error('fileId is empty');
}
const gridFs = new GridFSStorage('dataset', userId);
const [file, buffer] = await Promise.all([
gridFs.findAndAuthFile(fileId),
gridFs.download(fileId)
getFileById({ bucketName, fileId }),
getDownloadBuf({ bucketName, fileId })
]);
const encoding = jschardet.detect(buffer)?.encoding;

View File

@@ -1,11 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { customAlphabet } from 'nanoid';
import multer from 'multer';
import path from 'path';
import { uploadFile } from '@fastgpt/service/common/file/gridfs/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
const nanoid = customAlphabet('1234567890abcdef', 12);
@@ -38,7 +39,11 @@ class UploadModel {
}).any();
async doUpload(req: NextApiRequest, res: NextApiResponse) {
return new Promise<{ files: FileType[]; metadata: Record<string, any> }>((resolve, reject) => {
return new Promise<{
files: FileType[];
bucketName: `${BucketNameEnum}`;
metadata: Record<string, any>;
}>((resolve, reject) => {
// @ts-ignore
this.uploader(req, res, (error) => {
if (error) {
@@ -52,6 +57,7 @@ class UploadModel {
...file,
originalname: decodeURIComponent(file.originalname)
})) || [],
bucketName: req.body.bucketName,
metadata: (() => {
if (!req.body?.metadata) return {};
try {
@@ -73,15 +79,16 @@ const upload = new UploadModel();
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
const { files, metadata } = await upload.doUpload(req, res);
const gridFs = new GridFSStorage('dataset', userId);
const { files, bucketName, metadata } = await upload.doUpload(req, res);
const upLoadResults = await Promise.all(
files.map((file) =>
gridFs.save({
uploadFile({
teamId,
tmbId,
bucketName,
path: file.path,
filename: file.originalname,
metadata: {

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
type Props = { base64Img: string };
@@ -9,7 +9,7 @@ type Props = { base64Img: string };
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
const { userId } = await authCert({ req, authToken: true });
const { base64Img } = req.body as Props;
const data = await uploadMongoImg({

View File

@@ -1,14 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { startQueue } from '@/service/utils/tools';
import { authUser } from '@fastgpt/service/support/user/auth';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
const { userId } = await authCert({ req, authToken: true });
await unlockTask(userId);
} catch (error) {}
jsonRes(res);

View File

@@ -1,27 +1,20 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import type { CreateQuestionGuideParams } from '@/global/core/api/aiReq.d';
import { pushQuestionGuideBill } from '@/service/common/bill/push';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { messages } = req.body as CreateQuestionGuideParams;
const { user } = await authUser({
const { tmbId, teamId } = await authCert({
req,
authOutLink: true,
authToken: true,
authApiKey: true,
authBalance: true
authToken: true
});
if (!user) {
throw new Error('user not found');
}
const qgModel = global.qgModels[0];
const { result, tokens } = await createQuestionGuide({
@@ -35,7 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
pushQuestionGuideBill({
tokens: tokens,
userId: user._id
teamId,
tmbId
});
} catch (err) {
jsonRes(res, {

View File

@@ -1,11 +1,11 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { App } from '@/service/models/app';
import type { CreateAppParams } from '@/types/app';
import { AppTypeEnum } from '@/constants/app';
import type { CreateAppParams } from '@fastgpt/global/core/app/api.d';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -22,21 +22,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
// 上限校验
const authCount = await App.countDocuments({
userId
const authCount = await MongoApp.countDocuments({
teamId
});
if (authCount >= 50) {
throw new Error('上限 50 个应用');
throw new Error('每个团队上限 50 个应用');
}
// 创建模型
const response = await App.create({
const response = await MongoApp.create({
avatar,
name,
userId,
teamId,
tmbId,
modules,
type
});

View File

@@ -1,16 +1,17 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Bill } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { Types } from '@fastgpt/service/common/mongo';
import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, start, end } = req.body as { appId: string; start: number; end: number };
const { userId } = await authUser({ req, authToken: true });
const { userId } = await authCert({ req, authToken: true });
const result = await Bill.aggregate([
const result = await MongoBill.aggregate([
{
$match: {
appId: new Types.ObjectId(appId),

View File

@@ -1,9 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, App, connectToDatabase } from '@/service/mongo';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authUser } from '@fastgpt/service/support/user/auth';
import { authApp } from '@/service/utils/auth';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -16,16 +17,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
// 验证是否是该用户的 app
await authApp({
appId,
userId
});
await authApp({ req, authToken: true, appId, per: 'owner' });
// 删除对应的聊天
await Chat.deleteMany({
await MongoChat.deleteMany({
appId
});
@@ -35,9 +30,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// 删除模型
await App.deleteOne({
_id: appId,
userId
await MongoApp.deleteOne({
_id: appId
});
jsonRes(res);

View File

@@ -1,8 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { authApp } from '@/service/utils/auth';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -15,12 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const { app } = await authApp({
appId,
userId
});
const { app } = await authApp({ req, authToken: true, appId, per: 'w' });
jsonRes(res, {
data: app

View File

@@ -1,12 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { PagingData } from '@/types';
import { AppLogsListItemType } from '@/types/app';
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';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -24,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' });
const where = {
appId: new Types.ObjectId(appId),
userId: new Types.ObjectId(userId),
teamId: new Types.ObjectId(teamId),
updateTime: {
$gte: new Date(dateStart),
$lte: new Date(dateEnd)
@@ -36,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
};
const [data, total] = await Promise.all([
Chat.aggregate([
MongoChat.aggregate([
{ $match: where },
{
$lookup: {
@@ -84,6 +85,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{ $limit: pageSize },
{
$project: {
_id: 1,
id: '$chatId',
title: 1,
source: 1,
@@ -94,7 +96,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
]),
Chat.countDocuments(where)
MongoChat.countDocuments(where)
]);
jsonRes<PagingData<AppLogsListItemType>>(res, {

View File

@@ -0,0 +1,38 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
// 凭证校验
const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true });
// 根据 userId 获取模型信息
const myApps = await MongoApp.find(
{ ...mongoRPermission({ teamId, tmbId, role }) },
'_id avatar name intro tmbId permission'
).sort({
updateTime: -1
});
jsonRes<AppListItemType[]>(res, {
data: myApps.map((app) => ({
_id: app._id,
avatar: app.avatar,
name: app.name,
intro: app.intro,
isOwner: teamOwner || String(app.tmbId) === tmbId,
permission: app.permission
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,17 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { App } from '@/service/models/app';
import type { AppUpdateParams } from '@/types/app';
import { authApp } from '@/service/utils/auth';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { SystemOutputEnum } from '@/constants/app';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, share, intro, modules } = req.body as AppUpdateParams;
const { name, avatar, type, intro, modules, permission } = req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
@@ -19,28 +18,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await authApp({
appId,
userId
});
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
// 更新模型
await App.updateOne(
await MongoApp.updateOne(
{
_id: appId,
userId
_id: appId
},
{
name,
type,
avatar,
intro,
...(share && {
'share.isShare': share.isShare,
'share.isShareDetail': share.isShareDetail
}),
permission,
...(modules && {
modules: modules.map((modules) => ({
...modules,

View File

@@ -1,14 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@fastgpt/service/support/user/auth';
import { sseErrRes } from '@/service/response';
import { sseResponseEventEnum } from '@/constants/chat';
import { sseErrRes } from '@fastgpt/service/common/response';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { responseWrite } from '@fastgpt/service/common/response';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { dispatchModules } from '@/pages/api/v1/chat/completions';
import { pushChatBill } from '@/service/common/bill/push';
import { BillSourceEnum } from '@/constants/user';
import { ChatItemType } from '@/types/chat';
import { pushChatBill } from '@/service/support/wallet/bill/push';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { authUser } from '@/service/support/permission/auth/user';
import { dispatchModules } from '@/service/moduleDispatch';
export type Props = {
history: ChatItemType[];
@@ -39,17 +40,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
/* user auth */
const { userId, user } = await authUser({ req, authToken: true, authBalance: true });
if (!user) {
throw new Error('user not found');
}
const [{ teamId, tmbId }, { user }] = await Promise.all([
authApp({ req, authToken: true, appId, per: 'r' }),
authUser({
req,
authToken: true,
minBalance: 0
})
]);
/* start process */
const { responseData } = await dispatchModules({
res,
modules: modules,
variables,
teamId,
tmbId,
user,
params: {
history,
@@ -74,7 +80,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
pushChatBill({
appName,
appId,
userId,
teamId,
tmbId,
source: BillSourceEnum.fastgpt,
response: responseData
});

View File

@@ -0,0 +1,54 @@
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 { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
type Props = {
chatId?: string;
appId?: string;
};
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, appId } = req.query as Props;
const { tmbId } = await authCert({ req, authToken: true });
if (chatId) {
await MongoChatItem.deleteMany({
chatId,
tmbId
});
await MongoChat.findOneAndRemove({
chatId,
tmbId
});
}
if (appId) {
const chats = await MongoChat.find({
appId,
tmbId,
source: ChatSourceEnum.online
}).select('_id');
const chatIds = chats.map((chat) => chat._id);
await MongoChatItem.deleteMany({
chatId: { $in: chatIds }
});
await MongoChat.deleteMany({
_id: { $in: chatIds }
});
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

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