diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 11f1517104..bd7d27b449 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -16,7 +16,6 @@ import FilesPage from './pages/files/FilesPage' import HomePage from './pages/home/HomePage' import KnowledgePage from './pages/knowledge/KnowledgePage' import PaintingsPage from './pages/paintings/PaintingsPage' -import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' function App(): JSX.Element { @@ -37,7 +36,6 @@ function App(): JSX.Element { } /> } /> } /> - } /> diff --git a/src/renderer/src/components/Popups/SettingsPopup.tsx b/src/renderer/src/components/Popups/SettingsPopup.tsx new file mode 100644 index 0000000000..0d4f194bb3 --- /dev/null +++ b/src/renderer/src/components/Popups/SettingsPopup.tsx @@ -0,0 +1,59 @@ +import SettingsPage, { SettingsTab } from '@renderer/pages/settings/SettingsPage' +import { Modal } from 'antd' +import { FC, useState } from 'react' +import styled, { createGlobalStyle } from 'styled-components' + +interface Props { + actionButton?: React.ReactNode + activeTab?: SettingsTab +} + +const SettingsPopup: FC = ({ actionButton, activeTab }) => { + const [open, setOpen] = useState(false) + + const onCancel = () => { + setOpen(false) + } + + return ( + <> +
setOpen(true)}>{actionButton}
+ + + + + + ) +} + +const GlobalStyle = createGlobalStyle` + .ant-modal-mask { + backdrop-filter: blur(10px); + background-color: transparent !important; + } +` + +const StyledModal = styled(Modal)` + min-width: 900px; + max-width: 1300px; + padding-bottom: 0; + + .ant-modal-content { + padding: 0; + overflow: hidden; + border-radius: 12px; + /* border: 0.5px solid var(--color-border); */ + } + .ant-modal-close { + top: 4px; + right: 4px; + } +` + +export default SettingsPopup diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index bc337098e0..beabd6ee7b 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -1,10 +1,10 @@ import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons' import { isMac } from '@renderer/config/constant' -import { isLocalAi, UserAvatar } from '@renderer/config/env' +import { UserAvatar } from '@renderer/config/env' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' import { useMinapps } from '@renderer/hooks/useMinapps' -import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' +import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import type { MenuProps } from 'antd' import { Tooltip } from 'antd' @@ -18,14 +18,13 @@ import styled from 'styled-components' import DragableList from '../DragableList' import MinAppIcon from '../Icons/MinAppIcon' import MinApp from '../MinApp' +import SettingsPopup from '../Popups/SettingsPopup' import UserPopup from '../Popups/UserPopup' const Sidebar: FC = () => { - const { pathname } = useLocation() const avatar = useAvatar() const { minappShow } = useRuntime() const { t } = useTranslation() - const navigate = useNavigate() const { windowStyle, sidebarIcons } = useSettings() const { theme, toggleTheme } = useTheme() const { pinned } = useMinapps() @@ -37,11 +36,6 @@ const Sidebar: FC = () => { const showPinnedApps = pinned.length > 0 && sidebarIcons.visible.includes('minapp') - const to = async (path: string) => { - await modelGenerating() - navigate(path) - } - return ( { )} - - to(isLocalAi ? '/settings/assistant' : '/settings/provider')}> - - - - - + + + + + + } + /> ) diff --git a/src/renderer/src/pages/settings/ProviderSettings/index.tsx b/src/renderer/src/pages/settings/ProviderSettings/index.tsx index 6e6efef98c..88620ac024 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/index.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/index.tsx @@ -164,7 +164,7 @@ const ProviderListContainer = styled.div` display: flex; flex-direction: column; min-width: calc(var(--settings-width) + 10px); - height: calc(100vh - var(--navbar-height)); + height: calc(75vh - var(--navbar-height)); border-right: 0.5px solid var(--color-border); ` @@ -180,19 +180,18 @@ const ProviderListItem = styled.div` display: flex; flex-direction: row; align-items: center; - padding: 5px 10px; + padding: 8px 8px; width: 100%; cursor: grab; - border-radius: var(--list-item-border-radius); + border-radius: 8px; font-size: 14px; transition: all 0.2s ease-in-out; - border: 0.5px solid transparent; &:hover { - background: var(--color-background-soft); + background: var(--color-primary-mute); } &.active { - background: var(--color-background-soft); - border: 0.5px solid var(--color-border); + background: var(--color-primary-mute); + color: var(--color-primary); font-weight: bold !important; } ` diff --git a/src/renderer/src/pages/settings/SettingsPage.tsx b/src/renderer/src/pages/settings/SettingsPage.tsx index a091ad707b..9e517250e6 100644 --- a/src/renderer/src/pages/settings/SettingsPage.tsx +++ b/src/renderer/src/pages/settings/SettingsPage.tsx @@ -7,11 +7,10 @@ import { SaveOutlined, SettingOutlined } from '@ant-design/icons' -import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { isLocalAi } from '@renderer/config/env' -import { FC } from 'react' +import { Breadcrumb, Button, Menu } from 'antd' +import { FC, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Link, Route, Routes, useLocation } from 'react-router-dom' import styled from 'styled-components' import AboutSettings from './AboutSettings' @@ -23,94 +22,143 @@ import ProvidersList from './ProviderSettings' import QuickAssistantSettings from './QuickAssistantSettings' import ShortcutSettings from './ShortcutSettings' -const SettingsPage: FC = () => { - const { pathname } = useLocation() - const { t } = useTranslation() +export type SettingsTab = + | 'provider' + | 'model' + | 'general' + | 'display' + | 'data' + | 'quickAssistant' + | 'shortcut' + | 'about' - const isRoute = (path: string): string => (pathname.startsWith(path) ? 'active' : '') - - return ( - - - {t('settings.title')} - - - - {!isLocalAi && ( - <> - - - - {t('settings.provider.title')} - - - - - - {t('settings.model')} - - - - )} - - - - {t('settings.general')} - - - - - - {t('settings.display.title')} - - - - - - {t('settings.shortcuts.title')} - - - - - - {t('settings.quickAssistant.title')} - - - - - - {t('settings.data.title')} - - - - - - {t('settings.about')} - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - ) +interface Props { + activeTab?: SettingsTab +} +interface MenuItem { + label: string + icon: React.ReactNode + key: string + enabled: boolean } -const Container = styled.div` - display: flex; - flex-direction: column; - flex: 1; -` +const SettingsPage: FC = (props) => { + const { t } = useTranslation() + const [activeTab, setActiveTab] = useState(props.activeTab || 'provider') + const [collapsed, setCollapsed] = useState(false) + + const settingMenus = useMemo( + () => [ + { + label: t('settings.provider.title'), + icon: , + key: 'provider', + enabled: !isLocalAi + }, + { + label: t('settings.model'), + icon: , + key: 'model', + enabled: !isLocalAi + }, + { + label: t('settings.general'), + icon: , + key: 'general', + enabled: true + }, + { + label: t('settings.display.title'), + icon: , + key: 'display', + enabled: true + }, + { + label: t('settings.shortcuts.title'), + icon: , + key: 'shortcut', + enabled: true + }, + { + label: t('settings.quickAssistant.title'), + icon: , + key: 'quickAssistant', + enabled: true + }, + { + label: t('settings.data.title'), + icon: , + key: 'data', + enabled: true + }, + { + label: t('settings.about'), + icon: , + key: 'about', + enabled: true + } + ], + [t] + ) + + const breadcrumbItems = useMemo(() => { + return [ + { + title: t('settings.title') + }, + { + title: settingMenus.find((item) => item.key === activeTab)?.label + } + ] + }, [t, activeTab, settingMenus]) + + const renderContent = () => { + switch (activeTab) { + case 'provider': + return + case 'model': + return + case 'general': + return + case 'display': + return + case 'data': + return + case 'quickAssistant': + return + case 'shortcut': + return + case 'about': + return + default: + return + } + } + + return ( + + + {t('settings.title')} + setActiveTab(e.key)} + selectedKeys={[activeTab]} + items={settingMenus.filter((item) => item.enabled)} + inlineCollapsed={collapsed} + /> + + + + setCollapsed(!collapsed)} $isCollapsed={collapsed}> + + + + + {renderContent()} + + + ) +} const ContentContainer = styled.div` display: flex; @@ -118,57 +166,40 @@ const ContentContainer = styled.div` flex-direction: row; ` -const SettingMenus = styled.ul` - display: flex; - flex-direction: column; - min-width: var(--settings-width); - border-right: 0.5px solid var(--color-border); - padding: 10px; - user-select: none; -` - -const MenuItemLink = styled(Link)` - text-decoration: none; - color: var(--color-text-1); - margin-bottom: 5px; -` - -const MenuItem = styled.li` - 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; - .anticon { - font-size: 16px; - opacity: 0.8; +const MenuContainer = styled.div<{ $isCollapsed: boolean }>` + width: ${({ $isCollapsed }) => ($isCollapsed ? '80px' : '160px')}; + background-color: var(--color-background-mute); + transition: width 0.3s ease-in-out; + position: relative; + .ant-menu-light { + background-color: var(--color-background-mute); } +` + +const CollapseButton = styled(Button)<{ $isCollapsed: boolean }>` + color: var(--color-icon); .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); + transform: rotate(${({ $isCollapsed }) => ($isCollapsed ? '180deg' : '0deg')}); } ` +const Title = styled.div` + font-size: 16px; + font-weight: 600; + padding: 16px 24px; +` + const SettingContent = styled.div` - display: flex; height: 100%; flex: 1; - border-right: 0.5px solid var(--color-border); +` + +const SettingHeader = styled.div` + padding: 4px 8px; + border-bottom: 0.5px solid var(--color-border); + display: flex; + align-items: center; + gap: 8px; ` export default SettingsPage diff --git a/src/renderer/src/pages/settings/index.tsx b/src/renderer/src/pages/settings/index.tsx index ab22005242..ce454a776a 100644 --- a/src/renderer/src/pages/settings/index.tsx +++ b/src/renderer/src/pages/settings/index.tsx @@ -7,12 +7,12 @@ export const SettingContainer = styled.div<{ theme?: ThemeMode }>` display: flex; flex-direction: column; flex: 1; - height: calc(100vh - var(--navbar-height)); - padding: 20px; + height: calc(75vh - var(--navbar-height)); + padding: 16px; padding-top: 15px; overflow-y: scroll; font-family: Ubuntu; - background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')}; + /* background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')}; */ &::-webkit-scrollbar { display: none; diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index e7e9be3b3a..cfdd618583 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -1,6 +1,7 @@ import { CheckOutlined, SendOutlined, SettingOutlined, SwapOutlined, WarningOutlined } from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import CopyIcon from '@renderer/components/Icons/CopyIcon' +import SettingsPopup from '@renderer/components/Popups/SettingsPopup' import { isLocalAi } from '@renderer/config/env' import { TranslateLanguageOptions } from '@renderer/config/translate' import db from '@renderer/databases' @@ -90,9 +91,10 @@ const TranslatePage: FC = () => { if (translateModel) { return ( - - - + } />} + /> ) }