feature: customizable sidebar module #644

close #644
This commit is contained in:
kangfenmao 2025-01-06 16:59:10 +08:00
parent ac9017c031
commit d0948e6f8a
14 changed files with 124 additions and 56 deletions

View File

@ -21,7 +21,8 @@ const Sidebar: FC = () => {
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { t } = useTranslation() const { t } = useTranslation()
const navigate = useNavigate() const navigate = useNavigate()
const { windowStyle, showMinappIcon, showFilesIcon } = useSettings() const { windowStyle, showTranslateIcon, showPaintingIcon, showMinappIcon, showKnowledgeIcon, showFilesIcon } =
useSettings()
const { theme, toggleTheme } = useTheme() const { theme, toggleTheme } = useTheme()
const isRoute = (path: string): string => (pathname === path ? 'active' : '') const isRoute = (path: string): string => (pathname === path ? 'active' : '')
@ -61,6 +62,7 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</StyledLink> </StyledLink>
</Tooltip> </Tooltip>
{showPaintingIcon && (
<Tooltip title={t('paintings.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip title={t('paintings.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/paintings')}> <StyledLink onClick={() => to('/paintings')}>
<Icon className={isRoute('/paintings')}> <Icon className={isRoute('/paintings')}>
@ -68,6 +70,8 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</StyledLink> </StyledLink>
</Tooltip> </Tooltip>
)}
{showTranslateIcon && (
<Tooltip title={t('translate.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip title={t('translate.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/translate')}> <StyledLink onClick={() => to('/translate')}>
<Icon className={isRoute('/translate')}> <Icon className={isRoute('/translate')}>
@ -75,6 +79,7 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</StyledLink> </StyledLink>
</Tooltip> </Tooltip>
)}
{showMinappIcon && ( {showMinappIcon && (
<Tooltip title={t('minapp.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip title={t('minapp.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/apps')}> <StyledLink onClick={() => to('/apps')}>
@ -84,6 +89,7 @@ const Sidebar: FC = () => {
</StyledLink> </StyledLink>
</Tooltip> </Tooltip>
)} )}
{showKnowledgeIcon && (
<Tooltip title={t('knowledge_base.title')} mouseEnterDelay={0.5} placement="right"> <Tooltip title={t('knowledge_base.title')} mouseEnterDelay={0.5} placement="right">
<StyledLink onClick={() => to('/knowledge')}> <StyledLink onClick={() => to('/knowledge')}>
<Icon className={isRoute('/knowledge')}> <Icon className={isRoute('/knowledge')}>
@ -91,6 +97,7 @@ const Sidebar: FC = () => {
</Icon> </Icon>
</StyledLink> </StyledLink>
</Tooltip> </Tooltip>
)}
{showFilesIcon && ( {showFilesIcon && (
<Tooltip title={t('files.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip title={t('files.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/files')}> <StyledLink onClick={() => to('/files')}>

View File

@ -2,7 +2,6 @@ import { isMac } from '@renderer/config/constant'
import { isLocalAi } from '@renderer/config/env' import { isLocalAi } from '@renderer/config/env'
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime' import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime'
import { delay, runAsyncFunction } from '@renderer/utils' import { delay, runAsyncFunction } from '@renderer/utils'
@ -16,16 +15,7 @@ import useUpdateHandler from './useUpdateHandler'
export function useAppInit() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { const { proxyUrl, language, windowStyle, manualUpdateCheck, proxyMode, customCss } = useSettings()
proxyUrl,
language,
windowStyle,
manualUpdateCheck,
proxyMode,
webdavAutoSync,
webdavSyncInterval,
customCss
} = useSettings()
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar')) const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
@ -84,10 +74,6 @@ export function useAppInit() {
}) })
}, [dispatch]) }, [dispatch])
useEffect(() => {
webdavAutoSync ? startAutoSync() : stopAutoSync()
}, [webdavAutoSync, webdavSyncInterval])
useEffect(() => { useEffect(() => {
import('@renderer/queue/KnowledgeQueue') import('@renderer/queue/KnowledgeQueue')
}, []) }, [])

View File

@ -394,7 +394,10 @@
"general.user_name.placeholder": "Enter your name", "general.user_name.placeholder": "Enter your name",
"general.view_webdav_settings": "View WebDAV settings", "general.view_webdav_settings": "View WebDAV settings",
"general.display.title": "Display Settings", "general.display.title": "Display Settings",
"display.sidebar.translate.icon": "Show Translate icon",
"display.sidebar.painting.icon": "Show Painting icon",
"display.sidebar.minapp.icon": "Show MinApp icon", "display.sidebar.minapp.icon": "Show MinApp icon",
"display.sidebar.knowledge.icon": "Show Knowledge icon",
"display.sidebar.files.icon": "Show Files icon", "display.sidebar.files.icon": "Show Files icon",
"display.sidebar.title": "Sidebar Settings", "display.sidebar.title": "Sidebar Settings",
"display.topic.title": "Topic Settings", "display.topic.title": "Topic Settings",

View File

@ -392,7 +392,10 @@
"general.user_name.placeholder": "ユーザー名を入力", "general.user_name.placeholder": "ユーザー名を入力",
"general.view_webdav_settings": "WebDAV設定を表示", "general.view_webdav_settings": "WebDAV設定を表示",
"general.display.title": "表示設定", "general.display.title": "表示設定",
"display.sidebar.translate.icon": "翻訳のアイコンを表示",
"display.sidebar.painting.icon": "絵画のアイコンを表示",
"display.sidebar.minapp.icon": "ミニアプリのアイコンを表示", "display.sidebar.minapp.icon": "ミニアプリのアイコンを表示",
"display.sidebar.knowledge.icon": "ナレッジのアイコンを表示",
"display.sidebar.files.icon": "ファイルのアイコンを表示", "display.sidebar.files.icon": "ファイルのアイコンを表示",
"display.sidebar.title": "サイドバー設定", "display.sidebar.title": "サイドバー設定",
"display.topic.title": "トピック設定", "display.topic.title": "トピック設定",

View File

@ -394,7 +394,10 @@
"general.user_name.placeholder": "Введите ваше имя", "general.user_name.placeholder": "Введите ваше имя",
"general.view_webdav_settings": "Просмотр настроек WebDAV", "general.view_webdav_settings": "Просмотр настроек WebDAV",
"general.display.title": "Настройки отображения", "general.display.title": "Настройки отображения",
"display.sidebar.translate.icon": "Показывать иконку перевода",
"display.sidebar.painting.icon": "Показывать иконку рисования",
"display.sidebar.minapp.icon": "Показывать иконку мини-приложения", "display.sidebar.minapp.icon": "Показывать иконку мини-приложения",
"display.sidebar.knowledge.icon": "Показывать иконку знаний",
"display.sidebar.files.icon": "Показывать иконку файлов", "display.sidebar.files.icon": "Показывать иконку файлов",
"display.sidebar.title": "Настройки боковой панели", "display.sidebar.title": "Настройки боковой панели",
"display.topic.title": "Настройки топиков", "display.topic.title": "Настройки топиков",

View File

@ -395,7 +395,10 @@
"general.user_name.placeholder": "请输入用户名", "general.user_name.placeholder": "请输入用户名",
"general.view_webdav_settings": "查看 WebDAV 设置", "general.view_webdav_settings": "查看 WebDAV 设置",
"general.display.title": "显示设置", "general.display.title": "显示设置",
"display.sidebar.translate.icon": "显示翻译图标",
"display.sidebar.painting.icon": "显示绘画图标",
"display.sidebar.minapp.icon": "显示小程序图标", "display.sidebar.minapp.icon": "显示小程序图标",
"display.sidebar.knowledge.icon": "显示知识图标",
"display.sidebar.files.icon": "显示文件图标", "display.sidebar.files.icon": "显示文件图标",
"display.sidebar.title": "侧边栏设置", "display.sidebar.title": "侧边栏设置",
"display.topic.title": "话题设置", "display.topic.title": "话题设置",

View File

@ -394,7 +394,10 @@
"general.user_name.placeholder": "輸入您的名稱", "general.user_name.placeholder": "輸入您的名稱",
"general.view_webdav_settings": "查看 WebDAV 設定", "general.view_webdav_settings": "查看 WebDAV 設定",
"general.display.title": "顯示設定", "general.display.title": "顯示設定",
"display.sidebar.translate.icon": "顯示翻譯圖示",
"display.sidebar.painting.icon": "顯示繪圖圖示",
"display.sidebar.minapp.icon": "顯示小程序圖示", "display.sidebar.minapp.icon": "顯示小程序圖示",
"display.sidebar.knowledge.icon": "顯示知識圖示",
"display.sidebar.files.icon": "顯示文件圖示", "display.sidebar.files.icon": "顯示文件圖示",
"display.sidebar.title": "側邊欄設定", "display.sidebar.title": "側邊欄設定",
"display.topic.title": "話題設定", "display.topic.title": "話題設定",

View File

@ -1,8 +1,19 @@
import KeyvStorage from '@kangfenmao/keyv-storage' import KeyvStorage from '@kangfenmao/keyv-storage'
function init() { import { startAutoSync } from './services/BackupService'
import store from './store'
function initKeyv() {
window.keyv = new KeyvStorage() window.keyv = new KeyvStorage()
window.keyv.init() window.keyv.init()
} }
init() function initAutoSync() {
const { webdavAutoSync } = store.getState().settings
if (webdavAutoSync) {
startAutoSync()
}
}
initKeyv()
initAutoSync()

View File

@ -62,7 +62,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
showInputEstimatedTokens, showInputEstimatedTokens,
clickAssistantToShowTopic, clickAssistantToShowTopic,
language, language,
autoTranslateWithSpace autoTranslateWithSpace,
showKnowledgeIcon
} = useSettings() } = useSettings()
const [expended, setExpend] = useState(false) const [expended, setExpend] = useState(false)
const [estimateTokenCount, setEstimateTokenCount] = useState(0) const [estimateTokenCount, setEstimateTokenCount] = useState(0)
@ -450,12 +451,14 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
<ControlOutlined /> <ControlOutlined />
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
{showKnowledgeIcon && (
<KnowledgeBaseButton <KnowledgeBaseButton
selectedBase={selectedKnowledgeBase} selectedBase={selectedKnowledgeBase}
onSelect={handleKnowledgeBaseSelect} onSelect={handleKnowledgeBaseSelect}
ToolbarButton={ToolbarButton} ToolbarButton={ToolbarButton}
disabled={files.length > 0} disabled={files.length > 0}
/> />
)}
<AttachmentButton <AttachmentButton
model={model} model={model}
files={files} files={files}

View File

@ -27,7 +27,7 @@ const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => { const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
const avatar = useAvatar() const avatar = useAvatar()
const { theme } = useTheme() const { theme } = useTheme()
const { userName } = useSettings() const { userName, showMinappIcon } = useSettings()
const { t } = useTranslation() const { t } = useTranslation()
const { isBubbleStyle } = useMessageStyle() const { isBubbleStyle } = useMessageStyle()
@ -44,7 +44,9 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name]) const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name])
const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName]) const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName])
const showMiniApp = useCallback(() => model?.provider && startMinAppById(model.provider), [model?.provider]) const showMiniApp = useCallback(() => {
showMinappIcon && model?.provider && startMinAppById(model.provider)
}, [model?.provider, showMinappIcon])
const avatarStyle: CSSProperties | undefined = isBubbleStyle const avatarStyle: CSSProperties | undefined = isBubbleStyle
? { ? {
@ -62,7 +64,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
size={35} size={35}
style={{ style={{
borderRadius: '20%', borderRadius: '20%',
cursor: 'pointer', cursor: showMinappIcon ? 'pointer' : 'default',
border: isLocalAi ? '1px solid var(--color-border-soft)' : 'none', border: isLocalAi ? '1px solid var(--color-border-soft)' : 'none',
filter: theme === 'dark' ? 'invert(0.05)' : undefined filter: theme === 'dark' ? 'invert(0.05)' : undefined
}} }}

View File

@ -25,7 +25,7 @@ interface Props {
const HeaderNavbar: FC<Props> = ({ activeAssistant }) => { const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
const { assistant } = useAssistant(activeAssistant.id) const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants() const { showAssistants, toggleShowAssistants } = useShowAssistants()
const { topicPosition } = useSettings() const { topicPosition, showMinappIcon } = useSettings()
const { showTopics, toggleShowTopics } = useShowTopics() const { showTopics, toggleShowTopics } = useShowTopics()
useShortcut('toggle_show_assistants', () => { useShortcut('toggle_show_assistants', () => {
@ -79,11 +79,13 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
<NavbarIcon onClick={() => SearchPopup.show()}> <NavbarIcon onClick={() => SearchPopup.show()}>
<SearchOutlined /> <SearchOutlined />
</NavbarIcon> </NavbarIcon>
{showMinappIcon && (
<AppStorePopover> <AppStorePopover>
<NavbarIcon style={{ marginLeft: isMac ? 5 : 10 }}> <NavbarIcon style={{ marginLeft: isMac ? 5 : 10 }}>
<i className="iconfont icon-appstore" /> <i className="iconfont icon-appstore" />
</NavbarIcon> </NavbarIcon>
</AppStorePopover> </AppStorePopover>
)}
{topicPosition === 'right' && ( {topicPosition === 'right' && (
<NavbarIcon onClick={toggleShowTopics} style={{ marginLeft: isMac ? 5 : 10 }}> <NavbarIcon onClick={toggleShowTopics} style={{ marginLeft: isMac ? 5 : 10 }}>
<i className={`iconfont icon-${showTopics ? 'show' : 'hide'}-sidebar`} /> <i className={`iconfont icon-${showTopics ? 'show' : 'hide'}-sidebar`} />

View File

@ -6,8 +6,11 @@ import {
setClickAssistantToShowTopic, setClickAssistantToShowTopic,
setCustomCss, setCustomCss,
setShowFilesIcon, setShowFilesIcon,
setShowKnowledgeIcon,
setShowMinappIcon, setShowMinappIcon,
setShowTopicTime setShowPaintingIcon,
setShowTopicTime,
setShowTranslateIcon
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { ThemeMode } from '@renderer/types' import { ThemeMode } from '@renderer/types'
import { Input, Select, Switch } from 'antd' import { Input, Select, Switch } from 'antd'
@ -22,7 +25,10 @@ const DisplaySettings: FC = () => {
theme, theme,
windowStyle, windowStyle,
setWindowStyle, setWindowStyle,
showTranslateIcon,
showPaintingIcon,
showMinappIcon, showMinappIcon,
showKnowledgeIcon,
showFilesIcon, showFilesIcon,
topicPosition, topicPosition,
setTopicPosition, setTopicPosition,
@ -103,11 +109,26 @@ const DisplaySettings: FC = () => {
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.display.sidebar.title')}</SettingTitle> <SettingTitle>{t('settings.display.sidebar.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.translate.icon')}</SettingRowTitle>
<Switch checked={showTranslateIcon} onChange={(value) => dispatch(setShowTranslateIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.painting.icon')}</SettingRowTitle>
<Switch checked={showPaintingIcon} onChange={(value) => dispatch(setShowPaintingIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.minapp.icon')}</SettingRowTitle> <SettingRowTitle>{t('settings.display.sidebar.minapp.icon')}</SettingRowTitle>
<Switch checked={showMinappIcon} onChange={(value) => dispatch(setShowMinappIcon(value))} /> <Switch checked={showMinappIcon} onChange={(value) => dispatch(setShowMinappIcon(value))} />
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.knowledge.icon')}</SettingRowTitle>
<Switch checked={showKnowledgeIcon} onChange={(value) => dispatch(setShowKnowledgeIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.files.icon')}</SettingRowTitle> <SettingRowTitle>{t('settings.display.sidebar.files.icon')}</SettingRowTitle>
<Switch checked={showFilesIcon} onChange={(value) => dispatch(setShowFilesIcon(value))} /> <Switch checked={showFilesIcon} onChange={(value) => dispatch(setShowFilesIcon(value))} />

View File

@ -786,6 +786,9 @@ const migrateConfig = {
system: false system: false
}) })
} }
state.settings.showTranslateIcon = true
state.settings.showPaintingIcon = true
state.settings.showKnowledgeIcon = true
return state return state
} }
} }

View File

@ -42,7 +42,10 @@ export interface SettingsState {
autoTranslateWithSpace: boolean autoTranslateWithSpace: boolean
enableTopicNaming: boolean enableTopicNaming: boolean
// Sidebar icons // Sidebar icons
showTranslateIcon: boolean
showPaintingIcon: boolean
showMinappIcon: boolean showMinappIcon: boolean
showKnowledgeIcon: boolean
showFilesIcon: boolean showFilesIcon: boolean
customCss: string customCss: string
topicNamingPrompt: string topicNamingPrompt: string
@ -84,7 +87,10 @@ const initialState: SettingsState = {
translateModelPrompt: TRANSLATE_PROMPT, translateModelPrompt: TRANSLATE_PROMPT,
autoTranslateWithSpace: false, autoTranslateWithSpace: false,
enableTopicNaming: true, enableTopicNaming: true,
showTranslateIcon: true,
showPaintingIcon: true,
showMinappIcon: true, showMinappIcon: true,
showKnowledgeIcon: true,
showFilesIcon: true, showFilesIcon: true,
customCss: '', customCss: '',
topicNamingPrompt: '' topicNamingPrompt: ''
@ -203,9 +209,18 @@ const settingsSlice = createSlice({
setEnableTopicNaming: (state, action: PayloadAction<boolean>) => { setEnableTopicNaming: (state, action: PayloadAction<boolean>) => {
state.enableTopicNaming = action.payload state.enableTopicNaming = action.payload
}, },
setShowTranslateIcon: (state, action: PayloadAction<boolean>) => {
state.showTranslateIcon = action.payload
},
setShowPaintingIcon: (state, action: PayloadAction<boolean>) => {
state.showPaintingIcon = action.payload
},
setShowMinappIcon: (state, action: PayloadAction<boolean>) => { setShowMinappIcon: (state, action: PayloadAction<boolean>) => {
state.showMinappIcon = action.payload state.showMinappIcon = action.payload
}, },
setShowKnowledgeIcon: (state, action: PayloadAction<boolean>) => {
state.showKnowledgeIcon = action.payload
},
setShowFilesIcon: (state, action: PayloadAction<boolean>) => { setShowFilesIcon: (state, action: PayloadAction<boolean>) => {
state.showFilesIcon = action.payload state.showFilesIcon = action.payload
}, },
@ -258,7 +273,10 @@ export const {
setTranslateModelPrompt, setTranslateModelPrompt,
setAutoTranslateWithSpace, setAutoTranslateWithSpace,
setEnableTopicNaming, setEnableTopicNaming,
setShowTranslateIcon,
setShowPaintingIcon,
setShowMinappIcon, setShowMinappIcon,
setShowKnowledgeIcon,
setShowFilesIcon, setShowFilesIcon,
setPasteLongTextThreshold, setPasteLongTextThreshold,
setCustomCss, setCustomCss,