Compare commits
2 Commits
feat/backu
...
feat/setti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c66f0e41a | ||
|
|
fd1629e004 |
@@ -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 {
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</TopViewContainer>
|
||||
|
||||
66
src/renderer/src/components/Popups/SettingsPopup.tsx
Normal file
66
src/renderer/src/components/Popups/SettingsPopup.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
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<Props> = (props) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab | undefined>(props.activeTab)
|
||||
|
||||
const onOpen = () => {
|
||||
if (props.activeTab) {
|
||||
setActiveTab(props.activeTab)
|
||||
}
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div onClick={onOpen}>{props.actionButton}</div>
|
||||
<GlobalStyle />
|
||||
<StyledModal
|
||||
transitionName="ant-move-down"
|
||||
width="80vw"
|
||||
title={null}
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
footer={null}>
|
||||
<SettingsPage activeTab={activeTab} onTabChange={setActiveTab} />
|
||||
</StyledModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
.ant-modal-close {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
`
|
||||
|
||||
export default SettingsPopup
|
||||
@@ -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 (
|
||||
<Container
|
||||
id="app-sidebar"
|
||||
@@ -73,13 +67,15 @@ const Sidebar: FC = () => {
|
||||
)}
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('settings.title')} mouseEnterDelay={0.8} placement="right">
|
||||
<StyledLink onClick={() => to(isLocalAi ? '/settings/assistant' : '/settings/provider')}>
|
||||
<Icon className={pathname.startsWith('/settings') ? 'active' : ''}>
|
||||
<i className="iconfont icon-setting" />
|
||||
</Icon>
|
||||
</StyledLink>
|
||||
</Tooltip>
|
||||
<SettingsPopup
|
||||
actionButton={
|
||||
<Tooltip title={t('settings.title')} mouseEnterDelay={0.8} placement="right">
|
||||
<Icon>
|
||||
<i className="iconfont icon-setting" />
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</Menus>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -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,145 @@ 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 (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('settings.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<SettingMenus>
|
||||
{!isLocalAi && (
|
||||
<>
|
||||
<MenuItemLink to="/settings/provider">
|
||||
<MenuItem className={isRoute('/settings/provider')}>
|
||||
<CloudOutlined />
|
||||
{t('settings.provider.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/model">
|
||||
<MenuItem className={isRoute('/settings/model')}>
|
||||
<i className="iconfont icon-ai-model" />
|
||||
{t('settings.model')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
</>
|
||||
)}
|
||||
<MenuItemLink to="/settings/general">
|
||||
<MenuItem className={isRoute('/settings/general')}>
|
||||
<SettingOutlined />
|
||||
{t('settings.general')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/display">
|
||||
<MenuItem className={isRoute('/settings/display')}>
|
||||
<LayoutOutlined />
|
||||
{t('settings.display.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/shortcut">
|
||||
<MenuItem className={isRoute('/settings/shortcut')}>
|
||||
<MacCommandOutlined />
|
||||
{t('settings.shortcuts.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/quickAssistant">
|
||||
<MenuItem className={isRoute('/settings/quickAssistant')}>
|
||||
<RocketOutlined />
|
||||
{t('settings.quickAssistant.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/data">
|
||||
<MenuItem className={isRoute('/settings/data')}>
|
||||
<SaveOutlined />
|
||||
{t('settings.data.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/about">
|
||||
<MenuItem className={isRoute('/settings/about')}>
|
||||
<InfoCircleOutlined />
|
||||
{t('settings.about')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
</SettingMenus>
|
||||
<SettingContent>
|
||||
<Routes>
|
||||
<Route path="provider" element={<ProvidersList />} />
|
||||
<Route path="model" element={<ModelSettings />} />
|
||||
<Route path="general/*" element={<GeneralSettings />} />
|
||||
<Route path="display" element={<DisplaySettings />} />
|
||||
<Route path="data/*" element={<DataSettings />} />
|
||||
<Route path="quickAssistant" element={<QuickAssistantSettings />} />
|
||||
<Route path="shortcut" element={<ShortcutSettings />} />
|
||||
<Route path="about" element={<AboutSettings />} />
|
||||
</Routes>
|
||||
</SettingContent>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
interface Props {
|
||||
activeTab?: SettingsTab
|
||||
onTabChange?: (tab: SettingsTab) => void
|
||||
}
|
||||
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> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
const activeTab = props.activeTab || 'provider'
|
||||
|
||||
const settingMenus = useMemo<MenuItem[]>(
|
||||
() => [
|
||||
{
|
||||
label: t('settings.provider.title'),
|
||||
icon: <CloudOutlined />,
|
||||
key: 'provider',
|
||||
enabled: !isLocalAi
|
||||
},
|
||||
{
|
||||
label: t('settings.model'),
|
||||
icon: <i className="iconfont icon-ai-model" />,
|
||||
key: 'model',
|
||||
enabled: !isLocalAi
|
||||
},
|
||||
{
|
||||
label: t('settings.general'),
|
||||
icon: <SettingOutlined />,
|
||||
key: 'general',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
label: t('settings.display.title'),
|
||||
icon: <LayoutOutlined />,
|
||||
key: 'display',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
label: t('settings.shortcuts.title'),
|
||||
icon: <MacCommandOutlined />,
|
||||
key: 'shortcut',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
label: t('settings.quickAssistant.title'),
|
||||
icon: <RocketOutlined />,
|
||||
key: 'quickAssistant',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
label: t('settings.data.title'),
|
||||
icon: <SaveOutlined />,
|
||||
key: 'data',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
label: t('settings.about'),
|
||||
icon: <InfoCircleOutlined />,
|
||||
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 <ProvidersList />
|
||||
case 'model':
|
||||
return <ModelSettings />
|
||||
case 'general':
|
||||
return <GeneralSettings />
|
||||
case 'display':
|
||||
return <DisplaySettings />
|
||||
case 'data':
|
||||
return <DataSettings />
|
||||
case 'quickAssistant':
|
||||
return <QuickAssistantSettings />
|
||||
case 'shortcut':
|
||||
return <ShortcutSettings />
|
||||
case 'about':
|
||||
return <AboutSettings />
|
||||
default:
|
||||
return <GeneralSettings />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentContainer>
|
||||
<MenuContainer $isCollapsed={collapsed}>
|
||||
<Title>{t('settings.title')}</Title>
|
||||
<Menu
|
||||
mode="inline"
|
||||
onClick={(e) => props.onTabChange?.(e.key as SettingsTab)}
|
||||
selectedKeys={[activeTab]}
|
||||
items={settingMenus.filter((item) => item.enabled)}
|
||||
inlineCollapsed={collapsed}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<SettingContent>
|
||||
<SettingHeader>
|
||||
<CollapseButton shape="circle" type="text" onClick={() => setCollapsed(!collapsed)} $isCollapsed={collapsed}>
|
||||
<i className="iconfont icon-hide-sidebar" />
|
||||
</CollapseButton>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
</SettingHeader>
|
||||
{renderContent()}
|
||||
</SettingContent>
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -118,57 +168,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
|
||||
|
||||
@@ -7,12 +7,11 @@ 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)')};
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
||||
@@ -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 (
|
||||
<Link to="/settings/model" style={{ color: 'var(--color-text-2)' }}>
|
||||
<SettingOutlined />
|
||||
</Link>
|
||||
<SettingsPopup
|
||||
activeTab="model"
|
||||
actionButton={<Button type="text" shape="circle" icon={<SettingOutlined />} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user