From ff9c6c9b81cac4bf0985cbe60b70bb7e0abd36a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8D=E9=97=B2=E7=8A=AC?= Date: Wed, 20 Aug 2025 20:22:21 +0800 Subject: [PATCH] chore: extract chat history and drawer; fix model selector (#5492) * chore: extract chat history and drawer; fix model selector * refactor: chat slider components structure --- .../src/components/Select/AIModelSelector.tsx | 2 +- .../ChatContainer/ChatBox/Input/ChatInput.tsx | 2 +- .../src/pageComponents/chat/ChatHeader.tsx | 5 +- .../pageComponents/chat/ChatHistorySlider.tsx | 426 ------------------ .../pageComponents/chat/ChatSetting/index.tsx | 65 +-- .../pageComponents/chat/ChatTeamApp/index.tsx | 35 +- .../chat/ChatWindow/AppChatWindow.tsx | 43 +- .../chat/ChatWindow/HomeChatWindow.tsx | 116 ++--- .../src/pageComponents/chat/SliderApps.tsx | 4 +- .../pageComponents/chat/UserAvatarPopover.tsx | 12 +- .../chat/slider/ChatSliderFooter.tsx | 61 +++ .../chat/slider/ChatSliderHeader.tsx | 124 +++++ .../chat/slider/ChatSliderList.tsx | 193 ++++++++ .../chat/slider/ChatSliderMenu.tsx | 81 ++++ .../chat/slider/ChatSliderMobileDrawer.tsx | 64 +++ .../chat/slider/ChatSliderSidebar.tsx | 34 ++ projects/app/src/pages/chat/share.tsx | 28 +- .../core/chat/context/chatSettingContext.tsx | 6 +- 18 files changed, 673 insertions(+), 628 deletions(-) delete mode 100644 projects/app/src/pageComponents/chat/ChatHistorySlider.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderList.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderMenu.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderMobileDrawer.tsx create mode 100644 projects/app/src/pageComponents/chat/slider/ChatSliderSidebar.tsx diff --git a/projects/app/src/components/Select/AIModelSelector.tsx b/projects/app/src/components/Select/AIModelSelector.tsx index 03974b565..23bb4075f 100644 --- a/projects/app/src/components/Select/AIModelSelector.tsx +++ b/projects/app/src/components/Select/AIModelSelector.tsx @@ -60,7 +60,7 @@ const OneRowSelector = ({ list, onChange, disableTip, ...props }: Props) => { fallbackSrc={HUGGING_FACE_ICON} /> - {modelData.name} + {modelData.name} ) }; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx index 0b16db697..e76183674 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx @@ -256,7 +256,7 @@ const ChatInput = ({ gap={[0, 1]} > {/* 左侧自定义按钮组 */} - + {InputLeftComponent} diff --git a/projects/app/src/pageComponents/chat/ChatHeader.tsx b/projects/app/src/pageComponents/chat/ChatHeader.tsx index 8ffd1c643..6f2996e36 100644 --- a/projects/app/src/pageComponents/chat/ChatHeader.tsx +++ b/projects/app/src/pageComponents/chat/ChatHeader.tsx @@ -38,11 +38,13 @@ const ChatHeader = ({ showHistory, apps, totalRecordsCount, + pane, chatSettings }: { pane: ChatSidebarPaneEnum; chatSettings: ChatSettingSchema | undefined; + history: ChatItemType[]; showHistory?: boolean; apps?: AppListItemType[]; @@ -51,13 +53,14 @@ const ChatHeader = ({ const { t } = useTranslation(); const { isPc } = useSystem(); const pathname = usePathname(); + const { source } = useChatStore(); const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible); const isPlugin = chatData.app.type === AppTypeEnum.plugin; const isChat = pathname === '/chat'; - const isShare = pathname === '/chat/share'; + const isShare = source === 'share'; return isPc && isPlugin ? null : ( void; -}) => { - const theme = useTheme(); - const pathname = usePathname(); - const { t } = useTranslation(); - const { isPc } = useSystem(); - - const { userInfo } = useUserStore(); - - const { chatId: activeChatId, setChatId } = useChatStore(); - const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); - const ScrollData = useContextSelector(ChatContext, (v) => v.ScrollData); - const histories = useContextSelector(ChatContext, (v) => v.histories); - const onDelHistory = useContextSelector(ChatContext, (v) => v.onDelHistory); - const onClearHistory = useContextSelector(ChatContext, (v) => v.onClearHistories); - const onUpdateHistory = useContextSelector(ChatContext, (v) => v.onUpdateHistory); - const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); - - const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name); - const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar); - const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); - - const isActivePane = useCallback((active: ChatSidebarPaneEnum) => active === pane, [pane]); - - const isShare = pathname === '/chat/share'; - - const concatHistory = useMemo(() => { - const formatHistories: HistoryItemType[] = histories.map((item) => { - return { - id: item.chatId, - title: item.title, - customTitle: item.customTitle, - top: item.top, - updateTime: item.updateTime - }; - }); - const newChat: HistoryItemType = { - id: activeChatId, - title: t('common:core.chat.New Chat'), - updateTime: new Date() - }; - const activeChat = histories.find((item) => item.chatId === activeChatId); - - return !activeChat ? [newChat].concat(formatHistories) : formatHistories; - }, [activeChatId, histories, t]); - - // custom title edit - const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({ - title: t('common:core.chat.Custom History Title'), - placeholder: t('common:core.chat.Custom History Title Description') - }); - - return ( - - {isPc && ( - - {!customSliderTitle && } - - - {customSliderTitle || appName} - - - )} - - {!isPc && ( - <> - - banner - - - - - {!isShare && ( - <> - - { - onPaneChange?.(ChatSidebarPaneEnum.HOME); - onCloseSlider(); - setChatId(); - }} - > - - - - {t('chat:sidebar.home')} - - - - - { - onPaneChange?.(ChatSidebarPaneEnum.TEAM_APPS); - onCloseSlider(); - }} - > - - - - {t('chat:sidebar.team_apps')} - - - - - - - )} - - )} - - {/* menu */} - - {!isPc && ( - - - - {t('common:core.chat.History')} - - - )} - - - {/* Clear */} - {isPc && histories.length > 0 && ( - - } - /> - - } - type="delete" - content={confirmClearText} - onConfirm={() => onClearHistory()} - /> - )} - - - - {/* chat history */} - <> - {concatHistory.map((item, i) => ( - { - onChangeChatId(item.id); - setCiteModalData(undefined); - } - })} - {...(i !== concatHistory.length - 1 && { - mb: '8px' - })} - > - - - {item.customTitle || item.title} - - {!!item.id && ( - - - {t(formatTimeToChatTime(item.updateTime) as any).replace('#', ':')} - - - } - aria-label={''} - /> - } - menuList={[ - { - children: [ - { - label: item.top - ? t('common:core.chat.Unpin') - : t('common:core.chat.Pin'), - icon: 'core/chat/setTopLight', - onClick: () => { - onUpdateHistory({ - chatId: item.id, - top: !item.top - }); - } - }, - - { - label: t('common:custom_title'), - icon: 'common/customTitleLight', - onClick: () => { - onOpenModal({ - defaultVal: item.customTitle || item.title, - onSuccess: (e) => - onUpdateHistory({ - chatId: item.id, - customTitle: e - }) - }); - } - }, - { - label: t('common:Delete'), - icon: 'delete', - onClick: () => { - onDelHistory(item.id); - if (item.id === activeChatId) { - onChangeChatId(); - setCiteModalData(undefined); - } - }, - type: 'danger' - } - ] - } - ]} - /> - - - )} - - ))} - - - - {!isPc && ( - - - - - - {userInfo?.username} - - - - - {!isShare && ( - { - onPaneChange?.(ChatSidebarPaneEnum.SETTING); - onCloseSlider(); - }} - > - - - )} - - )} - - - - ); -}; - -export default ChatHistorySlider; diff --git a/projects/app/src/pageComponents/chat/ChatSetting/index.tsx b/projects/app/src/pageComponents/chat/ChatSetting/index.tsx index 006156905..b6e2ea86a 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/index.tsx @@ -1,17 +1,17 @@ import DiagramModal from '@/pageComponents/chat/ChatSetting/DiagramModal'; -import { useCallback, useState } from 'react'; +import { type PropsWithChildren, useCallback, useState } from 'react'; import { ChatSettingTabOptionEnum } from '@/pageComponents/chat/constants'; import dynamic from 'next/dynamic'; import SettingTabs from '@/pageComponents/chat/ChatSetting/SettingTabs'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import { Drawer, DrawerContent, DrawerOverlay, Flex } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import { useContextSelector } from 'use-context-selector'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider'; -import { useTranslation } from 'react-i18next'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import NextHead from '@/components/common/NextHead'; import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; +import { useTranslation } from 'react-i18next'; const HomepageSetting = dynamic(() => import('@/pageComponents/chat/ChatSetting/HomepageSetting')); const LogDetails = dynamic(() => import('@/pageComponents/chat/ChatSetting/LogDetails')); @@ -24,21 +24,15 @@ const ChatSetting = () => { const [isOpenDiagram, setIsOpenDiagram] = useState(false); const [tab, setTab] = useState<`${ChatSettingTabOptionEnum}`>('home'); - const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider); - const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); const SettingHeader = useCallback( - ({ children }: { children?: React.ReactNode }) => ( - <> - - {children} - - + ({ children }: PropsWithChildren) => ( + + {children} + ), [tab, setTab] ); @@ -48,33 +42,24 @@ const ChatSetting = () => { {!isPc && ( - - + <> + + + - - - - - - - + + )} {/* homepage setting */} diff --git a/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx b/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx index 9e405b812..9ab2873b0 100644 --- a/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx @@ -1,7 +1,5 @@ -'use client'; - import React, { useMemo, useState } from 'react'; -import { Box, Flex, Tab, TabIndicator, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; +import { Box, Flex, Tab, TabIndicator, TabList, Tabs } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useContextSelector } from 'use-context-selector'; import AppListContextProvider, { AppListContext } from '@/pageComponents/dashboard/apps/context'; @@ -13,27 +11,23 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem'; import List from '@/pageComponents/chat/ChatTeamApp/List'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { Drawer, DrawerContent, DrawerOverlay } from '@chakra-ui/react'; -import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import NextHead from '@/components/common/NextHead'; import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; const MyApps = () => { const { t } = useTranslation(); const router = useRouter(); const { isPc } = useSystem(); + const { paths, myApps, isFetchingApps, setSearchKey } = useContextSelector( AppListContext, (v) => v ); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); - const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); - const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider); const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider); const map = useMemo( @@ -69,23 +63,12 @@ const MyApps = () => { onClick={onOpenSlider} /> - - - - - - + )} diff --git a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx index 8762b49be..7c5f3b392 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx @@ -1,7 +1,6 @@ import ChatHeader from '@/pageComponents/chat/ChatHeader'; import ChatBox from '@/components/core/chat/ChatContainer/ChatBox'; -import { Flex, Box, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react'; -import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider'; +import { Flex, Box } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import SideBar from '@/components/SideBar'; @@ -20,26 +19,26 @@ import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getInitChatInfo } from '@/web/core/chat/api'; import { useUserStore } from '@/web/support/user/useUserStore'; -import { useRouter } from 'next/router'; import NextHead from '@/components/common/NextHead'; import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; import { ChatSidebarPaneEnum } from '../constants'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; +import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; type Props = { myApps: AppListItemType[]; }; const AppChatWindow = ({ myApps }: Props) => { - const router = useRouter(); const { userInfo } = useUserStore(); const { chatId, appId, outLinkAuthData } = useChatStore(); + const { feConfigs } = useSystemStore(); const { t } = useTranslation(); const { isPc } = useSystem(); - const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider); const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat); - const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle); const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); @@ -123,33 +122,19 @@ const AppChatWindow = ({ myApps }: Props) => { {/* show history slider */} - {isPc || !appId ? ( + {isPc ? ( - ) : ( - - - - - - + )} {/* chat container */} diff --git a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx index c4674a2d0..2e58dc829 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx @@ -2,9 +2,6 @@ import ChatBox from '@/components/core/chat/ChatContainer/ChatBox'; import { Flex, Box, - Drawer, - DrawerOverlay, - DrawerContent, Button, Menu, MenuButton, @@ -12,7 +9,6 @@ import { MenuItem, Checkbox } from '@chakra-ui/react'; -import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider'; import { useTranslation } from 'react-i18next'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import SideBar from '@/components/SideBar'; @@ -48,10 +44,9 @@ import type { } from '@fastgpt/global/core/app/type'; import ChatHeader from '@/pageComponents/chat/ChatHeader'; import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext'; -import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants'; -import { getModelFromList } from '@fastgpt/global/core/ai/model'; -import MyPopover from '@fastgpt/web/components/common/MyPopover'; import { ChatSidebarPaneEnum } from '../constants'; +import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; +import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; type Props = { myApps: AppListItemType[]; @@ -77,9 +72,7 @@ const HomeChatWindow = ({ myApps }: Props) => { const { llmModelList, defaultModels, feConfigs } = useSystemStore(); const { chatId, appId, outLinkAuthData } = useChatStore(); - const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider); const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat); - const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle); const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); @@ -101,14 +94,6 @@ const HomeChatWindow = ({ myApps }: Props) => { const [selectedModel, setSelectedModel] = useLocalStorageState('chat_home_model', { defaultValue: defaultModels.llm?.model }); - const selectedModelAvatar = useMemo(() => { - const modelData = getModelFromList(llmModelList, selectedModel || ''); - return modelData?.avatar || HUGGING_FACE_ICON; - }, [selectedModel, llmModelList]); - const selectedModelButtonLabel = useMemo(() => { - const modelData = availableModels.find((model) => model.value === selectedModel); - return modelData?.label || selectedModel; - }, [selectedModel, availableModels]); const availableTools = useMemo( () => chatSettings?.selectedTools || [], @@ -129,6 +114,8 @@ const HomeChatWindow = ({ myApps }: Props) => { setSelectedToolIds( selectedToolIds.filter((id) => availableTools.some((tool) => tool.pluginId === id)) ); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [availableTools, chatSettings?.selectedTools]); // 初始化聊天数据 @@ -254,38 +241,33 @@ const HomeChatWindow = ({ myApps }: Props) => { <> {/* 模型选择 */} {availableModels.length > 0 && ( - - {isPc && } - {selectedModelButtonLabel} - - } - onChange={async (model) => { - setChatBoxData((state) => ({ - ...state, - app: { - ...state.app, - chatConfig: { - ...state.app.chatConfig, - fileSelectConfig: { - ...defaultFileSelectConfig, - canSelectImg: !!getWebLLMModel(model).vision + + { + setChatBoxData((state) => ({ + ...state, + app: { + ...state.app, + chatConfig: { + ...state.app.chatConfig, + fileSelectConfig: { + ...defaultFileSelectConfig, + canSelectImg: !!getWebLLMModel(model).vision + } } } - } - })); - setSelectedModel(model); - }} - /> + })); + setSelectedModel(model); + }} + /> + )} {/* 工具选择下拉框 */} @@ -299,6 +281,7 @@ const HomeChatWindow = ({ myApps }: Props) => { rounded="full" variant="whiteBase" leftIcon={} + flexShrink={0} _active={{ transform: 'none' }} @@ -361,9 +344,7 @@ const HomeChatWindow = ({ myApps }: Props) => { selectedToolIds, setSelectedToolIds, setChatBoxData, - isPc, - selectedModelAvatar, - selectedModelButtonLabel + isPc ] ); @@ -373,35 +354,20 @@ const HomeChatWindow = ({ myApps }: Props) => { {/* show history slider */} - {isPc || !appId ? ( + {isPc ? ( - ) : ( - - - - - - + )} {/* chat container */} diff --git a/projects/app/src/pageComponents/chat/SliderApps.tsx b/projects/app/src/pageComponents/chat/SliderApps.tsx index 2fc14fb30..592a2cc0c 100644 --- a/projects/app/src/pageComponents/chat/SliderApps.tsx +++ b/projects/app/src/pageComponents/chat/SliderApps.tsx @@ -397,6 +397,7 @@ const BottomSection = () => { display="flex" alignItems="center" justifyContent={'flex-start'} + maxW={isCollapsed ? 'fit-content' : 'calc(100% - 52px)'} > {isLoggedIn ? ( { flexGrow={1} fontSize={'sm'} fontWeight={500} - overflow="hidden" - whiteSpace="nowrap" - textOverflow="ellipsis" minW={0} > {username} diff --git a/projects/app/src/pageComponents/chat/UserAvatarPopover.tsx b/projects/app/src/pageComponents/chat/UserAvatarPopover.tsx index 4fe13d642..131bf3a2f 100644 --- a/projects/app/src/pageComponents/chat/UserAvatarPopover.tsx +++ b/projects/app/src/pageComponents/chat/UserAvatarPopover.tsx @@ -33,7 +33,11 @@ const UserAvatarPopover = ({ return ( <> {children}} + Trigger={ + + {children} + + } trigger="hover" placement={placement} w="160px" @@ -59,7 +63,9 @@ const UserAvatarPopover = ({ gap={2} > - {userInfo?.username ?? '-'} + + {userInfo?.username ?? '-'} + )} @@ -75,7 +81,7 @@ const UserAvatarPopover = ({ w="100%" > - {t('common:logout')} + {t('common:logout')} ); diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx new file mode 100644 index 000000000..4d4a20e2d --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; +import { useContextSelector } from 'use-context-selector'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import { Box, Flex } from '@chakra-ui/react'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import UserAvatarPopover from '@/pageComponents/chat/UserAvatarPopover'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; + +const ChatSliderFooter = () => { + const { userInfo } = useUserStore(); + const { feConfigs } = useSystemStore(); + + const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); + const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const pane = useContextSelector(ChatSettingContext, (v) => v.pane); + + const isSettingPane = pane === ChatSidebarPaneEnum.SETTING; + + return ( + + + + + + {userInfo?.username} + + + + + {feConfigs.isPlus && ( + { + handlePaneChange(ChatSidebarPaneEnum.SETTING); + onCloseSlider(); + }} + > + + + )} + + ); +}; + +export default ChatSliderFooter; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx new file mode 100644 index 000000000..e953e19e6 --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx @@ -0,0 +1,124 @@ +import { GridItem, Grid } from '@chakra-ui/react'; +import React from 'react'; +import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; +import { useContextSelector } from 'use-context-selector'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import { useChatStore } from '@/web/core/chat/context/useChatStore'; +import { useTranslation } from 'react-i18next'; +import { Box, Flex, Image } from '@chakra-ui/react'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; +import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; +import MyDivider from '@fastgpt/web/components/common/MyDivider'; +import { DEFAULT_LOGO_BANNER_URL } from '@/pageComponents/chat/constants'; + +type Props = { + title?: string; + banner?: string; +}; + +const ChatSliderHeader = ({ title, banner }: Props) => { + const { t } = useTranslation(); + const { isPc } = useSystem(); + const { setChatId } = useChatStore(); + + const pane = useContextSelector(ChatSettingContext, (v) => v.pane); + const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + + const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name); + const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar); + + const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); + + const isHomePane = pane === ChatSidebarPaneEnum.HOME; + const isTeamAppsPane = pane === ChatSidebarPaneEnum.TEAM_APPS; + + return isPc ? ( + + {!title && } + + + {title || appName} + + + ) : ( + <> + + banner + + + + + + { + handlePaneChange(ChatSidebarPaneEnum.HOME); + onCloseSlider(); + setChatId(); + }} + > + + + + {t('chat:sidebar.home')} + + + + + { + handlePaneChange(ChatSidebarPaneEnum.TEAM_APPS); + onCloseSlider(); + }} + > + + + + {t('chat:sidebar.team_apps')} + + + + + + + + ); +}; + +export default ChatSliderHeader; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderList.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderList.tsx new file mode 100644 index 000000000..b3a43a1f8 --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderList.tsx @@ -0,0 +1,193 @@ +import React from 'react'; +import { useContextSelector } from 'use-context-selector'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import { useChatStore } from '@/web/core/chat/context/useChatStore'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useEditTitle } from '@/web/common/hooks/useEditTitle'; +import { Box, Flex, IconButton } from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; +import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; +import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; + +const ChatSliderList = () => { + const { isPc } = useSystem(); + const { t } = useTranslation(); + + const { chatId: activeChatId } = useChatStore(); + + const histories = useContextSelector(ChatContext, (v) => v.histories); + const ScrollData = useContextSelector(ChatContext, (v) => v.ScrollData); + const onDelHistory = useContextSelector(ChatContext, (v) => v.onDelHistory); + const onUpdateHistory = useContextSelector(ChatContext, (v) => v.onUpdateHistory); + const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); + + const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); + + const concatHistory = useMemo(() => { + const formatHistories: { + id: string; + title: string; + customTitle?: string; + top?: boolean; + updateTime: Date; + }[] = histories.map((item) => { + return { + id: item.chatId, + title: item.title, + customTitle: item.customTitle, + top: item.top, + updateTime: item.updateTime + }; + }); + + const newChat: { + id: string; + title: string; + customTitle?: string; + top?: boolean; + updateTime: Date; + } = { + id: activeChatId, + title: t('common:core.chat.New Chat'), + updateTime: new Date() + }; + const activeChat = histories.find((item) => item.chatId === activeChatId); + + return !activeChat ? [newChat].concat(formatHistories) : formatHistories; + }, [activeChatId, histories, t]); + + // custom title edit + const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({ + title: t('common:core.chat.Custom History Title'), + placeholder: t('common:core.chat.Custom History Title Description') + }); + + return ( + <> + + {concatHistory.map((item, i) => ( + { + onChangeChatId(item.id); + setCiteModalData(undefined); + } + })} + {...(i !== concatHistory.length - 1 && { + mb: '8px' + })} + > + + + {item.customTitle || item.title} + + {!!item.id && ( + + + {t(formatTimeToChatTime(item.updateTime) as any).replace('#', ':')} + + + } + aria-label={''} + /> + } + menuList={[ + { + children: [ + { + label: item.top + ? t('common:core.chat.Unpin') + : t('common:core.chat.Pin'), + icon: 'core/chat/setTopLight', + onClick: () => { + onUpdateHistory({ + chatId: item.id, + top: !item.top + }); + } + }, + + { + label: t('common:custom_title'), + icon: 'common/customTitleLight', + onClick: () => { + onOpenModal({ + defaultVal: item.customTitle || item.title, + onSuccess: (e) => + onUpdateHistory({ + chatId: item.id, + customTitle: e + }) + }); + } + }, + { + label: t('common:Delete'), + icon: 'delete', + onClick: () => { + onDelHistory(item.id); + if (item.id === activeChatId) { + onChangeChatId(); + setCiteModalData(undefined); + } + }, + type: 'danger' + } + ] + } + ]} + /> + + + )} + + ))} + + + + + ); +}; + +export default ChatSliderList; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderMenu.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderMenu.tsx new file mode 100644 index 000000000..d808b999d --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderMenu.tsx @@ -0,0 +1,81 @@ +import { useContextSelector } from 'use-context-selector'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import { useTranslation } from 'react-i18next'; +import { Box, Button, Flex, IconButton } from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; +import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; +import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; + +type Props = { + menuConfirmButtonText?: string; +}; + +const ChatSliderMenu = ({ menuConfirmButtonText }: Props) => { + const { t } = useTranslation(); + const { isPc } = useSystem(); + + const histories = useContextSelector(ChatContext, (v) => v.histories); + const onClearHistory = useContextSelector(ChatContext, (v) => v.onClearHistories); + const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); + + const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); + + return ( + + {!isPc && ( + + + + {t('common:core.chat.History')} + + + )} + + + + {isPc && histories.length > 0 && ( + + } + /> + + } + type="delete" + content={menuConfirmButtonText || t('common:Delete')} + onConfirm={() => onClearHistory()} + /> + )} + + ); +}; + +export default ChatSliderMenu; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderMobileDrawer.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderMobileDrawer.tsx new file mode 100644 index 000000000..0c946f889 --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderMobileDrawer.tsx @@ -0,0 +1,64 @@ +import { Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react'; +import React from 'react'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import ChatSliderHeader from '@/pageComponents/chat/slider/ChatSliderHeader'; +import ChatSliderMenu from '@/pageComponents/chat/slider/ChatSliderMenu'; +import ChatSliderList from '@/pageComponents/chat/slider/ChatSliderList'; +import { useContextSelector } from 'use-context-selector'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import ChatSliderFooter from '@/pageComponents/chat/slider/ChatSliderFooter'; + +type Props = { + title?: string; + banner?: string; + menuConfirmButtonText?: string; + showHeader?: boolean; + showFooter?: boolean; +}; + +const ChatSliderMobileDrawer = ({ + title, + banner, + menuConfirmButtonText, + showHeader = false, + showFooter = false +}: Props) => { + const theme = useTheme(); + + const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider); + const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); + + return ( + + + + + + {showHeader && } + + + + + + {showFooter && } + + + + ); +}; + +export default ChatSliderMobileDrawer; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderSidebar.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderSidebar.tsx new file mode 100644 index 000000000..aec95ce3e --- /dev/null +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderSidebar.tsx @@ -0,0 +1,34 @@ +import { useTheme } from '@chakra-ui/react'; +import React from 'react'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import ChatSliderHeader from '@/pageComponents/chat/slider/ChatSliderHeader'; +import ChatSliderMenu from '@/pageComponents/chat/slider/ChatSliderMenu'; +import ChatSliderList from '@/pageComponents/chat/slider/ChatSliderList'; + +type Props = { + title?: string; + banner?: string; + menuConfirmButtonText?: string; +}; + +const ChatHistorySidebar = ({ title, banner, menuConfirmButtonText }: Props) => { + const theme = useTheme(); + + return ( + + + + + + ); +}; + +export default ChatHistorySidebar; diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index d2b5563b4..71e9f9691 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useRouter } from 'next/router'; -import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { streamFetch } from '@/web/common/api/fetch'; import SideBar from '@/components/SideBar'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; @@ -10,7 +10,6 @@ import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type import PageContainer from '@/components/PageContainer'; import ChatHeader from '@/pageComponents/chat/ChatHeader'; -import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider'; import { serviceSideProps } from '@/web/common/i18n/utils'; import { useTranslation } from 'next-i18next'; import { getInitOutLinkChatInfo } from '@/web/core/chat/api'; @@ -41,6 +40,8 @@ import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; +import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; +import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox')); @@ -220,11 +221,7 @@ const OutLink = (props: Props) => { const RenderHistoryList = useMemo(() => { const Children = ( - + ); if (showHistory !== '1') return null; @@ -232,20 +229,11 @@ const OutLink = (props: Props) => { return isPc ? ( {Children} ) : ( - - - - {Children} - - + ); - }, [isOpenSlider, isPc, onCloseSlider, datasetCiteData, showHistory, t]); + }, [isPc, datasetCiteData, showHistory, t]); return ( <> diff --git a/projects/app/src/web/core/chat/context/chatSettingContext.tsx b/projects/app/src/web/core/chat/context/chatSettingContext.tsx index e4ee67033..7b6493c0f 100644 --- a/projects/app/src/web/core/chat/context/chatSettingContext.tsx +++ b/projects/app/src/web/core/chat/context/chatSettingContext.tsx @@ -46,9 +46,9 @@ export const ChatSettingContextProvider = ({ children }: { children: React.React const { feConfigs } = useSystemStore(); const { appId, setLastPane, setLastChatAppId, lastPane } = useChatStore(); - const { pane = lastPane || ChatSidebarPaneEnum.HOME } = ( - pathname === '/chat/share' ? { pane: ChatSidebarPaneEnum.RECENTLY_USED_APPS } : router.query - ) as { pane: ChatSidebarPaneEnum }; + const { pane = lastPane || ChatSidebarPaneEnum.HOME } = router.query as { + pane: ChatSidebarPaneEnum; + }; const [collapse, setCollapse] = useState(defaultCollapseStatus);