diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 85171e4edb..312f8f60bd 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -5,7 +5,7 @@ import { Provider } from 'react-redux' import { HashRouter } from 'react-router-dom' import { PersistGate } from 'redux-persist/integration/react' -import TabsContainer from './components/Tabs/TabsContainer' +import AppLayout from './components/Layout/AppLayout' import TopViewContainer from './components/TopView' import AntdProvider from './context/AntdProvider' import { CodeStyleProvider } from './context/CodeStyleProvider' @@ -13,6 +13,7 @@ import { NotificationProvider } from './context/NotificationProvider' import StyleSheetManager from './context/StyleSheetManager' import { ThemeProvider } from './context/ThemeProvider' import NavigationHandler from './handler/NavigationHandler' +import { ChatProvider } from './hooks/useChat' import Routes from './Routes' function App(): React.ReactElement { @@ -24,14 +25,16 @@ function App(): React.ReactElement { - - + + - - - - - + + + + + + + diff --git a/src/renderer/src/Routes.tsx b/src/renderer/src/Routes.tsx index 489ac8c98f..02fe6aba3a 100644 --- a/src/renderer/src/Routes.tsx +++ b/src/renderer/src/Routes.tsx @@ -5,10 +5,8 @@ import AppsPage from './pages/apps/AppsPage' import FilesPage from './pages/files/FilesPage' import HomePage from './pages/home/HomePage' import KnowledgePage from './pages/knowledge/KnowledgePage' -import LaunchpadPage from './pages/launchpad/LaunchpadPage' import McpServersPage from './pages/mcp-servers' import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage' -import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' const RouteContainer = () => { @@ -22,8 +20,8 @@ const RouteContainer = () => { } /> } /> } /> - } /> - } /> + {/* } /> + } /> */} ) } diff --git a/src/renderer/src/assets/styles/variables.scss b/src/renderer/src/assets/styles/variables.scss index 371590ff8a..659758f570 100644 --- a/src/renderer/src/assets/styles/variables.scss +++ b/src/renderer/src/assets/styles/variables.scss @@ -12,7 +12,7 @@ --list-item-border-radius: 15px; --border-width: 0.5px; - --main-height: calc(100vh - var(--navbar-height) - 6px); + --main-height: 100vh; --border-width: 0.5px; } diff --git a/src/renderer/src/components/Layout/AppLayout.tsx b/src/renderer/src/components/Layout/AppLayout.tsx new file mode 100644 index 0000000000..1afb002114 --- /dev/null +++ b/src/renderer/src/components/Layout/AppLayout.tsx @@ -0,0 +1,27 @@ +import { HStack } from '@renderer/components/Layout' +import MainSidebar from '@renderer/pages/home/MainSidebar/MainSidebar' +import { FC } from 'react' +import styled from 'styled-components' + +interface AppLayoutProps { + children: React.ReactNode +} + +const AppLayout: FC = ({ children }) => { + return ( + + + {children} + + ) +} + +const ContentArea = styled.div` + min-width: 0; + display: flex; + flex: 1; + flex-direction: column; + height: 100vh; +` + +export default AppLayout diff --git a/src/renderer/src/components/Popups/SettingsPopup.tsx b/src/renderer/src/components/Popups/SettingsPopup.tsx new file mode 100644 index 0000000000..a4bd356999 --- /dev/null +++ b/src/renderer/src/components/Popups/SettingsPopup.tsx @@ -0,0 +1,230 @@ +import AboutSettings from '@renderer/pages/settings/AboutSettings' +import DataSettings from '@renderer/pages/settings/DataSettings/DataSettings' +import DisplaySettings from '@renderer/pages/settings/DisplaySettings/DisplaySettings' +import GeneralSettings from '@renderer/pages/settings/GeneralSettings' +import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings' +import ProvidersList from '@renderer/pages/settings/ProviderSettings' +import QuickAssistantSettings from '@renderer/pages/settings/QuickAssistantSettings' +import QuickPhraseSettings from '@renderer/pages/settings/QuickPhraseSettings' +import SelectionAssistantSettings from '@renderer/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings' +import ShortcutSettings from '@renderer/pages/settings/ShortcutSettings' +import WebSearchSettings from '@renderer/pages/settings/WebSearchSettings' +import { Modal, Spin } from 'antd' +import { + Cloud, + Command, + Globe, + HardDrive, + Info, + MonitorCog, + Package, + Rocket, + Settings2, + TextCursorInput, + Zap +} from 'lucide-react' +import React, { Suspense, useState } from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +import { TopView } from '../TopView' + +type SettingsTab = + | 'provider' + | 'model' + | 'web-search' + | 'general' + | 'display' + | 'shortcut' + | 'quickAssistant' + | 'selectionAssistant' + | 'data' + | 'about' + | 'quickPhrase' + +interface SettingsPopupShowParams { + defaultTab?: SettingsTab +} + +interface Props extends SettingsPopupShowParams { + resolve?: (value: any) => void +} + +const SettingsPopupContainer: React.FC = ({ defaultTab = 'provider', resolve }) => { + const { t } = useTranslation() + const [activeTab, setActiveTab] = useState(defaultTab) + const [open, setOpen] = useState(true) + + const menuItems = [ + { key: 'provider', icon: , label: t('settings.provider.title') }, + { key: 'model', icon: , label: t('settings.model') }, + { key: 'web-search', icon: , label: t('settings.websearch.title') }, + { key: 'general', icon: , label: t('settings.general') }, + { key: 'display', icon: , label: t('settings.display.title') }, + { key: 'shortcut', icon: , label: t('settings.shortcuts.title') }, + { key: 'quickAssistant', icon: , label: t('settings.quickAssistant.title') }, + { key: 'selectionAssistant', icon: , label: t('selection.name') }, + { key: 'quickPhrase', icon: , label: t('settings.quickPhrase.title') }, + { key: 'data', icon: , label: t('settings.data.title') }, + { key: 'about', icon: , label: t('settings.about') } + ] as const + + const renderContent = () => { + switch (activeTab) { + case 'provider': + return ( + }> + + + ) + case 'model': + return + case 'web-search': + return + case 'general': + return + case 'display': + return + case 'shortcut': + return + case 'quickAssistant': + return + case 'selectionAssistant': + return + case 'data': + return + case 'about': + return + case 'quickPhrase': + return + default: + return + } + } + + const onCancel = () => { + setOpen(false) + } + + const onAfterClose = () => { + resolve && resolve(null) + TopView.hide(TopViewKey) + } + + // 设置全局隐藏方法 + SettingsPopup.hide = onCancel + + return ( + + + + {menuItems.map((item) => ( + setActiveTab(item.key as SettingsTab)}> + {item.icon} + {item.label} + + ))} + + {renderContent()} + + + ) +} + +const TopViewKey = 'SettingsPopup' + +export default class SettingsPopup { + static hide() { + TopView.hide(TopViewKey) + } + + static show(props: SettingsPopupShowParams = {}) { + return new Promise((resolve) => { + TopView.show(, TopViewKey) + }) + } +} + +const StyledModal = styled(Modal)` + .ant-modal-content { + height: 80vh; + display: flex; + flex-direction: column; + } + + .ant-modal-body { + flex: 1; + padding: 0; + overflow: hidden; + } +` + +const ContentContainer = styled.div` + display: flex; + flex: 1; + height: 100%; +` + +const SettingMenus = styled.div` + display: flex; + flex-direction: column; + min-width: var(--settings-width); + border-right: 0.5px solid var(--color-border); + padding: 10px; + user-select: none; + background: var(--color-background); +` + +const MenuItem = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + padding: 6px 10px; + width: 100%; + cursor: pointer; + border-radius: var(--list-item-border-radius); + font-weight: 500; + transition: all 0.2s ease-in-out; + border: 0.5px solid transparent; + margin-bottom: 5px; + + .anticon { + font-size: 16px; + opacity: 0.8; + } + + .iconfont { + font-size: 18px; + line-height: 18px; + opacity: 0.7; + margin-left: -1px; + } + + &:hover { + background: var(--color-background-soft); + } + + &.active { + background: var(--color-background-soft); + border: 0.5px solid var(--color-border); + } +` + +const SettingContent = styled.div` + display: flex; + height: 100%; + flex: 1; + overflow: auto; +` diff --git a/src/renderer/src/handler/NavigationHandler.tsx b/src/renderer/src/handler/NavigationHandler.tsx index 14e4e18304..01dc8b2ea5 100644 --- a/src/renderer/src/handler/NavigationHandler.tsx +++ b/src/renderer/src/handler/NavigationHandler.tsx @@ -1,15 +1,12 @@ +import SettingsPopup from '@renderer/components/Popups/SettingsPopup' import NavigationService from '@renderer/services/NavigationService' -import { useAppDispatch, useAppSelector } from '@renderer/store' -import { addTab, Tab } from '@renderer/store/tabs' +import { useAppSelector } from '@renderer/store' import { useEffect } from 'react' import { useHotkeys } from 'react-hotkeys-hook' -import { useLocation, useNavigate } from 'react-router-dom' +import { useNavigate } from 'react-router-dom' const NavigationHandler: React.FC = () => { - const dispatch = useAppDispatch() - const location = useLocation() const navigate = useNavigate() - const tabs = useAppSelector((state) => state.tabs.tabs) const showSettingsShortcutEnabled = useAppSelector( (state) => state.shortcuts.shortcuts.find((s) => s.key === 'show_settings')?.enabled @@ -22,10 +19,7 @@ const NavigationHandler: React.FC = () => { useHotkeys( 'meta+, ! ctrl+,', function () { - if (location.pathname.startsWith('/settings')) { - return - } - navigate('/settings/provider') + SettingsPopup.show({ defaultTab: 'provider' }) }, { splitKey: '!', @@ -35,20 +29,6 @@ const NavigationHandler: React.FC = () => { } ) - // 初始化 home tab - useEffect(() => { - if (tabs.length === 0) { - const homeTab: Tab = { - id: 'home', - titleKey: 'title.home', - title: '', - path: '/', - iconType: 'home' - } - dispatch(addTab(homeTab)) - } - }, [dispatch, tabs.length]) - return null } diff --git a/src/renderer/src/hooks/useChat.tsx b/src/renderer/src/hooks/useChat.tsx index 0a2c80404b..dd322288a7 100644 --- a/src/renderer/src/hooks/useChat.tsx +++ b/src/renderer/src/hooks/useChat.tsx @@ -1,10 +1,9 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { useAppDispatch, useAppSelector } from '@renderer/store' import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' -import { Assistant } from '@renderer/types' -import { Topic } from '@renderer/types' -import { use, useEffect, useMemo, useState } from 'react' -import { createContext } from 'react' +import { Assistant, Topic } from '@renderer/types' +import { createContext, use, useCallback, useEffect, useMemo, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' import { useTopicsForAssistant } from './useAssistant' import { useSettings } from './useSettings' @@ -20,14 +19,25 @@ const ChatContext = createContext(null) export const ChatProvider = ({ children }) => { const assistants = useAppSelector((state) => state.assistants.assistants) - const [activeAssistant, setActiveAssistant] = useState(assistants[0]) + const [activeAssistant, setActiveAssistantBase] = useState(assistants[0]) const topics = useTopicsForAssistant(activeAssistant.id) const [activeTopic, setActiveTopic] = useState(topics[0]) const { clickAssistantToShowTopic } = useSettings() const dispatch = useAppDispatch() + const navigate = useNavigate() + const location = useLocation() - console.log('activeAssistant', activeAssistant) - console.log('activeTopic', activeTopic) + // 包装setActiveAssistant以添加导航逻辑 + const setActiveAssistant = useCallback( + (assistant: Assistant) => { + setActiveAssistantBase(assistant) + // 如果当前不在聊天页面,导航到聊天页面 + if (location.pathname !== '/') { + navigate('/') + } + }, + [setActiveAssistantBase, location.pathname, navigate] + ) // 当 topics 变化时,如果当前 activeTopic 不在 topics 中,设置第一个 topic useEffect(() => { @@ -58,7 +68,7 @@ export const ChatProvider = ({ children }) => { EventEmitter.on(EVENT_NAMES.SET_TOPIC, setActiveTopic) ] return () => subscriptions.forEach((subscription) => subscription()) - }, []) + }, [setActiveAssistant]) const value = useMemo( () => ({ @@ -67,7 +77,7 @@ export const ChatProvider = ({ children }) => { setActiveAssistant, setActiveTopic }), - [activeAssistant, activeTopic] + [activeAssistant, activeTopic, setActiveAssistant] ) return {children} diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx index e1c0f0b0cd..0fd04e50f9 100644 --- a/src/renderer/src/pages/files/FilesPage.tsx +++ b/src/renderer/src/pages/files/FilesPage.tsx @@ -134,17 +134,6 @@ const FilesPage: FC = () => { {t('files.title')} - - {menuItems.map((item) => ( - setFileType(item.key as FileTypes)} - /> - ))} - @@ -207,6 +196,17 @@ const FilesPage: FC = () => { )} + + {menuItems.map((item) => ( + setFileType(item.key as FileTypes)} + /> + ))} + ) diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 906c99ca6f..9e1d820ff6 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -122,7 +122,7 @@ const Chat: FC = () => { } const Main = styled(Flex)` - height: calc(100vh - var(--navbar-height) - 50px); + height: calc(100vh - var(--navbar-height)); transform: translateZ(0); position: relative; ` diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 527717c7a3..64252fd1b4 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,12 +1,9 @@ -import { HStack } from '@renderer/components/Layout' -import { ChatProvider } from '@renderer/hooks/useChat' import { useSettings } from '@renderer/hooks/useSettings' import { FC, useEffect } from 'react' import styled from 'styled-components' import Chat from './Chat' import ChatNavbar from './ChatNavbar' -import MainSidebar from './MainSidebar/MainSidebar' const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => { const { showAssistants, showTopics, topicPosition } = useSettings() @@ -20,17 +17,12 @@ const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => { }, [showAssistants, showTopics, topicPosition]) return ( - - - - - - - - - - - + + + + + + ) } diff --git a/src/renderer/src/pages/home/MainSidebar/MainSidebarStyles.tsx b/src/renderer/src/pages/home/MainSidebar/MainSidebarStyles.tsx index 1f0cdcd32f..6b42f4120f 100644 --- a/src/renderer/src/pages/home/MainSidebar/MainSidebarStyles.tsx +++ b/src/renderer/src/pages/home/MainSidebar/MainSidebarStyles.tsx @@ -56,6 +56,7 @@ export const Container = styled.div<{ transparent?: boolean }>` min-height: var(--main-height); background: var(--color-background); padding-top: 10px; + margin-top: 50px; ` export const MainMenu = styled.div` diff --git a/src/renderer/src/pages/knowledge/KnowledgePage.tsx b/src/renderer/src/pages/knowledge/KnowledgePage.tsx index 3e609e7cab..aa24e698ff 100644 --- a/src/renderer/src/pages/knowledge/KnowledgePage.tsx +++ b/src/renderer/src/pages/knowledge/KnowledgePage.tsx @@ -96,6 +96,13 @@ const KnowledgePage: FC = () => { {t('knowledge.title')} + {bases.length === 0 ? ( + + + + ) : selectedBase ? ( + + ) : null} {
- {bases.length === 0 ? ( - - - - ) : selectedBase ? ( - - ) : null}
)