diff --git a/src/app/chat/(desktop)/features/ChatHeader.tsx b/src/app/chat/(desktop)/features/ChatHeader.tsx deleted file mode 100644 index aed2842c..00000000 --- a/src/app/chat/(desktop)/features/ChatHeader.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { ActionIcon, Avatar, ChatHeader, ChatHeaderTitle } from '@lobehub/ui'; -import { Skeleton } from 'antd'; -import { PanelRightClose, PanelRightOpen } from 'lucide-react'; -import { useRouter } from 'next/navigation'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Flexbox } from 'react-layout-kit'; - -import ModelTag from '@/components/ModelTag'; -import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens'; -import { useGlobalStore } from '@/store/global'; -import { modelProviderSelectors } from '@/store/global/slices/settings/selectors'; -import { useSessionStore } from '@/store/session'; -import { agentSelectors, sessionSelectors } from '@/store/session/selectors'; -import { pathString } from '@/utils/url'; - -import PluginTag from '../../features/ChatHeader/PluginTag'; -import SettingButton from '../../features/ChatHeader/SettingButton'; -import ShareButton from '../../features/ChatHeader/ShareButton'; - -const Left = memo(() => { - const { t } = useTranslation('chat'); - - const router = useRouter(); - - const [init, isInbox, title, description, avatar, backgroundColor, model, plugins] = - useSessionStore((s) => [ - sessionSelectors.isSomeSessionActive(s), - sessionSelectors.isInboxSession(s), - agentSelectors.currentAgentTitle(s), - agentSelectors.currentAgentDescription(s), - agentSelectors.currentAgentAvatar(s), - agentSelectors.currentAgentBackgroundColor(s), - agentSelectors.currentAgentModel(s), - agentSelectors.currentAgentPlugins(s), - ]); - - const showPlugin = useGlobalStore(modelProviderSelectors.modelEnabledFunctionCall(model)); - const displayTitle = isInbox ? t('inbox.title') : title; - const displayDesc = isInbox ? t('inbox.desc') : description; - - return !init ? ( - - - - ) : ( - - - isInbox - ? router.push('/settings/agent') - : router.push(pathString('/chat/settings', { search: location.search })) - } - size={40} - title={title} - /> - - - {showPlugin && plugins?.length > 0 && } - - } - title={displayTitle} - /> - - ); -}); - -const Right = memo(() => { - const { t } = useTranslation('chat'); - - const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [ - s.preference.showChatSideBar, - s.toggleChatSideBar, - ]); - - return ( - <> - - toggleConfig()} - size={DESKTOP_HEADER_ICON_SIZE} - title={t('roleAndArchive')} - /> - - - ); -}); - -const Header = memo(() => } right={} />); - -export default Header; diff --git a/src/app/chat/(desktop)/features/ChatHeader/HeaderAction.tsx b/src/app/chat/(desktop)/features/ChatHeader/HeaderAction.tsx new file mode 100644 index 00000000..8cc94083 --- /dev/null +++ b/src/app/chat/(desktop)/features/ChatHeader/HeaderAction.tsx @@ -0,0 +1,34 @@ +import { ActionIcon } from '@lobehub/ui'; +import { PanelRightClose, PanelRightOpen } from 'lucide-react'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens'; +import { useGlobalStore } from '@/store/global'; + +import SettingButton from '../../../features/ChatHeader/SettingButton'; +import ShareButton from '../../../features/ChatHeader/ShareButton'; + +const HeaderAction = memo(() => { + const { t } = useTranslation('chat'); + + const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [ + s.preference.showChatSideBar, + s.toggleChatSideBar, + ]); + + return ( + <> + + toggleConfig()} + size={DESKTOP_HEADER_ICON_SIZE} + title={t('roleAndArchive')} + /> + + + ); +}); + +export default HeaderAction; diff --git a/src/app/chat/(desktop)/features/ChatHeader/Main.tsx b/src/app/chat/(desktop)/features/ChatHeader/Main.tsx new file mode 100644 index 00000000..48cd68e1 --- /dev/null +++ b/src/app/chat/(desktop)/features/ChatHeader/Main.tsx @@ -0,0 +1,58 @@ +import { Avatar, ChatHeaderTitle } from '@lobehub/ui'; +import { Skeleton } from 'antd'; +import { useRouter } from 'next/navigation'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; + +import { useSessionStore } from '@/store/session'; +import { agentSelectors, sessionSelectors } from '@/store/session/selectors'; +import { pathString } from '@/utils/url'; + +import Tags from './Tags'; + +const Main = memo(() => { + const { t } = useTranslation('chat'); + + const router = useRouter(); + + const [init, isInbox, title, description, avatar, backgroundColor] = useSessionStore((s) => [ + sessionSelectors.isSomeSessionActive(s), + sessionSelectors.isInboxSession(s), + agentSelectors.currentAgentTitle(s), + agentSelectors.currentAgentDescription(s), + agentSelectors.currentAgentAvatar(s), + agentSelectors.currentAgentBackgroundColor(s), + ]); + + const displayTitle = isInbox ? t('inbox.title') : title; + const displayDesc = isInbox ? t('inbox.desc') : description; + + return !init ? ( + + + + ) : ( + + + isInbox + ? router.push('/settings/agent') + : router.push(pathString('/chat/settings', { search: location.search })) + } + size={40} + title={title} + /> + } title={displayTitle} /> + + ); +}); + +export default Main; diff --git a/src/app/chat/(desktop)/features/ChatHeader/Tags.tsx b/src/app/chat/(desktop)/features/ChatHeader/Tags.tsx new file mode 100644 index 00000000..d5b79690 --- /dev/null +++ b/src/app/chat/(desktop)/features/ChatHeader/Tags.tsx @@ -0,0 +1,30 @@ +import { memo } from 'react'; + +import ModelTag from '@/components/ModelTag'; +import ModelSwitchPanel from '@/features/ModelSwitchPanel'; +import { useGlobalStore } from '@/store/global'; +import { modelProviderSelectors } from '@/store/global/selectors'; +import { useSessionStore } from '@/store/session'; +import { agentSelectors } from '@/store/session/selectors'; + +import PluginTag from '../../../features/PluginTag'; + +const TitleTags = memo(() => { + const [model, plugins] = useSessionStore((s) => [ + agentSelectors.currentAgentModel(s), + agentSelectors.currentAgentPlugins(s), + ]); + + const showPlugin = useGlobalStore(modelProviderSelectors.modelEnabledFunctionCall(model)); + + return ( + <> + + + + {showPlugin && plugins?.length > 0 && } + + ); +}); + +export default TitleTags; diff --git a/src/app/chat/(desktop)/features/ChatHeader/index.tsx b/src/app/chat/(desktop)/features/ChatHeader/index.tsx new file mode 100644 index 00000000..6ec17cea --- /dev/null +++ b/src/app/chat/(desktop)/features/ChatHeader/index.tsx @@ -0,0 +1,9 @@ +import { ChatHeader } from '@lobehub/ui'; +import { memo } from 'react'; + +import HeaderAction from './HeaderAction'; +import Main from './Main'; + +const Header = memo(() => } right={} />); + +export default Header; diff --git a/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx b/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx index 20f5edfb..2d231610 100644 --- a/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +++ b/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx @@ -9,7 +9,7 @@ import ChatList from '@/features/Conversation/components/ChatList'; import { useSessionStore } from '@/store/session'; import { agentSelectors, sessionSelectors } from '@/store/session/selectors'; -import PluginTag from '../../ChatHeader/PluginTag'; +import PluginTag from '../../PluginTag'; import { useStyles } from './style'; import { FieldType } from './type'; diff --git a/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx b/src/app/chat/features/PluginTag/PluginStatus.tsx similarity index 100% rename from src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx rename to src/app/chat/features/PluginTag/PluginStatus.tsx diff --git a/src/app/chat/features/ChatHeader/PluginTag/index.tsx b/src/app/chat/features/PluginTag/index.tsx similarity index 100% rename from src/app/chat/features/ChatHeader/PluginTag/index.tsx rename to src/app/chat/features/PluginTag/index.tsx diff --git a/src/features/ChatInput/ActionBar/ModelSwitch.tsx b/src/features/ChatInput/ActionBar/ModelSwitch.tsx index 4b3a7718..e23e2819 100644 --- a/src/features/ChatInput/ActionBar/ModelSwitch.tsx +++ b/src/features/ChatInput/ActionBar/ModelSwitch.tsx @@ -1,84 +1,17 @@ import { ActionIcon } from '@lobehub/ui'; -import { Dropdown } from 'antd'; -import { createStyles } from 'antd-style'; -import isEqual from 'fast-deep-equal'; import { BrainCog } from 'lucide-react'; -import { memo, useMemo } from 'react'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; -import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect'; -import { useGlobalStore } from '@/store/global'; -import { modelProviderSelectors } from '@/store/global/selectors'; -import { useSessionStore } from '@/store/session'; -import { agentSelectors } from '@/store/session/selectors'; -import { ModelProviderCard } from '@/types/llm'; - -const useStyles = createStyles(({ css, prefixCls }) => ({ - menu: css` - .${prefixCls}-dropdown-menu-item { - display: flex; - gap: 8px; - } - .${prefixCls}-dropdown-menu { - &-item-group-title { - padding-inline: 8px; - } - - &-item-group-list { - margin: 0 !important; - } - } - `, -})); +import ModelSwitchPanel from '@/features/ModelSwitchPanel'; const ModelSwitch = memo(() => { const { t } = useTranslation('chat'); - const { styles } = useStyles(); - const model = useSessionStore(agentSelectors.currentAgentModel); - const updateAgentConfig = useSessionStore((s) => s.updateAgentConfig); - - const select = useGlobalStore(modelProviderSelectors.modelSelectList, isEqual); - const enabledList = select.filter((s) => s.enabled); - - const items = useMemo(() => { - const getModelItems = (provider: ModelProviderCard) => - provider.chatModels - .filter((c) => !c.hidden) - .map((model) => ({ - key: model.id, - label: , - onClick: () => { - updateAgentConfig({ model: model.id, provider: provider.id }); - }, - })); - - if (enabledList.length === 1) { - const provider = enabledList[0]; - return getModelItems(provider); - } - return enabledList.map((provider) => ({ - children: getModelItems(provider), - key: provider.id, - label: , - type: 'group', - })); - }, [enabledList]); return ( - + - + ); }); diff --git a/src/features/ModelSwitchPanel/index.tsx b/src/features/ModelSwitchPanel/index.tsx new file mode 100644 index 00000000..7ae84bb9 --- /dev/null +++ b/src/features/ModelSwitchPanel/index.tsx @@ -0,0 +1,85 @@ +import { Dropdown } from 'antd'; +import { createStyles } from 'antd-style'; +import isEqual from 'fast-deep-equal'; +import { PropsWithChildren, memo, useMemo } from 'react'; + +import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect'; +import { useGlobalStore } from '@/store/global'; +import { modelProviderSelectors } from '@/store/global/selectors'; +import { useSessionStore } from '@/store/session'; +import { agentSelectors } from '@/store/session/selectors'; +import { ModelProviderCard } from '@/types/llm'; + +const useStyles = createStyles(({ css, prefixCls }) => ({ + menu: css` + .${prefixCls}-dropdown-menu-item { + display: flex; + gap: 8px; + } + .${prefixCls}-dropdown-menu { + &-item-group-title { + padding-inline: 8px; + } + + &-item-group-list { + margin: 0 !important; + } + } + `, + tag: css` + cursor: pointer; + `, +})); + +const ModelSwitchPanel = memo(({ children }) => { + const { styles } = useStyles(); + const model = useSessionStore(agentSelectors.currentAgentModel); + const updateAgentConfig = useSessionStore((s) => s.updateAgentConfig); + + const select = useGlobalStore(modelProviderSelectors.modelSelectList, isEqual); + const enabledList = select.filter((s) => s.enabled); + + const items = useMemo(() => { + const getModelItems = (provider: ModelProviderCard) => + provider.chatModels + .filter((c) => !c.hidden) + .map((model) => ({ + key: model.id, + label: , + onClick: () => { + updateAgentConfig({ model: model.id, provider: provider.id }); + }, + })); + + if (enabledList.length === 1) { + const provider = enabledList[0]; + return getModelItems(provider); + } + + return enabledList.map((provider) => ({ + children: getModelItems(provider), + key: provider.id, + label: , + type: 'group', + })); + }, [enabledList]); + + return ( + +
{children}
+
+ ); +}); + +export default ModelSwitchPanel;