diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx
index 3f0358fa..5ff93e73 100644
--- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx
@@ -1,364 +1,38 @@
-import {
- DeleteOutlined,
- FileSearchOutlined,
- FolderOpenOutlined,
- InfoCircleOutlined,
- SaveOutlined
-} from '@ant-design/icons'
-import { Client } from '@notionhq/client'
+import { FileSearchOutlined, FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
-import MinApp from '@renderer/components/MinApp'
+import ListItem from '@renderer/components/ListItem'
import BackupPopup from '@renderer/components/Popups/BackupPopup'
import RestorePopup from '@renderer/components/Popups/RestorePopup'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useKnowledgeFiles } from '@renderer/hooks/useKnowledgeFiles'
import { reset } from '@renderer/services/BackupService'
-import { RootState, useAppDispatch } from '@renderer/store'
-import {
- setmarkdownExportPath,
- setNotionApiKey,
- setNotionAutoSplit,
- setNotionDatabaseID,
- setNotionPageNameKey,
- setNotionSplitSize,
- setYuqueRepoId,
- setYuqueToken,
- setYuqueUrl
-} from '@renderer/store/settings'
import { AppInfo } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
-import { Button, InputNumber, Modal, Switch, Tooltip, Typography } from 'antd'
-import Input from 'antd/es/input/Input'
+import { Button, Modal, Typography } from 'antd'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import { useSelector } from 'react-redux'
import styled from 'styled-components'
-import {
- SettingContainer,
- SettingDivider,
- SettingGroup,
- SettingHelpText,
- SettingRow,
- SettingRowTitle,
- SettingTitle
-} from '..'
+import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
+import MarkdownExportSettings from './MarkdownExportSettings'
+import NotionSettings from './NotionSettings'
import WebDavSettings from './WebDavSettings'
-
-// 新增的 MarkdownExportSettings 组件
-const MarkdownExportSettings: FC = () => {
- const { t } = useTranslation()
- const { theme } = useTheme()
- const dispatch = useAppDispatch()
-
- const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath)
-
- const handleSelectFolder = async () => {
- const path = await window.api.file.selectFolder()
- if (path) {
- dispatch(setmarkdownExportPath(path))
- }
- }
-
- const handleClearPath = () => {
- dispatch(setmarkdownExportPath(null))
- }
-
- return (
-
- {t('settings.data.markdown_export.title')}
-
-
- {t('settings.data.markdown_export.path')}
-
-
- ) : null
- }
- />
- }>
- {t('settings.data.markdown_export.select')}
-
-
-
-
- {t('settings.data.markdown_export.help')}
-
-
- )
-}
-
-// 新增的 NotionSettings 组件
-const NotionSettings: FC = () => {
- const { t } = useTranslation()
- const { theme } = useTheme()
- const dispatch = useAppDispatch()
-
- const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey)
- const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID)
- const notionPageNameKey = useSelector((state: RootState) => state.settings.notionPageNameKey)
- const notionAutoSplit = useSelector((state: RootState) => state.settings.notionAutoSplit)
- const notionSplitSize = useSelector((state: RootState) => state.settings.notionSplitSize)
-
- const handleNotionTokenChange = (e: React.ChangeEvent) => {
- dispatch(setNotionApiKey(e.target.value))
- }
-
- const handleNotionDatabaseIdChange = (e: React.ChangeEvent) => {
- dispatch(setNotionDatabaseID(e.target.value))
- }
-
- const handleNotionPageNameKeyChange = (e: React.ChangeEvent) => {
- dispatch(setNotionPageNameKey(e.target.value))
- }
-
- const handleNotionConnectionCheck = () => {
- if (notionApiKey === null) {
- window.message.error(t('settings.data.notion.check.empty_api_key'))
- return
- }
- if (notionDatabaseID === null) {
- window.message.error(t('settings.data.notion.check.empty_database_id'))
- return
- }
- const notion = new Client({ auth: notionApiKey })
- notion.databases
- .retrieve({
- database_id: notionDatabaseID
- })
- .then((result) => {
- if (result) {
- window.message.success(t('settings.data.notion.check.success'))
- } else {
- window.message.error(t('settings.data.notion.check.fail'))
- }
- })
- .catch(() => {
- window.message.error(t('settings.data.notion.check.error'))
- })
- }
-
- const handleNotionTitleClick = () => {
- MinApp.start({
- id: 'notion-help',
- name: 'Notion Help',
- url: 'https://docs.cherry-ai.com/advanced-basic/notion'
- })
- }
-
- const handleNotionAutoSplitChange = (checked: boolean) => {
- dispatch(setNotionAutoSplit(checked))
- }
-
- const handleNotionSplitSizeChange = (value: number | null) => {
- if (value !== null) {
- dispatch(setNotionSplitSize(value))
- }
- }
-
- return (
-
-
- {t('settings.data.notion.title')}
-
-
-
-
-
-
- {t('settings.data.notion.database_id')}
-
-
-
-
-
-
- {t('settings.data.notion.page_name_key')}
-
-
-
-
-
-
- {t('settings.data.notion.api_key')}
-
-
-
-
-
- {/* 添加分割线 */}
-
-
-
-
- {t('settings.data.notion.auto_split')}
-
-
-
-
-
-
- {notionAutoSplit && (
- <>
-
-
- {t('settings.data.notion.split_size')}
-
-
-
- {t('settings.data.notion.split_size_help')}
-
- >
- )}
-
- )
-}
-
-const YuqueSettings: FC = () => {
- const { t } = useTranslation()
- const { theme } = useTheme()
- const dispatch = useAppDispatch()
-
- const yuqueToken = useSelector((state: RootState) => state.settings.yuqueToken)
- const yuqueUrl = useSelector((state: RootState) => state.settings.yuqueUrl)
-
- const handleYuqueTokenChange = (e: React.ChangeEvent) => {
- dispatch(setYuqueToken(e.target.value))
- }
-
- const handleYuqueRepoUrlChange = (e: React.ChangeEvent) => {
- dispatch(setYuqueUrl(e.target.value))
- }
-
- const handleYuqueConnectionCheck = async () => {
- if (!yuqueToken) {
- window.message.error(t('settings.data.yuque.check.empty_token'))
- return
- }
- if (!yuqueUrl) {
- window.message.error(t('settings.data.yuque.check.empty_url'))
- return
- }
-
- const response = await fetch('https://www.yuque.com/api/v2/hello', {
- headers: {
- 'X-Auth-Token': yuqueToken
- }
- })
-
- if (!response.ok) {
- window.message.error(t('settings.data.yuque.check.fail'))
- return
- }
- const yuqueSlug = yuqueUrl.replace('https://www.yuque.com/', '')
- const repoIDResponse = await fetch(`https://www.yuque.com/api/v2/repos/${yuqueSlug}`, {
- headers: {
- 'X-Auth-Token': yuqueToken
- }
- })
- if (!repoIDResponse.ok) {
- window.message.error(t('settings.data.yuque.check.fail'))
- return
- }
- const data = await repoIDResponse.json()
- dispatch(setYuqueRepoId(data.data.id))
- window.message.success(t('settings.data.yuque.check.success'))
- }
-
- const handleYuqueHelpClick = () => {
- MinApp.start({
- id: 'yuque-help',
- name: 'Yuque Help',
- url: 'https://www.yuque.com/settings/tokens'
- })
- }
-
- return (
-
- {t('settings.data.yuque.title')}
-
-
- {t('settings.data.yuque.repo_url')}
-
-
-
-
-
-
-
- {t('settings.data.yuque.token')}
-
-
-
-
-
-
-
-
-
-
- )
-}
+import YuqueSettings from './YuqueSettings'
const DataSettings: FC = () => {
const { t } = useTranslation()
const [appInfo, setAppInfo] = useState()
const { size, removeAllFiles } = useKnowledgeFiles()
const { theme } = useTheme()
+ const [menu, setMenu] = useState('data')
+
+ const menuItems = [
+ { key: 'data', title: 'settings.data.data.title' },
+ { key: 'webdav', title: 'settings.data.webdav.title' },
+ { key: 'markdown_export', title: 'settings.data.markdown_export.title' },
+ { key: 'notion', title: 'settings.data.notion.title' },
+ { key: 'yuque', title: 'settings.data.yuque.title' }
+ ]
useEffect(() => {
window.api.getAppInfo().then(setAppInfo)
@@ -411,78 +85,91 @@ const DataSettings: FC = () => {
}
return (
-
-
- {t('settings.data.title')}
-
-
- {t('settings.general.backup.title')}
-
- }>
- {t('settings.general.backup.button')}
-
- }>
- {t('settings.general.restore.button')}
-
-
-
-
-
- {t('settings.general.reset.title')}
-
-
-
-
-
-
-
-
-
-
-
-
- {t('settings.data.data.title')}
-
-
- {t('settings.data.app_data')}
-
- {appInfo?.appDataPath}
- handleOpenPath(appInfo?.appDataPath)} />
-
-
-
-
- {t('settings.data.app_logs')}
-
- {appInfo?.logsPath}
- handleOpenPath(appInfo?.logsPath)} />
-
-
-
-
- {t('settings.data.app_knowledge')}
-
-
-
-
-
-
- {t('settings.data.clear_cache.title')}
-
-
-
-
-
-
+
+
+ {menuItems.map((item) => (
+ setMenu(item.key)} />
+ ))}
+
+
+ {menu === 'data' && (
+ <>
+
+ {t('settings.data.title')}
+
+
+ {t('settings.general.backup.title')}
+
+ }>
+ {t('settings.general.backup.button')}
+
+ }>
+ {t('settings.general.restore.button')}
+
+
+
+
+
+ {t('settings.general.reset.title')}
+
+
+
+
+
+
+ {t('settings.data.data.title')}
+
+
+ {t('settings.data.app_data')}
+
+ {appInfo?.appDataPath}
+ handleOpenPath(appInfo?.appDataPath)} />
+
+
+
+
+ {t('settings.data.app_logs')}
+
+ {appInfo?.logsPath}
+ handleOpenPath(appInfo?.logsPath)} />
+
+
+
+
+ {t('settings.data.app_knowledge')}
+
+
+
+
+
+
+ {t('settings.data.clear_cache.title')}
+
+
+
+
+
+ >
+ )}
+ {menu === 'webdav' && }
+ {menu === 'markdown_export' && }
+ {menu === 'notion' && }
+ {menu === 'yuque' && }
+
+
)
}
+const Container = styled(HStack)`
+ flex: 1;
+`
+
const StyledIcon = styled(FileSearchOutlined)`
color: var(--color-text-2);
cursor: pointer;
@@ -493,4 +180,14 @@ const StyledIcon = styled(FileSearchOutlined)`
}
`
+const MenuList = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ width: var(--settings-width);
+ padding: 12px;
+ border-right: 0.5px solid var(--color-border);
+ height: 100%;
+`
+
export default DataSettings
diff --git a/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx b/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx
new file mode 100644
index 00000000..f2e1cec4
--- /dev/null
+++ b/src/renderer/src/pages/settings/DataSettings/MarkdownExportSettings.tsx
@@ -0,0 +1,63 @@
+import { DeleteOutlined, FolderOpenOutlined } from '@ant-design/icons'
+import { HStack } from '@renderer/components/Layout'
+import { useTheme } from '@renderer/context/ThemeProvider'
+import { RootState, useAppDispatch } from '@renderer/store'
+import { setmarkdownExportPath } from '@renderer/store/settings'
+import { Button } from 'antd'
+import Input from 'antd/es/input/Input'
+import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSelector } from 'react-redux'
+
+import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
+
+const MarkdownExportSettings: FC = () => {
+ const { t } = useTranslation()
+ const { theme } = useTheme()
+ const dispatch = useAppDispatch()
+
+ const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath)
+
+ const handleSelectFolder = async () => {
+ const path = await window.api.file.selectFolder()
+ if (path) {
+ dispatch(setmarkdownExportPath(path))
+ }
+ }
+
+ const handleClearPath = () => {
+ dispatch(setmarkdownExportPath(null))
+ }
+
+ return (
+
+ {t('settings.data.markdown_export.title')}
+
+
+ {t('settings.data.markdown_export.path')}
+
+
+ ) : null
+ }
+ />
+ }>
+ {t('settings.data.markdown_export.select')}
+
+
+
+
+ {t('settings.data.markdown_export.help')}
+
+
+ )
+}
+
+export default MarkdownExportSettings
diff --git a/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx
new file mode 100644
index 00000000..bb15ce83
--- /dev/null
+++ b/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx
@@ -0,0 +1,178 @@
+import { InfoCircleOutlined } from '@ant-design/icons'
+import { Client } from '@notionhq/client'
+import { HStack } from '@renderer/components/Layout'
+import MinApp from '@renderer/components/MinApp'
+import { useTheme } from '@renderer/context/ThemeProvider'
+import { RootState, useAppDispatch } from '@renderer/store'
+import {
+ setNotionApiKey,
+ setNotionAutoSplit,
+ setNotionDatabaseID,
+ setNotionPageNameKey,
+ setNotionSplitSize
+} from '@renderer/store/settings'
+import { Button, InputNumber, Switch, Tooltip } from 'antd'
+import Input from 'antd/es/input/Input'
+import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSelector } from 'react-redux'
+
+import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
+const NotionSettings: FC = () => {
+ const { t } = useTranslation()
+ const { theme } = useTheme()
+ const dispatch = useAppDispatch()
+
+ const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey)
+ const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID)
+ const notionPageNameKey = useSelector((state: RootState) => state.settings.notionPageNameKey)
+ const notionAutoSplit = useSelector((state: RootState) => state.settings.notionAutoSplit)
+ const notionSplitSize = useSelector((state: RootState) => state.settings.notionSplitSize)
+
+ const handleNotionTokenChange = (e: React.ChangeEvent) => {
+ dispatch(setNotionApiKey(e.target.value))
+ }
+
+ const handleNotionDatabaseIdChange = (e: React.ChangeEvent) => {
+ dispatch(setNotionDatabaseID(e.target.value))
+ }
+
+ const handleNotionPageNameKeyChange = (e: React.ChangeEvent) => {
+ dispatch(setNotionPageNameKey(e.target.value))
+ }
+
+ const handleNotionConnectionCheck = () => {
+ if (notionApiKey === null) {
+ window.message.error(t('settings.data.notion.check.empty_api_key'))
+ return
+ }
+ if (notionDatabaseID === null) {
+ window.message.error(t('settings.data.notion.check.empty_database_id'))
+ return
+ }
+ const notion = new Client({ auth: notionApiKey })
+ notion.databases
+ .retrieve({
+ database_id: notionDatabaseID
+ })
+ .then((result) => {
+ if (result) {
+ window.message.success(t('settings.data.notion.check.success'))
+ } else {
+ window.message.error(t('settings.data.notion.check.fail'))
+ }
+ })
+ .catch(() => {
+ window.message.error(t('settings.data.notion.check.error'))
+ })
+ }
+
+ const handleNotionTitleClick = () => {
+ MinApp.start({
+ id: 'notion-help',
+ name: 'Notion Help',
+ url: 'https://docs.cherry-ai.com/advanced-basic/notion'
+ })
+ }
+
+ const handleNotionAutoSplitChange = (checked: boolean) => {
+ dispatch(setNotionAutoSplit(checked))
+ }
+
+ const handleNotionSplitSizeChange = (value: number | null) => {
+ if (value !== null) {
+ dispatch(setNotionSplitSize(value))
+ }
+ }
+
+ return (
+
+
+ {t('settings.data.notion.title')}
+
+
+
+
+
+
+ {t('settings.data.notion.database_id')}
+
+
+
+
+
+
+ {t('settings.data.notion.page_name_key')}
+
+
+
+
+
+
+ {t('settings.data.notion.api_key')}
+
+
+
+
+
+ {/* 添加分割线 */}
+
+
+
+
+ {t('settings.data.notion.auto_split')}
+
+
+
+
+
+
+ {notionAutoSplit && (
+ <>
+
+
+ {t('settings.data.notion.split_size')}
+
+
+
+ {t('settings.data.notion.split_size_help')}
+
+ >
+ )}
+
+ )
+}
+
+export default NotionSettings
diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
index dd9033dc..ba87dbe8 100644
--- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
@@ -1,5 +1,6 @@
import { FolderOpenOutlined, SaveOutlined, SyncOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
+import { useTheme } from '@renderer/context/ThemeProvider'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
@@ -17,7 +18,7 @@ import dayjs from 'dayjs'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import { SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '..'
+import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
const WebDavSettings: FC = () => {
const {
@@ -39,6 +40,7 @@ const WebDavSettings: FC = () => {
const [restoring, setRestoring] = useState(false)
const dispatch = useAppDispatch()
+ const { theme } = useTheme()
const { t } = useTranslation()
@@ -112,7 +114,7 @@ const WebDavSettings: FC = () => {
}
return (
- <>
+
{t('settings.data.webdav.title')}
@@ -196,7 +198,7 @@ const WebDavSettings: FC = () => {
>
)}
- >
+
)
}
diff --git a/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx b/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx
new file mode 100644
index 00000000..bee9f418
--- /dev/null
+++ b/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx
@@ -0,0 +1,116 @@
+import { InfoCircleOutlined } from '@ant-design/icons'
+import { HStack } from '@renderer/components/Layout'
+import MinApp from '@renderer/components/MinApp'
+import { useTheme } from '@renderer/context/ThemeProvider'
+import { RootState, useAppDispatch } from '@renderer/store'
+import { setYuqueRepoId, setYuqueToken, setYuqueUrl } from '@renderer/store/settings'
+import { Button, Tooltip } from 'antd'
+import Input from 'antd/es/input/Input'
+import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSelector } from 'react-redux'
+
+import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
+
+const YuqueSettings: FC = () => {
+ const { t } = useTranslation()
+ const { theme } = useTheme()
+ const dispatch = useAppDispatch()
+
+ const yuqueToken = useSelector((state: RootState) => state.settings.yuqueToken)
+ const yuqueUrl = useSelector((state: RootState) => state.settings.yuqueUrl)
+
+ const handleYuqueTokenChange = (e: React.ChangeEvent) => {
+ dispatch(setYuqueToken(e.target.value))
+ }
+
+ const handleYuqueRepoUrlChange = (e: React.ChangeEvent) => {
+ dispatch(setYuqueUrl(e.target.value))
+ }
+
+ const handleYuqueConnectionCheck = async () => {
+ if (!yuqueToken) {
+ window.message.error(t('settings.data.yuque.check.empty_token'))
+ return
+ }
+ if (!yuqueUrl) {
+ window.message.error(t('settings.data.yuque.check.empty_url'))
+ return
+ }
+
+ const response = await fetch('https://www.yuque.com/api/v2/hello', {
+ headers: {
+ 'X-Auth-Token': yuqueToken
+ }
+ })
+
+ if (!response.ok) {
+ window.message.error(t('settings.data.yuque.check.fail'))
+ return
+ }
+ const yuqueSlug = yuqueUrl.replace('https://www.yuque.com/', '')
+ const repoIDResponse = await fetch(`https://www.yuque.com/api/v2/repos/${yuqueSlug}`, {
+ headers: {
+ 'X-Auth-Token': yuqueToken
+ }
+ })
+ if (!repoIDResponse.ok) {
+ window.message.error(t('settings.data.yuque.check.fail'))
+ return
+ }
+ const data = await repoIDResponse.json()
+ dispatch(setYuqueRepoId(data.data.id))
+ window.message.success(t('settings.data.yuque.check.success'))
+ }
+
+ const handleYuqueHelpClick = () => {
+ MinApp.start({
+ id: 'yuque-help',
+ name: 'Yuque Help',
+ url: 'https://www.yuque.com/settings/tokens'
+ })
+ }
+
+ return (
+
+ {t('settings.data.yuque.title')}
+
+
+ {t('settings.data.yuque.repo_url')}
+
+
+
+
+
+
+
+ {t('settings.data.yuque.token')}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default YuqueSettings