perf: switch perf (#2131)

* feat:  新增对话框底部复制按钮

* fix: 对话框底部复制按钮定位问题

* perf: 优化应用切换逻辑

* perf: 删除不必要代码

* fix: 修复编译失败bug&&修复冲突错误

* feat:  新增对话框底部复制按钮

* fix: 对话框底部复制按钮定位问题

* feat: 过长引用折叠功能

* merge: 合并主仓库代码

* refactor:  删除不必要代码

* perf: 优化应用切换逻辑

* perf: 删除不必要代码

* fix: 修复编译失败bug&&修复冲突错误

* merge

* merge: 处理冲突

* refactor: 重构代码删除不必要代码

* perf: 删除不必要代码
This commit is contained in:
papapatrick
2024-07-23 18:53:48 +08:00
committed by GitHub
parent bf5145e632
commit abcf48d5ec
9 changed files with 405 additions and 262 deletions

View File

@@ -97,6 +97,8 @@ export const iconPaths = {
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'),
'core/chat/cancelSpeak': () => import('./icons/core/chat/cancelSpeak.svg'),
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
'core/chat/sideLine': () => import('./icons/core/chat/sideLine.svg'),
'core/chat/chatFill': () => import('./icons/core/chat/chatFill.svg'),
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),

View File

@@ -0,0 +1,3 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.28082 5.72828C6.95538 6.05372 6.95538 6.58136 7.28082 6.90679C7.60626 7.23223 8.1339 7.23223 8.45933 6.90679L10.5 4.86615L12.5406 6.90679C12.8661 7.23223 13.3937 7.23223 13.7191 6.90679C14.0446 6.58136 14.0446 6.05372 13.7191 5.72828L11.0892 3.09839C10.7638 2.77295 10.2362 2.77295 9.91072 3.09839L7.28082 5.72828ZM7.28082 13.0893C6.95538 13.4147 6.95538 13.9424 7.28082 14.2678L9.91072 16.8977C9.9514 16.9384 9.99524 16.974 10.0414 17.0045C10.3649 17.2181 10.8045 17.1825 11.0892 16.8977L13.7191 14.2678C14.0446 13.9424 14.0446 13.4147 13.7191 13.0893C13.3937 12.7639 12.8661 12.7639 12.5406 13.0893L10.5 15.1299L8.45933 13.0893C8.1339 12.7639 7.60626 12.7639 7.28082 13.0893Z"/>
</svg>

After

Width:  |  Height:  |  Size: 836 B

View File

@@ -0,0 +1,3 @@
<svg width="4" height="14" viewBox="0 0 4 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="2" y1="2" x2="2" y2="12" stroke="#3370FF" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 195 B

View File

@@ -1,6 +1,7 @@
{
"add_new": "Add new",
"App": "App",
"all_apps": "All Apps",
"click_to_resume": "Resume",
"code_editor": "Code edit",
"Export": "Export",

View File

@@ -1,6 +1,7 @@
{
"add_new": "新增",
"App": "应用",
"all_apps": "全部应用",
"click_to_resume": "点击恢复",
"code_editor": "代码编辑",
"Export": "导出",

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { Flex, useTheme, Box } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import React, { useState, useCallback } from 'react';
import { Flex, useTheme, Box, useDisclosure } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import ToolMenu from './ToolMenu';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { useTranslation } from 'next-i18next';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
@@ -13,85 +13,287 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { InitChatResponse } from '@/global/core/chat/api';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useRouter } from 'next/router';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import {
GetResourceFolderListProps,
GetResourceListItemResponse
} from '@fastgpt/global/common/parentFolder/type';
import { getMyApps } from '@/web/core/app/api';
import SelectOneResource from '@/components/common/folder/SelectOneResource';
const ChatHeader = ({
chatData,
history,
showHistory,
onRoute2AppDetail
onRoute2AppDetail,
apps
}: {
chatData: InitChatResponse;
history: ChatItemType[];
showHistory?: boolean;
onRoute2AppDetail?: () => void;
apps?: AppListItemType[];
chatData: InitChatResponse;
}) => {
const theme = useTheme();
const { t } = useTranslation();
const { isPc } = useSystem();
const chatModels = chatData.app.chatModels;
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
return isPc && isPlugin ? null : (
<Flex
alignItems={'center'}
px={[3, 5]}
minH={['46px', '60px']}
borderBottom={theme.borders.sm}
color={'myGray.900'}
fontSize={'sm'}
>
{isPc ? (
<>
<Box mr={3} maxW={'160px'} className="textEllipsis" color={'myGray.1000'}>
{chatData.title}
</Box>
<MyTag>
<MyIcon name={'history'} w={'14px'} />
<Box ml={1}>
{history.length === 0
? t('common:core.chat.New Chat')
: t('core.chat.History Amount', { amount: history.length })}
</Box>
</MyTag>
{!!chatModels && chatModels.length > 0 && (
<MyTooltip label={chatModels.join(',')}>
<MyTag ml={2} colorSchema={'green'}>
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
<Box ml={1} maxW={'200px'} className="textEllipsis">
{chatModels.join(',')}
</Box>
</MyTag>
</MyTooltip>
)}
<Box flex={1} />
</>
) : (
<>
{showHistory && (
<MyIcon
name={'menu'}
w={'20px'}
h={'20px'}
color={'myGray.900'}
onClick={onOpenSlider}
const { isPc } = useSystem();
return (
<>
{isPc && isPlugin ? null : (
<Flex
alignItems={'center'}
px={[3, 5]}
minH={['46px', '60px']}
borderBottom={'sm'}
color={'myGray.900'}
fontSize={'sm'}
>
{isPc ? (
<PcHeader
title={chatData.title}
chatModels={chatData.app.chatModels}
history={history}
/>
) : (
<MobileHeader
apps={apps}
appId={chatData.appId}
go2AppDetail={onRoute2AppDetail}
name={chatData.app.name}
avatar={chatData.app.avatar}
showHistory={showHistory}
/>
)}
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
<Avatar src={chatData.app.avatar} w={'16px'} />
<Box ml={1} className="textEllipsis" onClick={onRoute2AppDetail}>
{chatData.app.name}
</Box>
</Flex>
</>
{/* control */}
{!isPlugin && <ToolMenu history={history} />}
</Flex>
)}
</>
);
};
{/* control */}
{!isPlugin && <ToolMenu history={history} />}
</Flex>
const MobileDrawer = ({
onCloseDrawer,
appId,
apps
}: {
onCloseDrawer: () => void;
appId: string;
apps?: AppListItemType[];
}) => {
enum TabEnum {
recently = 'recently',
app = 'app'
}
const { t } = useTranslation();
const { isPc } = useSystem();
const router = useRouter();
const isTeamChat = router.pathname === '/chat/team';
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.recently);
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
return getMyApps({ parentId }).then((res) =>
res.map<GetResourceListItemResponse>((item) => ({
id: item._id,
name: item.name,
avatar: item.avatar,
isFolder: item.type === AppTypeEnum.folder
}))
);
}, []);
const { onChangeAppId } = useContextSelector(ChatContext, (v) => v);
return (
<>
<Box
position={'absolute'}
top={'45px'}
w={'100vw'}
h={'calc(100% - 45px)'}
background={'rgba(0, 0, 0, 0.2)'}
left={0}
zIndex={5}
onClick={() => {
onCloseDrawer();
}}
>
{/* menu */}
<Box
w={'100vw'}
px={[2, 5]}
padding={2}
onClick={(e) => e.stopPropagation()}
background={'white'}
position={'relative'}
>
{!isPc && appId && (
<LightRowTabs<TabEnum>
flex={'1 0 0'}
width={isTeamChat ? '30%' : '60%'}
mr={10}
inlineStyles={{
px: 1
}}
list={[
...(isTeamChat
? [{ label: t('common:all_apps'), value: TabEnum.recently }]
: [
{ label: t('common:core.chat.Recent use'), value: TabEnum.recently },
{ label: t('common:all_apps'), value: TabEnum.app }
])
]}
value={currentTab}
onChange={setCurrentTab}
/>
)}
</Box>
<Box
width={'100vw'}
height={'auto'}
minH={'10vh'}
maxH={'60vh'}
overflow={'auto'}
background={'white'}
zIndex={3}
onClick={(e) => e.stopPropagation()}
borderRadius={'0 0 10px 10px'}
position={'relative'}
padding={3}
pt={0}
pb={4}
>
{/* history */}
{currentTab === TabEnum.recently && (
<>
{Array.isArray(apps) &&
apps.map((item) => (
<Flex justify={'center'} key={item._id}>
<Flex
py={2.5}
px={2}
width={'100%'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {
backgroundColor: 'primary.50 !important',
color: 'primary.600'
}
: {
onClick: () => onChangeAppId(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
</Flex>
</Flex>
))}
</>
)}
{currentTab === TabEnum.app && !isPc && (
<>
<SelectOneResource
value={appId}
onSelect={(id) => {
if (!id) return;
onChangeAppId(id);
}}
server={getAppList}
/>
</>
)}
</Box>
</Box>
</>
);
};
const MobileHeader = ({
showHistory,
go2AppDetail,
name,
avatar,
appId,
apps
}: {
showHistory?: boolean;
go2AppDetail?: () => void;
avatar: string;
name: string;
apps?: AppListItemType[];
appId: string;
}) => {
const { isPc } = useSystem();
const router = useRouter();
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
const { isOpen: isOpenDrawer, onToggle: toggleDrawer, onClose: onCloseDrawer } = useDisclosure();
const isShareChat = router.pathname === '/chat/share';
return (
<>
{showHistory && (
<MyIcon name={'menu'} w={'20px'} h={'20px'} color={'myGray.900'} onClick={onOpenSlider} />
)}
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
<Avatar src={avatar} w={'16px'} />
<Box ml={1} className="textEllipsis" onClick={go2AppDetail}>
{name}
</Box>
{isShareChat ? null : (
<MyIcon
_active={{ transform: 'scale(0.9)' }}
name={'core/chat/chevronSelector'}
w={'20px'}
h={'20px'}
color={isOpenDrawer ? 'primary.600' : 'myGray.900'}
onClick={toggleDrawer}
/>
)}
</Flex>
{!isPc && isOpenDrawer && !isShareChat && (
<MobileDrawer apps={apps} appId={appId} onCloseDrawer={onCloseDrawer} />
)}
</>
);
};
const PcHeader = ({
title,
chatModels,
history
}: {
title: string;
chatModels?: string[];
history: ChatItemType[];
}) => {
const { t } = useTranslation();
return (
<>
<Box mr={3} maxW={'160px'} className="textEllipsis" color={'myGray.1000'}>
{title}
</Box>
<MyTag>
<MyIcon name={'history'} w={'14px'} />
<Box ml={1}>
{history.length === 0
? t('common:core.chat.New Chat')
: t('common:core.chat.History Amount', { amount: history.length })}
</Box>
</MyTag>
{!!chatModels && chatModels.length > 0 && (
<MyTooltip label={chatModels.join(',')}>
<MyTag ml={2} colorSchema={'green'}>
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
<Box ml={1} maxW={'200px'} className="textEllipsis">
{chatModels.join(',')}
</Box>
</MyTag>
</MyTooltip>
)}
<Box flex={1} />
</>
);
};

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { Box, Button, Flex, useTheme, IconButton } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useRouter } from 'next/router';
import Avatar from '@fastgpt/web/components/common/Avatar';
@@ -8,22 +8,13 @@ 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 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';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import SelectOneResource from '@/components/common/folder/SelectOneResource';
import {
GetResourceFolderListProps,
GetResourceListItemResponse
} 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';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
type HistoryItemType = {
id: string;
@@ -32,12 +23,6 @@ type HistoryItemType = {
top?: boolean;
};
enum TabEnum {
recently = 'recently',
'app' = 'app',
'history' = 'history'
}
const ChatHistorySlider = ({
appId,
appName,
@@ -69,12 +54,9 @@ const ChatHistorySlider = ({
const { isPc } = useSystem();
const { userInfo } = useUserStore();
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.history);
const {
histories,
onChangeChatId,
onChangeAppId,
chatId: activeChatId,
isLoading
} = useContextSelector(ChatContext, (v) => v);
@@ -86,7 +68,10 @@ const ChatHistorySlider = ({
customTitle: item.customTitle,
top: item.top
}));
const newChat: HistoryItemType = { id: activeChatId, title: t('common:core.chat.New Chat') };
const newChat: HistoryItemType = {
id: activeChatId,
title: t('common:core.chat.New Chat')
};
const activeChat = histories.find((item) => item.chatId === activeChatId);
return !activeChat ? [newChat].concat(formatHistories) : formatHistories;
@@ -106,17 +91,6 @@ const ChatHistorySlider = ({
[appId, userInfo?.team.permission.hasWritePer]
);
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
return getMyApps({ parentId }).then((res) =>
res.map<GetResourceListItemResponse>((item) => ({
id: item._id,
name: item.name,
avatar: item.avatar,
isFolder: item.type === AppTypeEnum.folder
}))
);
}, []);
return (
<MyBox
isLoading={isLoading}
@@ -154,26 +128,21 @@ const ChatHistorySlider = ({
)}
{/* menu */}
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5} alignItems={'center'}>
<Flex
w={'100%'}
px={[2, 5]}
h={'36px'}
my={5}
justify={['space-between', '']}
alignItems={'center'}
>
{!isPc && appId && (
<LightRowTabs<TabEnum>
flex={'1 0 0'}
mr={1}
inlineStyles={{
px: 1
}}
list={[
...(isTeamChat
? [{ label: t('common:App'), value: TabEnum.recently }]
: [
{ label: t('common:core.chat.Recent use'), value: TabEnum.recently },
{ label: t('common:App'), value: TabEnum.app }
]),
{ label: t('common:core.chat.History'), value: TabEnum.history }
]}
value={currentTab}
onChange={setCurrentTab}
/>
<Flex height={'100%'} align={'center'} justify={'center'}>
<MyIcon ml={2} name="core/chat/sideLine" />
<Box ml={2} fontWeight={'bold'}>
{t('common:core.chat.History')}
</Box>
</Flex>
)}
<Button
@@ -210,151 +179,112 @@ const ChatHistorySlider = ({
<Box flex={'1 0 0'} h={0} px={[2, 5]} overflow={'overlay'}>
{/* chat history */}
{(currentTab === TabEnum.history || isPc) && (
<>
{concatHistory.map((item, i) => (
<Flex
position={'relative'}
key={item.id || `${i}`}
alignItems={'center'}
py={2.5}
px={4}
cursor={'pointer'}
userSelect={'none'}
borderRadius={'md'}
mb={2}
fontSize={'sm'}
_hover={{
bg: 'myGray.50',
'& .more': {
visibility: 'visible'
}
}}
bg={item.top ? '#E6F6F6 !important' : ''}
{...(item.id === activeChatId
? {
backgroundColor: 'primary.50 !important',
color: 'primary.600'
}
: {
onClick: () => {
onChangeChatId(item.id);
}
})}
>
<MyIcon
name={item.id === activeChatId ? 'core/chat/chatFill' : 'core/chat/chatLight'}
w={'16px'}
/>
<Box flex={'1 0 0'} ml={3} className="textEllipsis">
{item.customTitle || item.title}
</Box>
{!!item.id && (
<Box className="more" visibility={['visible', 'hidden']}>
<MyMenu
Button={
<IconButton
size={'xs'}
variant={'whiteBase'}
icon={<MyIcon name={'more'} w={'14px'} p={1} />}
aria-label={''}
/>
}
menuList={[
{
children: [
...(onSetHistoryTop
? [
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({ chatId: item.id, top: !item.top });
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChatId();
}
},
type: 'danger'
}
]
}
]}
/>
</Box>
)}
</Flex>
))}
</>
)}
{currentTab === TabEnum.recently && !isPc && (
<>
{Array.isArray(apps) &&
apps.map((item) => (
<Flex
key={item._id}
py={2}
px={3}
mb={3}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {
backgroundColor: 'primary.50 !important',
color: 'primary.600'
}
: {
onClick: () => onChangeAppId(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
</Flex>
))}
</>
)}
{currentTab === TabEnum.app && !isPc && (
<>
<SelectOneResource
value={appId}
onSelect={(id) => {
if (!id) return;
onChangeAppId(id);
<>
{concatHistory.map((item, i) => (
<Flex
position={'relative'}
key={item.id || `${i}`}
alignItems={'center'}
py={2.5}
px={4}
cursor={'pointer'}
userSelect={'none'}
borderRadius={'md'}
mb={2}
fontSize={'sm'}
_hover={{
bg: 'myGray.50',
'& .more': {
visibility: 'visible'
}
}}
server={getAppList}
/>
</>
)}
bg={item.top ? '#E6F6F6 !important' : ''}
{...(item.id === activeChatId
? {
backgroundColor: 'primary.50 !important',
color: 'primary.600'
}
: {
onClick: () => {
onChangeChatId(item.id);
}
})}
>
<MyIcon
name={item.id === activeChatId ? 'core/chat/chatFill' : 'core/chat/chatLight'}
w={'16px'}
/>
<Box flex={'1 0 0'} ml={3} className="textEllipsis">
{item.customTitle || item.title}
</Box>
{!!item.id && (
<Box className="more" visibility={['visible', 'hidden']}>
<MyMenu
Button={
<IconButton
size={'xs'}
variant={'whiteBase'}
icon={<MyIcon name={'more'} w={'14px'} p={1} />}
aria-label={''}
/>
}
menuList={[
{
children: [
...(onSetHistoryTop
? [
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({
chatId: item.id,
top: !item.top
});
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChatId();
}
},
type: 'danger'
}
]
}
]}
/>
</Box>
)}
</Flex>
))}
</>
</Box>
{/* exec */}

View File

@@ -226,6 +226,7 @@ const Chat = ({
>
{/* header */}
<ChatHeader
apps={myApps}
chatData={chatData}
history={chatRecords}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}

View File

@@ -230,7 +230,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
flexDirection={'column'}
>
{/* header */}
<ChatHeader chatData={chatData} history={chatData.history} showHistory />
<ChatHeader apps={myApps} chatData={chatData} history={chatData.history} showHistory />
{/* chat box */}
<Box flex={1}>
{chatData.app.type === AppTypeEnum.plugin ? (