4.8.5 test (#1819)

This commit is contained in:
Archer
2024-06-21 18:32:05 +08:00
committed by GitHub
parent 5cc01b8509
commit 24596a6e21
40 changed files with 908 additions and 1058 deletions

View File

@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { Box, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
@@ -7,7 +7,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import PageContainer from '@/components/PageContainer';
import SideTabs from '@/components/SideTabs';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import UserInfo from './components/Info';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
@@ -31,7 +31,7 @@ enum TabEnum {
'loginout' = 'loginout'
}
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
const { t } = useTranslation();
const { userInfo, setUserInfo } = useUserStore();
const { feConfigs, isPc, systemVersion } = useSystemStore();
@@ -40,14 +40,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/user/userLight',
label: t('user.Personal Information'),
id: TabEnum.info
value: TabEnum.info
},
...(feConfigs?.isPlus
? [
{
icon: 'support/usage/usageRecordLight',
label: t('user.Usage Record'),
id: TabEnum.usage
value: TabEnum.usage
}
]
: []),
@@ -56,7 +56,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/bill/payRecordLight',
label: t('support.wallet.Bills'),
id: TabEnum.bill
value: TabEnum.bill
}
]
: []),
@@ -66,7 +66,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/account/promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
value: TabEnum.promotion
}
]
: []),
@@ -75,21 +75,21 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/outlink/apikeyLight',
label: t('user.apikey.key'),
id: TabEnum.apikey
value: TabEnum.apikey
}
]
: []),
{
icon: 'support/user/individuation',
label: t('support.account.Individuation'),
id: TabEnum.individuation
value: TabEnum.individuation
},
...(feConfigs.isPlus
? [
{
icon: 'support/user/informLight',
label: t('user.Notice'),
id: TabEnum.inform
value: TabEnum.inform
}
]
: []),
@@ -97,7 +97,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/account/loginoutLight',
label: t('user.Sign Out'),
id: TabEnum.loginout
value: TabEnum.loginout
}
];
@@ -139,13 +139,13 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs
<SideTabs<TabEnum>
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList}
activeId={currentTab}
value={currentTab}
onChange={setCurrentTab}
/>
<Flex alignItems={'center'}>
@@ -157,14 +157,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Flex>
) : (
<Box mb={3}>
<Tabs
<LightRowTabs<TabEnum>
m={'auto'}
size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({
id: item.id,
value: item.value,
label: item.label
}))}
activeId={currentTab}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>

View File

@@ -8,7 +8,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
/* update chat top, custom title */
async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiResponse) {
const { appId, chatId, customTitle, top } = req.body;
const { appId, chatId, title, customTitle, top } = req.body;
await autChatCrud({
req,
authToken: true,
@@ -20,6 +20,7 @@ async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiRes
{ appId, chatId },
{
updateTime: new Date(),
...(title !== undefined && { title }),
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })
}

View File

@@ -19,7 +19,7 @@ import {
Switch,
Textarea
} from '@chakra-ui/react';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
@@ -97,7 +97,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
>
{/* Header: row and search */}
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
<RowTabs
<FillRowTabs
list={[
{
icon: 'core/modules/teamPlugin',

View File

@@ -15,7 +15,7 @@ import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
@@ -143,7 +143,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
>
<Box pl={'20px'} mb={3} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
<FillRowTabs
list={[
{
icon: 'core/modules/basicNode',

View File

@@ -21,11 +21,10 @@ import {
} from '@chakra-ui/react';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
@@ -310,9 +309,9 @@ export function RenderHttpProps({
label={t('core.module.http.Props tip', { variable: variableText })}
></QuestionTip>
</Flex>
<Tabs
<LightRowTabs<TabEnum>
list={[
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
{ label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params },
...(!['GET', 'DELETE'].includes(requestMethods)
? [
{
@@ -322,14 +321,17 @@ export function RenderHttpProps({
{jsonBody?.value && <Box ml={1}></Box>}
</Flex>
),
id: TabEnum.body
value: TabEnum.body
}
]
: []),
{ label: <RenderPropsItem text="Headers" num={headersLength} />, id: TabEnum.headers }
{
label: <RenderPropsItem text="Headers" num={headersLength} />,
value: TabEnum.headers
}
]}
activeId={selectedTab}
onChange={(e) => setSelectedTab(e as any)}
value={selectedTab}
onChange={setSelectedTab}
/>
<Box bg={'white'} borderRadius={'md'}>
{params &&

View File

@@ -1,8 +1,7 @@
import React, { useCallback, useState } from 'react';
import { Box, Flex, Button, useDisclosure, HStack } from '@chakra-ui/react';
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/utils/i18n';
import PageContainer from '@/components/PageContainer';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useI18n } from '@/web/context/I18n';
import { useTranslation } from 'next-i18next';
@@ -32,8 +31,8 @@ import {
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { CreateAppType } from './components/CreateModal';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import Tabs from '@/components/Tabs';
import MyBox from '@fastgpt/web/components/common/MyBox';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
const CreateModal = dynamic(() => import('./components/CreateModal'));
const EditFolderModal = dynamic(
@@ -121,26 +120,26 @@ const MyApps = () => {
alignItems={'center'}
justifyContent={'space-between'}
>
<Tabs
<LightRowTabs
list={[
{
label: appT('type.All'),
id: 'ALL'
value: 'ALL'
},
{
label: appT('type.Simple bot'),
id: AppTypeEnum.simple
value: AppTypeEnum.simple
},
{
label: appT('type.Workflow bot'),
id: AppTypeEnum.workflow
value: AppTypeEnum.workflow
},
{
label: appT('type.Plugin'),
id: AppTypeEnum.plugin
value: AppTypeEnum.plugin
}
]}
activeId={appType}
value={appType}
inlineStyles={{ px: 0.5 }}
gap={5}
display={'flex'}

View File

@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
const ChatHeader = ({
history,
@@ -16,8 +18,7 @@ const ChatHeader = ({
appAvatar,
chatModels,
showHistory,
onRoute2AppDetail,
onOpenSlider
onRoute2AppDetail
}: {
history: ChatItemType[];
appName: string;
@@ -25,7 +26,6 @@ const ChatHeader = ({
chatModels?: string[];
showHistory?: boolean;
onRoute2AppDetail?: () => void;
onOpenSlider: () => void;
}) => {
const theme = useTheme();
const { t } = useTranslation();
@@ -36,6 +36,8 @@ const ChatHeader = ({
[appName, history, t]
);
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
return (
<Flex
alignItems={'center'}

View File

@@ -8,7 +8,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useUserStore } from '@/web/support/user/useUserStore';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useI18n } from '@/web/context/I18n';
@@ -20,6 +20,9 @@ import {
} from '@fastgpt/global/common/parentFolder/type';
import { getMyApps } from '@/web/core/app/api';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import MyBox from '@fastgpt/web/components/common/MyBox';
type HistoryItemType = {
id: string;
@@ -38,30 +41,22 @@ const ChatHistorySlider = ({
appId,
appName,
appAvatar,
history,
apps = [],
confirmClearText,
activeChatId,
onChangeChat,
onDelHistory,
onClearHistory,
onSetHistoryTop,
onSetCustomTitle,
onClose
onSetCustomTitle
}: {
appId?: string;
appName: string;
appAvatar: string;
history: HistoryItemType[];
activeChatId: string;
apps?: AppListItemType[];
confirmClearText: string;
onChangeChat: (chatId?: string) => void;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
onClose: () => void;
}) => {
const theme = useTheme();
const router = useRouter();
@@ -73,7 +68,28 @@ const ChatHistorySlider = ({
const { isPc } = useSystemStore();
const { userInfo } = useUserStore();
const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.history);
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.history);
const {
histories,
onChangeChatId,
onChangeAppId,
chatId: activeChatId,
isLoading
} = useContextSelector(ChatContext, (v) => v);
const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}));
const newChat: HistoryItemType = { id: activeChatId, title: t('core.chat.New Chat') };
const activeChat = histories.find((item) => item.chatId === activeChatId);
return !activeChat ? [newChat].concat(formatHistories) : formatHistories;
}, [activeChatId, histories, t]);
const showApps = apps?.length > 0;
@@ -86,15 +102,6 @@ const ChatHistorySlider = ({
content: confirmClearText
});
const concatHistory = useMemo<HistoryItemType[]>(
() =>
!activeChatId
? //@ts-ignore
[{ id: activeChatId, title: t('core.chat.New Chat') }].concat(history)
: history,
[activeChatId, history, t]
);
const canRouteToDetail = useMemo(
() => appId && userInfo?.team.permission.hasWritePer,
[appId, userInfo?.team.permission.hasWritePer]
@@ -111,22 +118,10 @@ const ChatHistorySlider = ({
);
}, []);
const onChangeApp = useCallback(
(appId: string) => {
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});
},
[router]
);
return (
<Flex
position={'relative'}
<MyBox
isLoading={isLoading}
display={'flex'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
@@ -162,27 +157,27 @@ const ChatHistorySlider = ({
{/* menu */}
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5} alignItems={'center'}>
{!isPc && appId && (
<Tabs
<LightRowTabs<TabEnum>
flex={'1 0 0'}
mr={2}
list={[
{ label: t('core.chat.Recent use'), id: TabEnum.recently },
{ label: t('App'), id: TabEnum.app },
{ label: t('core.chat.History'), id: TabEnum.history }
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
{ label: t('App'), value: TabEnum.app },
{ label: t('core.chat.History'), value: TabEnum.history }
]}
activeId={currentTab}
onChange={(e) => setCurrentTab(e as `${TabEnum}`)}
value={currentTab}
onChange={setCurrentTab}
/>
)}
<Button
variant={'whitePrimary'}
flex={['0', 1]}
flex={['0 0 auto', 1]}
h={'100%'}
color={'primary.600'}
borderRadius={'xl'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
overflow={'hidden'}
onClick={() => onChangeChat()}
onClick={() => onChangeChatId()}
>
{t('core.chat.New Chat')}
</Button>
@@ -195,7 +190,11 @@ const ChatHistorySlider = ({
size={'mdSquare'}
aria-label={''}
borderRadius={'50%'}
onClick={openConfirm(onClearHistory)}
onClick={() =>
openConfirm(() => {
onClearHistory();
})()
}
>
<MyIcon name={'common/clearLight'} w={'16px'} />
</IconButton>
@@ -232,7 +231,7 @@ const ChatHistorySlider = ({
}
: {
onClick: () => {
onChangeChat(item.id);
onChangeChatId(item.id);
}
})}
>
@@ -292,7 +291,7 @@ const ChatHistorySlider = ({
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChat();
onChangeChatId();
}
},
type: 'danger'
@@ -324,10 +323,7 @@ const ChatHistorySlider = ({
color: 'primary.600'
}
: {
onClick: () => {
onChangeApp(item._id);
onClose();
}
onClick: () => onChangeAppId(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
@@ -344,8 +340,7 @@ const ChatHistorySlider = ({
value={appId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
onChangeAppId(id);
}}
server={getAppList}
/>
@@ -377,7 +372,7 @@ const ChatHistorySlider = ({
)}
<EditTitleModal />
<ConfirmModal />
</Flex>
</MyBox>
);
};

View File

@@ -7,13 +7,15 @@ import Avatar from '@/components/Avatar';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
import MyPopover from '@fastgpt/web/components/common/MyPopover/index';
import SelectOneResource from '@/components/common/folder/SelectOneResource';
import { getMyApps } from '@/web/core/app/api';
import {
GetResourceFolderListProps,
GetResourceListItemResponse
} from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
const SelectOneResource = dynamic(() => import('@/components/common/folder/SelectOneResource'));
const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppId: string }) => {
const { t } = useTranslation();
@@ -74,19 +76,19 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
{!isTeamChat && (
<>
<MyDivider h={2} my={1} />
<MyPopover
placement="right-start"
offset={[30, -65]}
trigger="hover"
Trigger={
<HStack
px={4}
my={2}
color={'myGray.500'}
fontSize={'sm'}
justifyContent={'space-between'}
>
<Box>{t('core.chat.Recent use')}</Box>
<HStack
px={4}
my={2}
color={'myGray.500'}
fontSize={'sm'}
justifyContent={'space-between'}
>
<Box>{t('core.chat.Recent use')}</Box>
<MyPopover
placement="bottom-end"
offset={[20, 10]}
trigger="hover"
Trigger={
<HStack
spacing={0.5}
cursor={'pointer'}
@@ -102,23 +104,23 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
<Box>{t('common.More')}</Box>
<MyIcon name={'common/select'} w={'1rem'} />
</HStack>
</HStack>
}
>
{({ onClose }) => (
<Box minH={'200px'}>
<SelectOneResource
value={activeAppId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
}}
server={getAppList}
/>
</Box>
)}
</MyPopover>
}
>
{({ onClose }) => (
<Box minH={'200px'}>
<SelectOneResource
value={activeAppId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
}}
server={getAppList}
/>
</Box>
)}
</MyPopover>
</HStack>
</>
)}

View File

@@ -1,25 +1,12 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import ChatBox from '@/components/ChatBox';
@@ -29,7 +16,6 @@ import SideBar from '@/components/SideBar';
import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps';
import ChatHeader from './components/ChatHeader';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
@@ -39,40 +25,48 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
import { useMount } from 'ahooks';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
type Props = { appId: string; chatId: string };
const Chat = ({
appId,
chatId,
myApps
}: Props & {
myApps: AppListItemType[];
}) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { setLastChatAppId } = useChatStore();
const {
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
clearHistories,
chatData,
setChatData,
delOneHistoryItem
} = useChatStore();
const { userInfo } = useUserStore();
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const { userInfo } = useUserStore();
const { isPc } = useSystemStore();
const { Loading, setIsLoading } = useLoading();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
const completionChatId = chatId ? chatId : nanoid();
const completionChatId = chatId ? chatId : getNanoid();
const { responseText, responseData } = await streamFetch({
data: {
@@ -89,170 +83,82 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId
}
});
onChangeChatId(completionChatId, true);
loadHistories();
}
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
});
onUpdateHistory({
appId,
chatId: completionChatId,
title: newTitle
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
title: newTitle
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
);
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
loading && setIsLoading(true);
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
setChatData({
...res,
history
});
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}));
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
const result: InitChatResponse = {
...res,
history
};
// reset chat box
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setLastChatAppId(appId);
setChatData(result);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId('');
setLastChatId('');
toast({
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
// reset all chat tore
if (e?.code === 501) {
router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
setIsLoading(false);
return null;
},
[setIsLoading, setChatData, setLastChatAppId, setLastChatId, toast, t, router]
}
);
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId
}
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId
}
});
}
if (!appId) {
(async () => {
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId
}
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
useQuery(['loadHistories', appId], () => (appId ? loadHistories({ appId }) : null));
return (
<Flex h={'100%'}>
<NextHead title={chatData.app.name}></NextHead>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{/* pc always show history. */}
{((children: React.ReactNode) => {
@@ -285,42 +191,17 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
clearHistories({ appId });
router.replace({
query: {
appId
}
});
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
onUpdateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
@@ -340,7 +221,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appName={chatData.app.name}
history={chatData.history}
chatModels={chatData.app.chatModels}
onOpenSlider={onOpenSlider}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
showHistory
/>
@@ -356,19 +236,81 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })}
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId}
chatId={chatId}
/>
</Box>
</Flex>
</Flex>
<Loading fixed={false} />
</PageContainer>
</Flex>
);
};
const Render = (props: Props) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { lastChatAppId, lastChatId } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
);
const { data: histories = [], runAsync: loadHistories } = useRequest2(
() => (appId ? getChatHistories({ appId }) : Promise.resolve([])),
{
manual: false,
refreshDeps: [appId]
}
);
// 初始化聊天框
useMount(async () => {
// pc: redirect to latest model chat
if (!appId) {
if (lastChatAppId) {
return router.replace({
query: {
...router.query,
appId: lastChatAppId,
chatId: lastChatId
}
});
}
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
...router.query,
appId: apps[0]._id,
chatId: ''
}
});
}
}
});
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
);
};
export async function getServerSideProps(context: any) {
return {
props: {
@@ -379,4 +321,4 @@ export async function getServerSideProps(context: any) {
};
}
export default Chat;
export default Render;

View File

@@ -1,15 +1,13 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import SideBar from '@/components/SideBar';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getErrText } from '@fastgpt/global/common/error/utils';
import type { ChatHistoryItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -21,27 +19,30 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { delChatRecordById, getChatHistories, getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { connectToDatabase } from '@/service/mongo';
import NextHead from '@/components/common/NextHead';
import Head from 'next/head';
import { useContextSelector } from 'use-context-selector';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
import { useMount } from 'ahooks';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const OutLink = ({
appName,
appIntro,
appAvatar
}: {
type Props = {
appName: string;
appIntro: string;
appAvatar: string;
}) => {
shareId: string;
authToken: string;
};
const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const {
@@ -58,28 +59,28 @@ const OutLink = ({
[key: string]: string;
};
const { toast } = useToast();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const { localUId } = useShareChatStore();
const {
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const appId = chatData.appId;
const { localUId } = useShareChatStore();
const outLinkUid: string = authToken || localUId;
const {
loadHistories,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
@@ -115,101 +116,86 @@ const OutLink = ({
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
onChangeChatId(completionChatId, true);
loadHistories();
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle,
shareId,
outLinkUid
});
onUpdateHistory({
appId,
chatId: completionChatId,
title: newTitle,
shareId,
outLinkUid
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
/* post message to report result */
const result: ChatSiteItemType[] = GPTMessages2Chats(prompts).map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: 'finish'
title: newTitle
}));
// hook message
window.top?.postMessage(
{
type: 'shareChatFinish',
data: {
question: result[0]?.value,
question: prompts[0]?.content,
answer: responseText
}
},
'*'
);
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
customVariables,
shareId,
outLinkUid,
setChatData,
appId,
pushHistory,
router,
histories,
updateHistory
forbidLoadChat,
onChangeChatId,
loadHistories,
onUpdateHistory,
appId
]
);
const loadChatInfo = useCallback(
async (shareId: string, chatId: string) => {
if (!shareId) return null;
const { loading } = useRequest2(
async () => {
if (!shareId || !outLinkUid || forbidLoadChat.current) return;
try {
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const result: InitChatResponse = {
...res,
history
};
setChatData({
...res,
history
});
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
// reset chat box
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setChatData(result);
},
{
manual: false,
refreshDeps: [shareId, outLinkUid, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
@@ -217,149 +203,87 @@ const OutLink = ({
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
if (chatId && res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
},
onError(e: any) {
console.log(e);
toast({
status: 'error',
title: getErrText(e, t('core.shareChat.Init Error'))
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
return null;
},
[outLinkUid, router, setChatData, t, toast]
}
);
const { isFetching } = useQuery(['init', shareId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadChatInfo(shareId, chatId);
});
// load histories
useQuery(['loadHistories', outLinkUid, shareId], () => {
if (shareId && outLinkUid) {
return loadHistories({
shareId,
outLinkUid
});
}
return null;
});
// window init
useEffect(() => {
useMount(() => {
setIdEmbed(window !== top);
}, []);
});
return (
<>
<NextHead title={appName} desc={appIntro} icon={appAvatar} />
<PageContainer
isLoading={loading}
{...(isEmbed
? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } }
: { p: [0, 5] })}
>
<MyBox
isLoading={isFetching}
h={'100%'}
display={'flex'}
flexDirection={['column', 'row']}
bg={'white'}
>
{showHistory === '1'
? ((children: React.ReactNode) => {
return isPc ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{children}
</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('core.chat.Confirm to clear share chat history')}
activeChatId={chatId}
history={histories.map((item) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{showHistory === '1' &&
((children: React.ReactNode) => {
return isPc ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
onChangeChat={(chatId) => {
router.replace({
query: {
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={({ chatId }) =>
delOneHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
clearHistories({ shareId, outLinkUid });
router.replace({
query: {
...router.query,
chatId: ''
}
});
}}
onSetHistoryTop={(e) => {
updateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={async (e) => {
updateHistory({
appId: chatData.appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
)
: null}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{children}
</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
)}
{/* chat container */}
<Flex
@@ -375,12 +299,10 @@ const OutLink = ({
appName={chatData.app.name}
history={chatData.history}
showHistory={showHistory === '1'}
onOpenSlider={onOpenSlider}
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
@@ -389,8 +311,14 @@ const OutLink = ({
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, shareId, outLinkUid })
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
shareId,
outLinkUid
})
}
appId={chatData.appId}
chatId={chatId}
@@ -399,16 +327,37 @@ const OutLink = ({
/>
</Box>
</Flex>
</MyBox>
</Flex>
</PageContainer>
</>
);
};
export default OutLink;
const Render = (props: Props) => {
const { shareId, authToken } = props;
const { localUId } = useShareChatStore();
const outLinkUid: string = authToken || localUId;
const { data: histories = [], runAsync: loadHistories } = useRequest2(
() => (shareId && outLinkUid ? getChatHistories({ shareId, outLinkUid }) : Promise.resolve([])),
{
manual: false,
refreshDeps: [shareId, outLinkUid]
}
);
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<OutLink {...props} />;
</ChatContextProvider>
);
};
export default Render;
export async function getServerSideProps(context: any) {
const shareId = context?.query?.shareId || '';
const authToken = context?.query?.authToken || '';
const app = await (async () => {
try {
@@ -433,6 +382,8 @@ export async function getServerSideProps(context: any) {
appName: app?.appId?.name ?? 'name',
appAvatar: app?.appId?.avatar ?? '',
appIntro: app?.appId?.intro ?? 'intro',
shareId: shareId ?? '',
authToken: authToken ?? '',
...(await serviceSideProps(context, ['file']))
}
};

View File

@@ -1,18 +1,9 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { delChatRecordById, getChatHistories, getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
@@ -22,21 +13,26 @@ import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { streamFetch } from '@/web/common/api/fetch';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
const OutLink = () => {
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const { t } = useTranslation();
const router = useRouter();
const {
@@ -45,11 +41,7 @@ const OutLink = () => {
chatId = '',
teamToken,
...customVariables
} = router.query as {
teamId: string;
appId: string;
chatId: string;
teamToken: string;
} = router.query as Props & {
[key: string]: string;
};
@@ -57,22 +49,20 @@ const OutLink = () => {
const theme = useTheme();
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const {
chatData,
setChatData,
histories,
loadHistories,
lastChatAppId,
lastChatId,
pushHistory,
updateHistory,
delOneHistory,
delOneHistoryItem,
clearHistories
} = useChatStore();
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId,
onChangeAppId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -99,34 +89,16 @@ const OutLink = () => {
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
onChangeChatId(completionChatId, true);
loadHistories();
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle,
teamId,
teamToken
});
onUpdateHistory({
appId: chatData.appId,
chatId: completionChatId,
title: newTitle,
teamId,
teamToken
});
}
// update chat window
setChatData((state) => ({
@@ -135,7 +107,7 @@ const OutLink = () => {
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
@@ -143,132 +115,63 @@ const OutLink = () => {
appId,
teamId,
teamToken,
setChatData,
pushHistory,
router,
histories,
updateHistory
forbidLoadChat,
onChangeChatId,
loadHistories,
onUpdateHistory,
chatData.appId
]
);
/* replace router query to last chat */
useEffect(() => {
if ((!chatId || !appId) && (lastChatId || lastChatAppId)) {
router.replace({
query: {
...router.query,
chatId: chatId || lastChatId,
appId: appId || lastChatAppId
}
});
}
}, []);
// get chat app list
const loadApps = useCallback(async () => {
try {
const apps = await getMyTokensApps({ teamId, teamToken });
if (apps.length <= 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return [];
}
// if app id not exist, redirect to first app
if (!appId || !apps.find((item) => item._id === appId)) {
router.replace({
query: {
...router.query,
appId: apps[0]?._id
}
});
}
return apps;
} catch (error: any) {
toast({
status: 'warning',
title: getErrText(error)
});
}
return [];
}, [appId, teamToken, router, teamId, t, toast]);
const { data: myApps = [], isLoading: isLoadingApps } = useQuery(['initApps', teamId], () => {
if (!teamId) {
toast({
status: 'error',
title: t('support.user.team.tag.Have not opened')
});
return;
}
return loadApps();
});
// load histories
useQuery(['loadHistories', appId], () => {
if (teamId && appId) {
return loadHistories({ teamId, appId, teamToken: teamToken });
}
return;
});
// get chat app info
const loadChatInfo = useCallback(async () => {
try {
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken });
const { loading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
setChatData({
const result: InitChatResponse = {
...res,
history
});
};
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
setChatData(result);
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
toast({
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
return null;
}, [teamId, appId, chatId, teamToken, setChatData, toast, t, router]);
const { isFetching } = useQuery(['init', teamId, appId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
if (teamId && appId) {
return loadChatInfo();
}
return null;
});
);
return (
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}>
<NextHead title={chatData.app.name}></NextHead>
<Flex h={'100%'}>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@@ -276,7 +179,7 @@ const OutLink = () => {
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => {
return isPc || !appId ? (
@@ -299,44 +202,18 @@ const OutLink = () => {
apps={myApps}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
confirmClearText={t('core.chat.Confirm to clear history')}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId, teamId, teamToken })}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
clearHistories({ appId, teamId, teamToken });
router.replace({
query: {
...router.query,
chatId: ''
}
});
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, teamId, teamToken, appId });
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
onUpdateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title,
teamId,
teamToken
@@ -357,13 +234,11 @@ const OutLink = () => {
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
onOpenSlider={onOpenSlider}
showHistory
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
@@ -372,8 +247,8 @@ const OutLink = () => {
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken })
onDelMessage={({ contentId }) =>
delChatRecordById({ contentId, appId: chatData.appId, chatId, teamId, teamToken })
}
appId={chatData.appId}
chatId={chatId}
@@ -384,16 +259,73 @@ const OutLink = () => {
</Flex>
</Flex>
</PageContainer>
</MyBox>
</Flex>
);
};
const Render = (props: Props) => {
const { teamId, appId, teamToken } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
async () => {
if (teamId && teamToken) {
return getMyTokensApps({ teamId, teamToken });
}
return [];
},
{
manual: false
}
);
const { data: histories = [], runAsync: loadHistories } = useRequest2(
async () => {
if (teamId && appId && teamToken) {
return getChatHistories({ teamId, appId, teamToken: teamToken });
}
return [];
},
{
manual: false,
refreshDeps: [appId, teamId, teamToken]
}
);
// 初始化聊天框
useEffect(() => {
(async () => {
if (appId || myApps.length === 0) return;
router.replace({
query: {
...router.query,
appId: myApps[0]._id,
chatId: ''
}
});
})();
}, [appId, loadMyApps, myApps, router, t, toast]);
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
);
};
export async function getServerSideProps(context: any) {
return {
props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
teamId: context?.query?.teamId || '',
teamToken: context?.query?.teamToken || '',
...(await serviceSideProps(context, ['file']))
}
};
}
export default OutLink;
export default Render;

View File

@@ -75,16 +75,16 @@ const InputDataModal = ({
});
const tabList = [
{ label: t('dataset.data.edit.Content'), id: TabEnum.content, icon: 'common/overviewLight' },
{ label: t('dataset.data.edit.Content'), value: TabEnum.content, icon: 'common/overviewLight' },
{
label: t('dataset.data.edit.Index', { amount: indexes.length }),
id: TabEnum.index,
value: TabEnum.index,
icon: 'kbTest'
},
...(dataId
? [{ label: t('dataset.data.edit.Delete'), id: TabEnum.delete, icon: 'delete' }]
? [{ label: t('dataset.data.edit.Delete'), value: TabEnum.delete, icon: 'delete' }]
: []),
{ label: t('dataset.data.edit.Course'), id: TabEnum.doc, icon: 'common/courseLight' }
{ label: t('dataset.data.edit.Course'), value: TabEnum.doc, icon: 'common/courseLight' }
];
const { ConfirmModal, openConfirm } = useConfirm({
@@ -243,10 +243,10 @@ const InputDataModal = ({
mb={6}
fontSize={'sm'}
/>
<SideTabs
<SideTabs<TabEnum>
list={tabList}
activeId={currentTab}
onChange={async (e: any) => {
value={currentTab}
onChange={async (e) => {
if (e === TabEnum.delete) {
return openConfirm(onDeleteData)();
}

View File

@@ -8,9 +8,9 @@ import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
import MyIcon from '@fastgpt/web/components/common/Icon';
import SideTabs from '@/components/SideTabs';
import { useRouter } from 'next/router';
import Tabs from '@/components/Tabs';
import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useI18n } from '@/web/context/I18n';
export enum TabEnum {
@@ -34,12 +34,12 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
const tabList = [
{
label: t('core.dataset.Collection'),
id: TabEnum.collectionCard,
value: TabEnum.collectionCard,
icon: 'common/overviewLight'
},
{ label: t('core.dataset.test.Search Test'), id: TabEnum.test, icon: 'kbTest' },
{ label: t('core.dataset.test.Search Test'), value: TabEnum.test, icon: 'kbTest' },
...(datasetDetail.permission.hasManagePer
? [{ label: t('common.Config'), id: TabEnum.info, icon: 'common/settingLight' }]
? [{ label: t('common.Config'), value: TabEnum.info, icon: 'common/settingLight' }]
: [])
];
@@ -78,16 +78,14 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
</Flex>
)}
</Box>
<SideTabs
<SideTabs<TabEnum>
px={4}
flex={1}
mx={'auto'}
w={'100%'}
list={tabList}
activeId={currentTab}
onChange={(e: any) => {
setCurrentTab(e);
}}
value={currentTab}
onChange={setCurrentTab}
/>
<Box px={4}>
{rebuildingCount > 0 && (
@@ -149,16 +147,13 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
</Flex>
) : (
<Box mb={3}>
<Tabs
<LightRowTabs<TabEnum>
m={'auto'}
w={'260px'}
size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
list={tabList}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>
)}

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api';

View File

@@ -5,7 +5,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';

View File

@@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api';