feature: customizable sidebar module #644 (#680)

* feat:对话的时候支持侧边栏拖拽调整宽度

* feat:对话的时候支持侧边栏拖拽调整宽度

* feat: 隐藏app sidebar 用户体验度提升,不支持隐藏对话

* fix:对话勾选知识库 国际化错误

* refactor: split the SidebarIconsManager module out of DisplaySettings

* style: update SidebarIconsManager style

* ci: fix typecheck

* Revert "feat:对话的时候支持侧边栏拖拽调整宽度"

This reverts commit 58072128f0741ecc4f918564512ec1e4ee3e9edb.

* refactor: merge migrate versions

* refactor: simplify sidebarIcons data structure

* chore: move react-beautiful-dnd to dev dependencies

* chore: use @hello-pangea/dnd replace react-beautiful-dnd

* docs: update translation and formatting of input messages

---------

Co-authored-by: hxp0618 <1169924772@qq.com>
Co-authored-by: huang <hxp0618@gmail.com>
This commit is contained in:
亢奋猫 2025-01-07 19:11:12 +08:00 committed by GitHub
parent edac2004a0
commit c9813bb1e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 2338 additions and 1890 deletions

View File

@ -50,7 +50,7 @@ export default defineConfig({
} }
}, },
optimizeDeps: { optimizeDeps: {
exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js'] exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js', 'chunk-AFE5XGNG.js']
} }
} }
}) })

View File

@ -89,12 +89,14 @@ class KnowledgeService {
if (item.type === 'url') { if (item.type === 'url') {
const content = item.content as string const content = item.content as string
if (content.startsWith('http')) { if (content.startsWith('http')) {
// @ts-ignore loader type
return await ragApplication.addLoader(new WebLoader({ urlOrContent: content }), forceReload) return await ragApplication.addLoader(new WebLoader({ urlOrContent: content }), forceReload)
} }
} }
if (item.type === 'sitemap') { if (item.type === 'sitemap') {
const content = item.content as string const content = item.content as string
// @ts-ignore loader type
return await ragApplication.addLoader(new SitemapLoader({ url: content }), forceReload) return await ragApplication.addLoader(new SitemapLoader({ url: content }), forceReload)
} }

View File

@ -21,8 +21,7 @@ const Sidebar: FC = () => {
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { t } = useTranslation() const { t } = useTranslation()
const navigate = useNavigate() const navigate = useNavigate()
const { windowStyle, showTranslateIcon, showPaintingIcon, showMinappIcon, showKnowledgeIcon, showFilesIcon } = const { windowStyle, sidebarIcons } = useSettings()
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' : '')
@ -38,6 +37,41 @@ const Sidebar: FC = () => {
navigate(path) navigate(path)
} }
const renderMainMenus = () => {
return sidebarIcons.visible.map((icon) => {
const iconMap = {
assistants: <i className="iconfont icon-chat" />,
agents: <i className="iconfont icon-business-smart-assistant" />,
paintings: <PictureOutlined style={{ fontSize: 16 }} />,
translate: <TranslationOutlined />,
minapp: <i className="iconfont icon-appstore" />,
knowledge: <FileSearchOutlined />,
files: <FolderOutlined />
}
const pathMap = {
assistants: '/',
agents: '/agents',
paintings: '/paintings',
translate: '/translate',
minapp: '/apps',
knowledge: '/knowledge',
files: '/files'
}
const path = pathMap[icon]
const isActive = path === '/' ? isRoute(path) : isRoutes(path)
return (
<Tooltip key={icon} title={t(`${icon}.title`)} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to(path)}>
<Icon className={isActive}>{iconMap[icon]}</Icon>
</StyledLink>
</Tooltip>
)
})
}
return ( return (
<Container <Container
id="app-sidebar" id="app-sidebar"
@ -47,67 +81,7 @@ const Sidebar: FC = () => {
}}> }}>
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} /> <AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
<MainMenus> <MainMenus>
<Menus onClick={MinApp.onClose}> <Menus onClick={MinApp.onClose}>{renderMainMenus()}</Menus>
<Tooltip title={t('assistants.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/')}>
<Icon className={isRoute('/')}>
<i className="iconfont icon-chat" />
</Icon>
</StyledLink>
</Tooltip>
<Tooltip title={t('agents.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/agents')}>
<Icon className={isRoutes('/agents')}>
<i className="iconfont icon-business-smart-assistant" />
</Icon>
</StyledLink>
</Tooltip>
{showPaintingIcon && (
<Tooltip title={t('paintings.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/paintings')}>
<Icon className={isRoute('/paintings')}>
<PictureOutlined style={{ fontSize: 16 }} />
</Icon>
</StyledLink>
</Tooltip>
)}
{showTranslateIcon && (
<Tooltip title={t('translate.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/translate')}>
<Icon className={isRoute('/translate')}>
<TranslationOutlined />
</Icon>
</StyledLink>
</Tooltip>
)}
{showMinappIcon && (
<Tooltip title={t('minapp.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/apps')}>
<Icon className={isRoute('/apps')}>
<i className="iconfont icon-appstore" />
</Icon>
</StyledLink>
</Tooltip>
)}
{showKnowledgeIcon && (
<Tooltip title={t('knowledge_base.title')} mouseEnterDelay={0.5} placement="right">
<StyledLink onClick={() => to('/knowledge')}>
<Icon className={isRoute('/knowledge')}>
<FileSearchOutlined />
</Icon>
</StyledLink>
</Tooltip>
)}
{showFilesIcon && (
<Tooltip title={t('files.title')} mouseEnterDelay={0.8} placement="right">
<StyledLink onClick={() => to('/files')}>
<Icon className={isRoute('/files')}>
<FolderOutlined />
</Icon>
</StyledLink>
</Tooltip>
)}
</Menus>
</MainMenus> </MainMenus>
<Menus onClick={MinApp.onClose}> <Menus onClick={MinApp.onClose}>
<Tooltip title={t('settings.theme.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip title={t('settings.theme.title')} mouseEnterDelay={0.8} placement="right">

View File

@ -401,6 +401,10 @@
"display.sidebar.knowledge.icon": "Show Knowledge 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.sidebar.visible": "Show my sidebar icons",
"display.sidebar.disabled": "Hide my sidebar icons",
"display.sidebar.chat.hiddenMessage": "Assistants are basic functions, not supported for hiding",
"display.sidebar.empty": "Drag the hidden feature from the left side here",
"display.topic.title": "Topic Settings", "display.topic.title": "Topic Settings",
"display.custom.css": "Custom CSS", "display.custom.css": "Custom CSS",
"display.custom.css.placeholder": "/* Put custom CSS here */", "display.custom.css.placeholder": "/* Put custom CSS here */",
@ -411,7 +415,7 @@
"messages.input.show_estimated_tokens": "Show estimated tokens", "messages.input.show_estimated_tokens": "Show estimated tokens",
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec", "messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
"messages.input.title": "Input Settings", "messages.input.title": "Input Settings",
"messages.markdown_rendering_input_message": "Markdown render input msg", "messages.markdown_rendering_input_message": "Markdown render input message",
"messages.math_engine": "Math render engine", "messages.math_engine": "Math render engine",
"messages.model.title": "Model Settings", "messages.model.title": "Model Settings",
"messages.title": "Message Settings", "messages.title": "Message Settings",
@ -547,7 +551,7 @@
"show_window": "Show Window", "show_window": "Show Window",
"quit": "Quit" "quit": "Quit"
}, },
"knowledge_base": { "knowledge": {
"title": "Knowledge Base", "title": "Knowledge Base",
"search": "Search knowledge base", "search": "Search knowledge base",
"empty": "No knowledge base found", "empty": "No knowledge base found",

View File

@ -399,6 +399,10 @@
"display.sidebar.knowledge.icon": "ナレッジのアイコンを表示", "display.sidebar.knowledge.icon": "ナレッジのアイコンを表示",
"display.sidebar.files.icon": "ファイルのアイコンを表示", "display.sidebar.files.icon": "ファイルのアイコンを表示",
"display.sidebar.title": "サイドバー設定", "display.sidebar.title": "サイドバー設定",
"display.sidebar.visible": "サイドバーのアイコンを表示する",
"display.sidebar.disabled": "サイドバーのアイコンを非表示にする",
"display.sidebar.chat.hiddenMessage": "アシスタントは基本的な機能であり、非表示はサポートされていません",
"display.sidebar.empty": "非表示にする機能を左側からここにドラッグ",
"display.topic.title": "トピック設定", "display.topic.title": "トピック設定",
"display.custom.css": "カスタムCSS", "display.custom.css": "カスタムCSS",
"display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */", "display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */",
@ -533,7 +537,7 @@
"show_window": "ウィンドウを表示", "show_window": "ウィンドウを表示",
"quit": "終了" "quit": "終了"
}, },
"knowledge_base": { "knowledge": {
"title": "ナレッジベース", "title": "ナレッジベース",
"search": "ナレッジベースを検索", "search": "ナレッジベースを検索",
"empty": "ナレッジベースが見つかりません", "empty": "ナレッジベースが見つかりません",

View File

@ -401,6 +401,10 @@
"display.sidebar.knowledge.icon": "Показывать иконку знаний", "display.sidebar.knowledge.icon": "Показывать иконку знаний",
"display.sidebar.files.icon": "Показывать иконку файлов", "display.sidebar.files.icon": "Показывать иконку файлов",
"display.sidebar.title": "Настройки боковой панели", "display.sidebar.title": "Настройки боковой панели",
"display.sidebar.visible": "Показать мои значки на боковой панели",
"display.sidebar.disabled": "Скрыть значок на боковой панели",
"display.sidebar.chat.hiddenMessage": "Помощник является базовой функцией и не поддерживает скрытие",
"display.sidebar.empty": "Перетащите скрываемую функцию с левой стороны сюда",
"display.topic.title": "Настройки топиков", "display.topic.title": "Настройки топиков",
"display.custom.css": "Пользовательский CSS", "display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */", "display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",
@ -547,7 +551,7 @@
"show_window": "Показать окно", "show_window": "Показать окно",
"quit": "Выйти" "quit": "Выйти"
}, },
"knowledge_base": { "knowledge": {
"title": "База знаний", "title": "База знаний",
"search": "Поиск в базе знаний", "search": "Поиск в базе знаний",
"empty": "База знаний не найдена", "empty": "База знаний не найдена",

View File

@ -402,6 +402,10 @@
"display.sidebar.knowledge.icon": "显示知识图标", "display.sidebar.knowledge.icon": "显示知识图标",
"display.sidebar.files.icon": "显示文件图标", "display.sidebar.files.icon": "显示文件图标",
"display.sidebar.title": "侧边栏设置", "display.sidebar.title": "侧边栏设置",
"display.sidebar.visible": "显示我的侧边栏图标",
"display.sidebar.disabled": "隐藏我的侧边栏图标",
"display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏",
"display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里",
"display.topic.title": "话题设置", "display.topic.title": "话题设置",
"display.custom.css": "自定义 CSS", "display.custom.css": "自定义 CSS",
"display.custom.css.placeholder": "/* 这里写自定义CSS */", "display.custom.css.placeholder": "/* 这里写自定义CSS */",
@ -536,7 +540,7 @@
"show_window": "显示窗口", "show_window": "显示窗口",
"quit": "退出" "quit": "退出"
}, },
"knowledge_base": { "knowledge": {
"title": "知识库", "title": "知识库",
"search": "搜索知识库", "search": "搜索知识库",
"empty": "暂无知识库", "empty": "暂无知识库",

View File

@ -402,6 +402,10 @@
"display.sidebar.files.icon": "顯示文件圖示", "display.sidebar.files.icon": "顯示文件圖示",
"display.sidebar.title": "側邊欄設定", "display.sidebar.title": "側邊欄設定",
"display.topic.title": "話題設定", "display.topic.title": "話題設定",
"display.sidebar.chat.hiddenMessage": "助手是基礎功能,不支援隱藏",
"display.sidebar.empty": "把要隱藏的功能從左側拖拽到這裡",
"display.sidebar.visible": "顯示我的側邊欄圖標",
"display.sidebar.disabled": "隱藏我的側邊欄圖標",
"display.custom.css": "自定義 CSS", "display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
"input.auto_translate_with_space": "快速敲擊3次空格翻譯", "input.auto_translate_with_space": "快速敲擊3次空格翻譯",
@ -535,7 +539,7 @@
"show_window": "顯示視窗", "show_window": "顯示視窗",
"quit": "退出" "quit": "退出"
}, },
"knowledge_base": { "knowledge": {
"title": "知識庫", "title": "知識庫",
"search": "搜尋知識庫", "search": "搜尋知識庫",
"empty": "暫無知識庫", "empty": "暫無知識庫",

View File

@ -63,7 +63,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
clickAssistantToShowTopic, clickAssistantToShowTopic,
language, language,
autoTranslateWithSpace, autoTranslateWithSpace,
showKnowledgeIcon sidebarIcons
} = useSettings() } = useSettings()
const [expended, setExpend] = useState(false) const [expended, setExpend] = useState(false)
const [estimateTokenCount, setEstimateTokenCount] = useState(0) const [estimateTokenCount, setEstimateTokenCount] = useState(0)
@ -85,6 +85,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const isVision = useMemo(() => isVisionModel(model), [model]) const isVision = useMemo(() => isVisionModel(model), [model])
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
const showKnowledgeIcon = sidebarIcons.visible.includes('knowledge')
const estimateTextTokens = useCallback(debounce(estimateTxtTokens, 1000), []) const estimateTextTokens = useCallback(debounce(estimateTxtTokens, 1000), [])
const inputTokenCount = useMemo( const inputTokenCount = useMemo(
() => (showInputEstimatedTokens ? estimateTextTokens(text) || 0 : 0), () => (showInputEstimatedTokens ? estimateTextTokens(text) || 0 : 0),

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, showMinappIcon } = useSettings() const { userName, sidebarIcons } = useSettings()
const { t } = useTranslation() const { t } = useTranslation()
const { isBubbleStyle } = useMessageStyle() const { isBubbleStyle } = useMessageStyle()
@ -40,6 +40,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
}, [message.modelId, message.role, model?.id, model?.name, t, userName]) }, [message.modelId, message.role, model?.id, model?.name, t, userName])
const isAssistantMessage = message.role === 'assistant' const isAssistantMessage = message.role === 'assistant'
const showMinappIcon = sidebarIcons.visible.includes('minapp')
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])

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, showMinappIcon } = useSettings() const { topicPosition, sidebarIcons } = useSettings()
const { showTopics, toggleShowTopics } = useShowTopics() const { showTopics, toggleShowTopics } = useShowTopics()
useShortcut('toggle_show_assistants', () => { useShortcut('toggle_show_assistants', () => {
@ -79,7 +79,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
<NavbarIcon onClick={() => SearchPopup.show()}> <NavbarIcon onClick={() => SearchPopup.show()}>
<SearchOutlined /> <SearchOutlined />
</NavbarIcon> </NavbarIcon>
{showMinappIcon && ( {sidebarIcons.visible.includes('minapp') && (
<AppStorePopover> <AppStorePopover>
<NavbarIcon style={{ marginLeft: isMac ? 5 : 10 }}> <NavbarIcon style={{ marginLeft: isMac ? 5 : 10 }}>
<i className="iconfont icon-appstore" /> <i className="iconfont icon-appstore" />

View File

@ -126,9 +126,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
} }
const url = await PromptPopup.show({ const url = await PromptPopup.show({
title: t('knowledge_base.add_url'), title: t('knowledge.add_url'),
message: '', message: '',
inputPlaceholder: t('knowledge_base.url_placeholder'), inputPlaceholder: t('knowledge.url_placeholder'),
inputProps: { inputProps: {
maxLength: 1000, maxLength: 1000,
rows: 1 rows: 1
@ -139,7 +139,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
try { try {
new URL(url) new URL(url)
if (urlItems.find((item) => item.content === url)) { if (urlItems.find((item) => item.content === url)) {
message.success(t('knowledge_base.url_added')) message.success(t('knowledge.url_added'))
return return
} }
addUrl(url) addUrl(url)
@ -155,9 +155,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
} }
const url = await PromptPopup.show({ const url = await PromptPopup.show({
title: t('knowledge_base.add_sitemap'), title: t('knowledge.add_sitemap'),
message: '', message: '',
inputPlaceholder: t('knowledge_base.sitemap_placeholder'), inputPlaceholder: t('knowledge.sitemap_placeholder'),
inputProps: { inputProps: {
maxLength: 1000, maxLength: 1000,
rows: 1 rows: 1
@ -168,7 +168,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
try { try {
new URL(url) new URL(url)
if (sitemapItems.find((item) => item.content === url)) { if (sitemapItems.find((item) => item.content === url)) {
message.success(t('knowledge_base.sitemap_added')) message.success(t('knowledge.sitemap_added'))
return return
} }
addSitemap(url) addSitemap(url)
@ -209,16 +209,16 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
return ( return (
<MainContent> <MainContent>
{!base?.version && ( {!base?.version && (
<Alert message={t('knowledge_base.not_support')} type="error" style={{ marginBottom: 20 }} showIcon /> <Alert message={t('knowledge.not_support')} type="error" style={{ marginBottom: 20 }} showIcon />
)} )}
{!providerName && ( {!providerName && (
<Alert message={t('knowledge_base.no_provider')} type="error" style={{ marginBottom: 20 }} showIcon /> <Alert message={t('knowledge.no_provider')} type="error" style={{ marginBottom: 20 }} showIcon />
)} )}
<FileSection> <FileSection>
<TitleWrapper> <TitleWrapper>
<Title level={5}>{t('files.title')}</Title> <Title level={5}>{t('files.title')}</Title>
<Button icon={<PlusOutlined />} onClick={handleAddFile} disabled={disabled}> <Button icon={<PlusOutlined />} onClick={handleAddFile} disabled={disabled}>
{t('knowledge_base.add_file')} {t('knowledge.add_file')}
</Button> </Button>
</TitleWrapper> </TitleWrapper>
<Dragger <Dragger
@ -227,9 +227,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
multiple={true} multiple={true}
accept={fileTypes.join(',')} accept={fileTypes.join(',')}
style={{ marginTop: 10, background: 'transparent' }}> style={{ marginTop: 10, background: 'transparent' }}>
<p className="ant-upload-text">{t('knowledge_base.drag_file')}</p> <p className="ant-upload-text">{t('knowledge.drag_file')}</p>
<p className="ant-upload-hint"> <p className="ant-upload-hint">
{t('knowledge_base.file_hint', { file_types: fileTypes.join(', ').replaceAll('.', '') })} {t('knowledge.file_hint', { file_types: fileTypes.join(', ').replaceAll('.', '') })}
</p> </p>
</Dragger> </Dragger>
</FileSection> </FileSection>
@ -256,9 +256,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ContentSection> <ContentSection>
<TitleWrapper> <TitleWrapper>
<Title level={5}>{t('knowledge_base.directories')}</Title> <Title level={5}>{t('knowledge.directories')}</Title>
<Button icon={<PlusOutlined />} onClick={handleAddDirectory} disabled={disabled}> <Button icon={<PlusOutlined />} onClick={handleAddDirectory} disabled={disabled}>
{t('knowledge_base.add_directory')} {t('knowledge.add_directory')}
</Button> </Button>
</TitleWrapper> </TitleWrapper>
<FlexColumn> <FlexColumn>
@ -283,9 +283,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ContentSection> <ContentSection>
<TitleWrapper> <TitleWrapper>
<Title level={5}>{t('knowledge_base.urls')}</Title> <Title level={5}>{t('knowledge.urls')}</Title>
<Button icon={<PlusOutlined />} onClick={handleAddUrl} disabled={disabled}> <Button icon={<PlusOutlined />} onClick={handleAddUrl} disabled={disabled}>
{t('knowledge_base.add_url')} {t('knowledge.add_url')}
</Button> </Button>
</TitleWrapper> </TitleWrapper>
<FlexColumn> <FlexColumn>
@ -310,9 +310,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ContentSection> <ContentSection>
<TitleWrapper> <TitleWrapper>
<Title level={5}>{t('knowledge_base.sitemaps')}</Title> <Title level={5}>{t('knowledge.sitemaps')}</Title>
<Button icon={<PlusOutlined />} onClick={handleAddSitemap} disabled={disabled}> <Button icon={<PlusOutlined />} onClick={handleAddSitemap} disabled={disabled}>
{t('knowledge_base.add_sitemap')} {t('knowledge.add_sitemap')}
</Button> </Button>
</TitleWrapper> </TitleWrapper>
<FlexColumn> <FlexColumn>
@ -337,9 +337,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ContentSection> <ContentSection>
<TitleWrapper> <TitleWrapper>
<Title level={5}>{t('knowledge_base.notes')}</Title> <Title level={5}>{t('knowledge.notes')}</Title>
<Button icon={<PlusOutlined />} onClick={handleAddNote} disabled={disabled}> <Button icon={<PlusOutlined />} onClick={handleAddNote} disabled={disabled}>
{t('knowledge_base.add_note')} {t('knowledge.add_note')}
</Button> </Button>
</TitleWrapper> </TitleWrapper>
<FlexColumn> <FlexColumn>
@ -363,7 +363,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<Divider style={{ margin: '10px 0' }} /> <Divider style={{ margin: '10px 0' }} />
<ModelInfo> <ModelInfo>
<label htmlFor="model-info">{t('knowledge_base.model_info')}</label> <label htmlFor="model-info">{t('knowledge.model_info')}</label>
<Tag color="blue">{base.model.name}</Tag> <Tag color="blue">{base.model.name}</Tag>
<Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag> <Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag>
{providerName && <Tag color="purple">{providerName}</Tag>} {providerName && <Tag color="purple">{providerName}</Tag>}
@ -375,7 +375,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
onClick={() => KnowledgeSearchPopup.show({ base })} onClick={() => KnowledgeSearchPopup.show({ base })}
icon={<SearchOutlined />} icon={<SearchOutlined />}
disabled={disabled}> disabled={disabled}>
{t('knowledge_base.search')} {t('knowledge.search')}
</Button> </Button>
</IndexSection> </IndexSection>

View File

@ -22,7 +22,7 @@ const KnowledgePage: FC = () => {
const prevLength = useRef(0) const prevLength = useRef(0)
const handleAddKnowledge = async () => { const handleAddKnowledge = async () => {
await AddKnowledgePopup.show({ title: t('knowledge_base.add.title') }) await AddKnowledgePopup.show({ title: t('knowledge.add.title') })
} }
useEffect(() => { useEffect(() => {
@ -48,12 +48,12 @@ const KnowledgePage: FC = () => {
(base: KnowledgeBase) => { (base: KnowledgeBase) => {
const menus: MenuProps['items'] = [ const menus: MenuProps['items'] = [
{ {
label: t('knowledge_base.rename'), label: t('knowledge.rename'),
key: 'rename', key: 'rename',
icon: <EditOutlined />, icon: <EditOutlined />,
async onClick() { async onClick() {
const name = await PromptPopup.show({ const name = await PromptPopup.show({
title: t('knowledge_base.rename'), title: t('knowledge.rename'),
message: '', message: '',
defaultValue: base.name || '' defaultValue: base.name || ''
}) })
@ -70,7 +70,7 @@ const KnowledgePage: FC = () => {
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
onClick: () => { onClick: () => {
window.modal.confirm({ window.modal.confirm({
title: t('knowledge_base.delete_confirm'), title: t('knowledge.delete_confirm'),
centered: true, centered: true,
onOk: () => { onOk: () => {
deleteKnowledgeBase(base.id) deleteKnowledgeBase(base.id)
@ -88,7 +88,7 @@ const KnowledgePage: FC = () => {
return ( return (
<Container> <Container>
<Navbar> <Navbar>
<NavbarCenter style={{ borderRight: 'none' }}>{t('knowledge_base.title')}</NavbarCenter> <NavbarCenter style={{ borderRight: 'none' }}>{t('knowledge.title')}</NavbarCenter>
</Navbar> </Navbar>
<ContentContainer id="content-container"> <ContentContainer id="content-container">
<SideNav> <SideNav>
@ -125,7 +125,7 @@ const KnowledgePage: FC = () => {
</SideNav> </SideNav>
{bases.length === 0 ? ( {bases.length === 0 ? (
<MainContent> <MainContent>
<Empty description={t('knowledge_base.empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty description={t('knowledge.empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
</MainContent> </MainContent>
) : selectedBase ? ( ) : selectedBase ? (
<KnowledgeContent selectedBase={selectedBase} /> <KnowledgeContent selectedBase={selectedBase} />

View File

@ -77,7 +77,7 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
return ( return (
<Modal <Modal
title={t('knowledge_base.search')} title={t('knowledge.search')}
open={open} open={open}
onOk={onOk} onOk={onOk}
onCancel={onCancel} onCancel={onCancel}
@ -88,7 +88,7 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
transitionName="ant-move-down"> transitionName="ant-move-down">
<SearchContainer> <SearchContainer>
<Search <Search
placeholder={t('knowledge_base.search_placeholder')} placeholder={t('knowledge.search_placeholder')}
allowClear allowClear
enterButton enterButton
size="large" size="large"
@ -109,7 +109,7 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
<Paragraph>{highlightText(item.pageContent)}</Paragraph> <Paragraph>{highlightText(item.pageContent)}</Paragraph>
<MetadataContainer> <MetadataContainer>
<Text type="secondary"> <Text type="secondary">
{t('knowledge_base.source')}:{' '} {t('knowledge.source')}:{' '}
{item.file ? ( {item.file ? (
<a href={`http://file/${item.file.name}`} target="_blank" rel="noreferrer"> <a href={`http://file/${item.file.name}`} target="_blank" rel="noreferrer">
{item.file.origin_name} {item.file.origin_name}

View File

@ -21,13 +21,13 @@ const StatusIcon: FC<StatusIconProps> = ({ sourceId, base, getProcessingStatus }
if (!status) { if (!status) {
if (item?.uniqueId) { if (item?.uniqueId) {
return ( return (
<Tooltip title={t('knowledge_base.status_completed')} placement="left"> <Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} /> <CheckCircleOutlined style={{ color: '#52c41a' }} />
</Tooltip> </Tooltip>
) )
} }
return ( return (
<Tooltip title={t('knowledge_base.status_new')} placement="left"> <Tooltip title={t('knowledge.status_new')} placement="left">
<Center style={{ width: '16px', height: '16px' }}> <Center style={{ width: '16px', height: '16px' }}>
<StatusDot $status="new" /> <StatusDot $status="new" />
</Center> </Center>
@ -38,25 +38,25 @@ const StatusIcon: FC<StatusIconProps> = ({ sourceId, base, getProcessingStatus }
switch (status) { switch (status) {
case 'pending': case 'pending':
return ( return (
<Tooltip title={t('knowledge_base.status_pending')} placement="left"> <Tooltip title={t('knowledge.status_pending')} placement="left">
<StatusDot $status="pending" /> <StatusDot $status="pending" />
</Tooltip> </Tooltip>
) )
case 'processing': case 'processing':
return ( return (
<Tooltip title={t('knowledge_base.status_processing')} placement="left"> <Tooltip title={t('knowledge.status_processing')} placement="left">
<StatusDot $status="processing" /> <StatusDot $status="processing" />
</Tooltip> </Tooltip>
) )
case 'completed': case 'completed':
return ( return (
<Tooltip title={t('knowledge_base.status_completed')} placement="left"> <Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} /> <CheckCircleOutlined style={{ color: '#52c41a' }} />
</Tooltip> </Tooltip>
) )
case 'failed': case 'failed':
return ( return (
<Tooltip title={errorText || t('knowledge_base.status_failed')} placement="left"> <Tooltip title={errorText || t('knowledge.status_failed')} placement="left">
<CloseCircleOutlined style={{ color: '#ff4d4f' }} /> <CloseCircleOutlined style={{ color: '#ff4d4f' }} />
</Tooltip> </Tooltip>
) )

View File

@ -3,21 +3,20 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { import {
DEFAULT_SIDEBAR_ICONS,
setClickAssistantToShowTopic, setClickAssistantToShowTopic,
setCustomCss, setCustomCss,
setShowFilesIcon,
setShowKnowledgeIcon,
setShowMinappIcon,
setShowPaintingIcon,
setShowTopicTime, setShowTopicTime,
setShowTranslateIcon setSidebarIcons
} 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 { Button, Input, Select, Switch } from 'antd'
import { FC } from 'react' import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '.' import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import SidebarIconsManager from './SidebarIconsManager'
const DisplaySettings: FC = () => { const DisplaySettings: FC = () => {
const { const {
@ -25,25 +24,33 @@ const DisplaySettings: FC = () => {
theme, theme,
windowStyle, windowStyle,
setWindowStyle, setWindowStyle,
showTranslateIcon,
showPaintingIcon,
showMinappIcon,
showKnowledgeIcon,
showFilesIcon,
topicPosition, topicPosition,
setTopicPosition, setTopicPosition,
clickAssistantToShowTopic, clickAssistantToShowTopic,
showTopicTime, showTopicTime,
customCss customCss,
sidebarIcons
} = useSettings() } = useSettings()
const { theme: themeMode } = useTheme() const { theme: themeMode } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const handleWindowStyleChange = (checked: boolean) => { const [visibleIcons, setVisibleIcons] = useState(sidebarIcons?.visible || DEFAULT_SIDEBAR_ICONS)
setWindowStyle(checked ? 'transparent' : 'opaque') const [disabledIcons, setDisabledIcons] = useState(sidebarIcons?.disabled || [])
}
// 使用useCallback优化回调函数
const handleWindowStyleChange = useCallback(
(checked: boolean) => {
setWindowStyle(checked ? 'transparent' : 'opaque')
},
[setWindowStyle]
)
const handleReset = useCallback(() => {
setVisibleIcons([...DEFAULT_SIDEBAR_ICONS])
setDisabledIcons([])
dispatch(setSidebarIcons({ visible: DEFAULT_SIDEBAR_ICONS, disabled: [] }))
}, [dispatch])
return ( return (
<SettingContainer theme={themeMode}> <SettingContainer theme={themeMode}>
@ -53,7 +60,7 @@ const DisplaySettings: FC = () => {
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle> <SettingRowTitle>{t('settings.theme.title')}</SettingRowTitle>
<Select <Select
defaultValue={theme} value={theme}
style={{ width: 120 }} style={{ width: 120 }}
onChange={setTheme} onChange={setTheme}
options={[ options={[
@ -79,7 +86,7 @@ const DisplaySettings: FC = () => {
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.topic.position')}</SettingRowTitle> <SettingRowTitle>{t('settings.topic.position')}</SettingRowTitle>
<Select <Select
defaultValue={topicPosition || 'right'} value={topicPosition || 'right'}
style={{ width: 120 }} style={{ width: 120 }}
onChange={setTopicPosition} onChange={setTopicPosition}
options={[ options={[
@ -107,39 +114,27 @@ const DisplaySettings: FC = () => {
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.display.sidebar.title')}</SettingTitle> <SettingTitle
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<span>{t('settings.display.sidebar.title')}</span>
<ResetButtonWrapper>
<Button onClick={handleReset}>{t('common.reset')}</Button>
</ResetButtonWrapper>
</SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SidebarIconsManager
<SettingRowTitle>{t('settings.display.sidebar.translate.icon')}</SettingRowTitle> visibleIcons={visibleIcons}
<Switch checked={showTranslateIcon} onChange={(value) => dispatch(setShowTranslateIcon(value))} /> disabledIcons={disabledIcons}
</SettingRow> setVisibleIcons={setVisibleIcons}
<SettingDivider /> setDisabledIcons={setDisabledIcons}
<SettingRow> />
<SettingRowTitle>{t('settings.display.sidebar.painting.icon')}</SettingRowTitle>
<Switch checked={showPaintingIcon} onChange={(value) => dispatch(setShowPaintingIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.minapp.icon')}</SettingRowTitle>
<Switch checked={showMinappIcon} onChange={(value) => dispatch(setShowMinappIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.knowledge.icon')}</SettingRowTitle>
<Switch checked={showKnowledgeIcon} onChange={(value) => dispatch(setShowKnowledgeIcon(value))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.display.sidebar.files.icon')}</SettingRowTitle>
<Switch checked={showFilesIcon} onChange={(value) => dispatch(setShowFilesIcon(value))} />
</SettingRow>
</SettingGroup> </SettingGroup>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.display.custom.css')}</SettingTitle> <SettingTitle>{t('settings.display.custom.css')}</SettingTitle>
<SettingDivider /> <SettingDivider />
<Input.TextArea <Input.TextArea
defaultValue={customCss} value={customCss}
onBlur={(e) => dispatch(setCustomCss(e.target.value))} onChange={(e) => dispatch(setCustomCss(e.target.value))}
placeholder={t('settings.display.custom.css.placeholder')} placeholder={t('settings.display.custom.css.placeholder')}
style={{ style={{
minHeight: 200, minHeight: 200,
@ -151,4 +146,10 @@ const DisplaySettings: FC = () => {
) )
} }
const ResetButtonWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
export default DisplaySettings export default DisplaySettings

View File

@ -0,0 +1,272 @@
import { CloseOutlined } from '@ant-design/icons'
import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
import {
DragDropContext,
Draggable,
DraggableProvided,
Droppable,
DroppableProvided,
DropResult
} from '@hello-pangea/dnd'
import { useAppDispatch } from '@renderer/store'
import { setSidebarIcons } from '@renderer/store/settings'
import { message } from 'antd'
import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SidebarIcon } from '../../../store/settings'
interface SidebarIconsManagerProps {
visibleIcons: SidebarIcon[]
disabledIcons: SidebarIcon[]
setVisibleIcons: (icons: SidebarIcon[]) => void
setDisabledIcons: (icons: SidebarIcon[]) => void
}
const SidebarIconsManager: FC<SidebarIconsManagerProps> = ({
visibleIcons,
disabledIcons,
setVisibleIcons,
setDisabledIcons
}) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const onDragEnd = useCallback(
(result: DropResult) => {
if (!result.destination) return
const { source, destination } = result
// 如果是chat图标且目标是disabled区域,则不允许移动并提示
const draggedItem = source.droppableId === 'visible' ? visibleIcons[source.index] : disabledIcons[source.index]
if (draggedItem === 'assistants' && destination.droppableId === 'disabled') {
message.warning(t('settings.display.sidebar.chat.hiddenMessage'))
return
}
if (source.droppableId === destination.droppableId) {
const list = source.droppableId === 'visible' ? [...visibleIcons] : [...disabledIcons]
const [removed] = list.splice(source.index, 1)
list.splice(destination.index, 0, removed)
if (source.droppableId === 'visible') {
setVisibleIcons(list)
dispatch(setSidebarIcons({ visible: list, disabled: disabledIcons }))
} else {
setDisabledIcons(list)
dispatch(setSidebarIcons({ visible: visibleIcons, disabled: list }))
}
return
}
const sourceList = source.droppableId === 'visible' ? [...visibleIcons] : [...disabledIcons]
const destList = destination.droppableId === 'visible' ? [...visibleIcons] : [...disabledIcons]
const [removed] = sourceList.splice(source.index, 1)
const targetList = destList.filter((icon) => icon !== removed)
targetList.splice(destination.index, 0, removed)
const newVisibleIcons = destination.droppableId === 'visible' ? targetList : sourceList
const newDisabledIcons = destination.droppableId === 'disabled' ? targetList : sourceList
setVisibleIcons(newVisibleIcons)
setDisabledIcons(newDisabledIcons)
dispatch(setSidebarIcons({ visible: newVisibleIcons, disabled: newDisabledIcons }))
},
[visibleIcons, disabledIcons, dispatch, setVisibleIcons, setDisabledIcons, t]
)
const onMoveIcon = useCallback(
(icon: SidebarIcon, fromList: 'visible' | 'disabled') => {
// 如果是chat图标且要移动到disabled列表,则不允许并提示
if (icon === 'assistants' && fromList === 'visible') {
message.warning(t('settings.display.sidebar.chat.hiddenMessage'))
return
}
if (fromList === 'visible') {
const newVisibleIcons = visibleIcons.filter((i) => i !== icon)
const newDisabledIcons = disabledIcons.some((i) => i === icon) ? disabledIcons : [...disabledIcons, icon]
setVisibleIcons(newVisibleIcons)
setDisabledIcons(newDisabledIcons)
dispatch(setSidebarIcons({ visible: newVisibleIcons, disabled: newDisabledIcons }))
} else {
const newDisabledIcons = disabledIcons.filter((i) => i !== icon)
const newVisibleIcons = visibleIcons.some((i) => i === icon) ? visibleIcons : [...visibleIcons, icon]
setDisabledIcons(newDisabledIcons)
setVisibleIcons(newVisibleIcons)
dispatch(setSidebarIcons({ visible: newVisibleIcons, disabled: newDisabledIcons }))
}
},
[t, visibleIcons, disabledIcons, setVisibleIcons, setDisabledIcons, dispatch]
)
// 使用useMemo缓存图标映射
const iconMap = useMemo(
() => ({
assistants: <i className="iconfont icon-chat" />,
agents: <i className="iconfont icon-business-smart-assistant" />,
paintings: <PictureOutlined style={{ fontSize: 14 }} />,
translate: <TranslationOutlined />,
minapp: <i className="iconfont icon-appstore" />,
knowledge: <FileSearchOutlined />,
files: <FolderOutlined />
}),
[]
)
const renderIcon = (icon: SidebarIcon) => iconMap[icon] || <i className={`iconfont ${icon}`} />
return (
<DragDropContext onDragEnd={onDragEnd}>
<IconSection>
<IconColumn>
<h4>{t('settings.display.sidebar.visible')}</h4>
<Droppable droppableId="visible">
{(provided: DroppableProvided) => (
<IconList ref={provided.innerRef} {...provided.droppableProps}>
{visibleIcons.map((icon, index) => (
<Draggable key={icon} draggableId={icon} index={index}>
{(provided: DraggableProvided) => (
<IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<IconContent>
{renderIcon(icon)}
<span>{t(`${icon}.title`)}</span>
</IconContent>
{icon !== 'assistants' && (
<CloseButton onClick={() => onMoveIcon(icon, 'visible')}>
<CloseOutlined />
</CloseButton>
)}
</IconItem>
)}
</Draggable>
))}
{provided.placeholder}
</IconList>
)}
</Droppable>
</IconColumn>
<IconColumn>
<h4>{t('settings.display.sidebar.disabled')}</h4>
<Droppable droppableId="disabled">
{(provided: DroppableProvided) => (
<IconList ref={provided.innerRef} {...provided.droppableProps}>
{disabledIcons.length === 0 ? (
<EmptyPlaceholder>{t('settings.display.sidebar.empty')}</EmptyPlaceholder>
) : (
disabledIcons.map((icon, index) => (
<Draggable key={icon} draggableId={icon} index={index}>
{(provided: DraggableProvided) => (
<IconItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<IconContent>
{renderIcon(icon)}
<span>{t(`${icon}.title`)}</span>
</IconContent>
<CloseButton onClick={() => onMoveIcon(icon, 'disabled')}>
<CloseOutlined />
</CloseButton>
</IconItem>
)}
</Draggable>
))
)}
{provided.placeholder}
</IconList>
)}
</Droppable>
</IconColumn>
</IconSection>
</DragDropContext>
)
}
// Styled components remain the same
const IconSection = styled.div`
display: flex;
gap: 20px;
padding: 10px;
background: var(--color-background);
`
const IconColumn = styled.div`
flex: 1;
h4 {
margin-bottom: 10px;
color: var(--color-text);
font-weight: normal;
}
`
const IconList = styled.div`
height: 365px;
min-height: 365px;
padding: 10px;
background: var(--color-background-soft);
border-radius: 8px;
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
overflow-y: hidden;
`
const IconItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
margin-bottom: 8px;
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 4px;
cursor: move;
`
const IconContent = styled.div`
display: flex;
align-items: center;
gap: 10px;
.iconfont {
font-size: 16px;
color: var(--color-text);
}
span {
color: var(--color-text);
}
`
const CloseButton = styled.div`
cursor: pointer;
color: var(--color-text-2);
opacity: 0;
transition: all 0.2s;
&:hover {
color: var(--color-text);
}
${IconItem}:hover & {
opacity: 1;
}
`
const EmptyPlaceholder = styled.div`
display: flex;
flex: 1;
align-items: center;
justify-content: center;
color: var(--color-text-2);
text-align: center;
padding: 20px;
font-size: 14px;
`
export default SidebarIconsManager

View File

@ -15,7 +15,7 @@ import styled from 'styled-components'
import AboutSettings from './AboutSettings' import AboutSettings from './AboutSettings'
import DataSettings from './DataSettings/DataSettings' import DataSettings from './DataSettings/DataSettings'
import DisplaySettings from './DisplaySettings' import DisplaySettings from './DisplaySettings/DisplaySettings'
import GeneralSettings from './GeneralSettings' import GeneralSettings from './GeneralSettings'
import ModelSettings from './ModalSettings/ModelSettings' import ModelSettings from './ModalSettings/ModelSettings'
import ProvidersList from './ProviderSettings' import ProvidersList from './ProviderSettings'

View File

@ -28,7 +28,7 @@ const persistedReducer = persistReducer(
{ {
key: 'cherry-studio', key: 'cherry-studio',
storage, storage,
version: 52, version: 54,
blacklist: ['runtime'], blacklist: ['runtime'],
migrate migrate
}, },

View File

@ -9,6 +9,7 @@ import { isEmpty } from 'lodash'
import { createMigrate } from 'redux-persist' import { createMigrate } from 'redux-persist'
import { RootState } from '.' import { RootState } from '.'
import { DEFAULT_SIDEBAR_ICONS } from './settings'
const migrateConfig = { const migrateConfig = {
'2': (state: RootState) => { '2': (state: RootState) => {
@ -742,8 +743,6 @@ const migrateConfig = {
return state return state
}, },
'49': (state: RootState) => { '49': (state: RootState) => {
state.settings.showMinappIcon = true
state.settings.showFilesIcon = true
state.settings.pasteLongTextThreshold = 1500 state.settings.pasteLongTextThreshold = 1500
if (state.shortcuts) { if (state.shortcuts) {
state.shortcuts.shortcuts = [ state.shortcuts.shortcuts = [
@ -776,7 +775,7 @@ const migrateConfig = {
state.settings.topicNamingPrompt = '' state.settings.topicNamingPrompt = ''
return state return state
}, },
'52': (state: RootState) => { '54': (state: RootState) => {
if (state.shortcuts) { if (state.shortcuts) {
state.shortcuts.shortcuts.push({ state.shortcuts.shortcuts.push({
key: 'search_message', key: 'search_message',
@ -786,9 +785,10 @@ const migrateConfig = {
system: false system: false
}) })
} }
state.settings.showTranslateIcon = true state.settings.sidebarIcons = {
state.settings.showPaintingIcon = true visible: DEFAULT_SIDEBAR_ICONS,
state.settings.showKnowledgeIcon = true disabled: []
}
return state return state
} }
} }

View File

@ -4,6 +4,18 @@ import { CodeStyleVarious, LanguageVarious, ThemeMode } from '@renderer/types'
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter'
export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files'
export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [
'assistants',
'agents',
'paintings',
'translate',
'minapp',
'knowledge',
'files'
]
export interface SettingsState { export interface SettingsState {
showAssistants: boolean showAssistants: boolean
showTopics: boolean showTopics: boolean
@ -41,14 +53,13 @@ export interface SettingsState {
translateModelPrompt: string translateModelPrompt: string
autoTranslateWithSpace: boolean autoTranslateWithSpace: boolean
enableTopicNaming: boolean enableTopicNaming: boolean
// Sidebar icons
showTranslateIcon: boolean
showPaintingIcon: boolean
showMinappIcon: boolean
showKnowledgeIcon: boolean
showFilesIcon: boolean
customCss: string customCss: string
topicNamingPrompt: string topicNamingPrompt: string
// Sidebar icons
sidebarIcons: {
visible: SidebarIcon[]
disabled: SidebarIcon[]
}
} }
const initialState: SettingsState = { const initialState: SettingsState = {
@ -87,13 +98,12 @@ const initialState: SettingsState = {
translateModelPrompt: TRANSLATE_PROMPT, translateModelPrompt: TRANSLATE_PROMPT,
autoTranslateWithSpace: false, autoTranslateWithSpace: false,
enableTopicNaming: true, enableTopicNaming: true,
showTranslateIcon: true,
showPaintingIcon: true,
showMinappIcon: true,
showKnowledgeIcon: true,
showFilesIcon: true,
customCss: '', customCss: '',
topicNamingPrompt: '' topicNamingPrompt: '',
sidebarIcons: {
visible: DEFAULT_SIDEBAR_ICONS,
disabled: []
}
} }
const settingsSlice = createSlice({ const settingsSlice = createSlice({
@ -209,21 +219,6 @@ 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>) => {
state.showMinappIcon = action.payload
},
setShowKnowledgeIcon: (state, action: PayloadAction<boolean>) => {
state.showKnowledgeIcon = action.payload
},
setShowFilesIcon: (state, action: PayloadAction<boolean>) => {
state.showFilesIcon = action.payload
},
setPasteLongTextThreshold: (state, action: PayloadAction<number>) => { setPasteLongTextThreshold: (state, action: PayloadAction<number>) => {
state.pasteLongTextThreshold = action.payload state.pasteLongTextThreshold = action.payload
}, },
@ -232,6 +227,9 @@ const settingsSlice = createSlice({
}, },
setTopicNamingPrompt: (state, action: PayloadAction<string>) => { setTopicNamingPrompt: (state, action: PayloadAction<string>) => {
state.topicNamingPrompt = action.payload state.topicNamingPrompt = action.payload
},
setSidebarIcons: (state, action: PayloadAction<{ visible: SidebarIcon[]; disabled: SidebarIcon[] }>) => {
state.sidebarIcons = action.payload
} }
} }
}) })
@ -273,14 +271,10 @@ export const {
setTranslateModelPrompt, setTranslateModelPrompt,
setAutoTranslateWithSpace, setAutoTranslateWithSpace,
setEnableTopicNaming, setEnableTopicNaming,
setShowTranslateIcon,
setShowPaintingIcon,
setShowMinappIcon,
setShowKnowledgeIcon,
setShowFilesIcon,
setPasteLongTextThreshold, setPasteLongTextThreshold,
setCustomCss, setCustomCss,
setTopicNamingPrompt setTopicNamingPrompt,
setSidebarIcons
} = settingsSlice.actions } = settingsSlice.actions
export default settingsSlice.reducer export default settingsSlice.reducer

3566
yarn.lock

File diff suppressed because it is too large Load Diff