refactor: enhance preference management and update component integrations

- Updated preference handling to utilize the usePreference hook across various components, improving consistency and maintainability.
- Introduced new preference types for ProxyMode and MultiModelFoldDisplayMode to enhance type safety.
- Refactored components to replace useSettings with usePreference, streamlining preference management.
- Cleaned up unused code and comments related to previous settings for better clarity.
- Updated auto-generated preference mappings to reflect recent changes in preference structure.
This commit is contained in:
fullex 2025-09-02 22:11:15 +08:00
parent 5d789ef394
commit 1b57ffeb56
28 changed files with 538 additions and 671 deletions

View File

@ -55,3 +55,7 @@ export type SidebarIcon =
| 'notes'
export type AssistantIconType = 'model' | 'emoji' | 'none'
export type ProxyMode = 'system' | 'custom' | 'none'
export type MultiModelFoldDisplayMode = 'expanded' | 'compact'

View File

@ -1,6 +1,6 @@
/**
* Auto-generated preferences configuration
* Generated at: 2025-09-02T08:08:51.242Z
* Generated at: 2025-09-02T11:59:32.955Z
*
* This file is automatically generated from classification.json
* To update this file, modify classification.json and run:
@ -13,6 +13,8 @@ import { TRANSLATE_PROMPT } from '@shared/config/prompts'
import type {
AssistantIconType,
AssistantTabSortType,
MultiModelFoldDisplayMode,
ProxyMode,
SelectionActionItem,
SelectionFilterMode,
SelectionTriggerMode,
@ -52,15 +54,15 @@ export interface PreferencesType {
// redux/settings/enableDataCollection
'app.privacy.data_collection.enabled': boolean
// redux/settings/proxyBypassRules
'app.proxy.bypass_rules': string | null
'app.proxy.bypass_rules': string
// redux/settings/proxyMode
'app.proxy.mode': string
'app.proxy.mode': ProxyMode
// redux/settings/proxyUrl
'app.proxy.url': string | null
'app.proxy.url': string
// redux/settings/enableSpellCheck
'app.spell_check.enabled': boolean
// redux/settings/spellCheckLanguages
'app.spell_check.languages': unknown[]
'app.spell_check.languages': string[]
// redux/settings/tray
'app.tray.enabled': boolean
// redux/settings/trayOnClose
@ -138,9 +140,11 @@ export interface PreferencesType {
// redux/settings/fontSize
'chat.message.font_size': number
// redux/settings/mathEngine
'chat.message.math_engine': string
'chat.message.math.engine': string
// redux/settings/mathEnableSingleDollar
'chat.message.math.single_dollar': boolean
// redux/settings/foldDisplayMode
'chat.message.multi_model.fold_display_mode': string
'chat.message.multi_model.fold_display_mode': MultiModelFoldDisplayMode
// redux/settings/gridColumns
'chat.message.multi_model.grid_columns': number
// redux/settings/gridPopoverTrigger
@ -149,8 +153,12 @@ export interface PreferencesType {
'chat.message.multi_model.style': string
// redux/settings/messageNavigation
'chat.message.navigation_mode': string
// redux/settings/renderInputMessageAsMarkdown
'chat.message.render_as_markdown': boolean
// redux/settings/showMessageDivider
'chat.message.show_divider': boolean
// redux/settings/showMessageOutline
'chat.message.show_outline': boolean
// redux/settings/showPrompt
'chat.message.show_prompt': boolean
// redux/settings/messageStyle
@ -408,9 +416,9 @@ export const DefaultPreferences: PreferencesType = {
'app.notification.backup.enabled': false,
'app.notification.knowledge.enabled': false,
'app.privacy.data_collection.enabled': false,
'app.proxy.bypass_rules': null,
'app.proxy.bypass_rules': '',
'app.proxy.mode': 'system',
'app.proxy.url': null,
'app.proxy.url': '',
'app.spell_check.enabled': false,
'app.spell_check.languages': [],
'app.tray.enabled': true,
@ -451,13 +459,16 @@ export const DefaultPreferences: PreferencesType = {
'chat.message.confirm_regenerate': true,
'chat.message.font': 'system',
'chat.message.font_size': 14,
'chat.message.math_engine': 'KaTeX',
'chat.message.math.engine': 'KaTeX',
'chat.message.math.single_dollar': true,
'chat.message.multi_model.fold_display_mode': 'expanded',
'chat.message.multi_model.grid_columns': 2,
'chat.message.multi_model.grid_popover_trigger': 'click',
'chat.message.multi_model.style': 'horizontal',
'chat.message.navigation_mode': 'none',
'chat.message.render_as_markdown': false,
'chat.message.show_divider': true,
'chat.message.show_outline': false,
'chat.message.show_prompt': true,
'chat.message.style': 'plain',
'chat.message.thought.auto_collapse': true,
@ -642,8 +653,8 @@ export const DefaultPreferences: PreferencesType = {
/**
* :
* - 总配置项: 181
* - 总配置项: 184
* - electronStore项: 1
* - redux项: 180
* - redux项: 183
* - localStorage项: 0
*/

View File

@ -75,7 +75,8 @@ vi.mock('@logger', () => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
silly: vi.fn()
silly: vi.fn(),
verbose: vi.fn()
})
}
}))

View File

@ -10,6 +10,7 @@ import {
PushpinOutlined,
ReloadOutlined
} from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { isLinux, isMac, isWin } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
@ -19,10 +20,7 @@ import { useMinapps } from '@renderer/hooks/useMinapps'
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { useTimer } from '@renderer/hooks/useTimer'
import { useAppDispatch } from '@renderer/store'
import { setMinappsOpenLinkExternal } from '@renderer/store/settings'
import { MinAppType } from '@renderer/types'
import { delay } from '@renderer/utils'
import { Alert, Avatar, Button, Drawer, Tooltip } from 'antd'
@ -142,12 +140,12 @@ const GoogleLoginTip = ({
/** The main container for MinApp popup */
const MinappPopupContainer: React.FC = () => {
const { openedKeepAliveMinapps, openedOneOffMinapp, currentMinappId, minappShow } = useRuntime()
const [minappsOpenLinkExternal, setMinappsOpenLinkExternal] = usePreference('feature.minapp.open_link_external')
const { closeMinapp, hideMinappPopup } = useMinappPopup()
const { pinned, updatePinnedMinapps } = useMinapps()
const { t } = useTranslation()
const backgroundColor = useNavBackgroundColor()
const { isTopNavbar } = useNavbarPosition()
const dispatch = useAppDispatch()
/** control the drawer open or close */
const [isPopupShow, setIsPopupShow] = useState(true)
@ -166,7 +164,6 @@ const MinappPopupContainer: React.FC = () => {
/** indicate whether the webview has loaded */
const webviewLoadedRefs = useRef<Map<string, boolean>>(new Map())
/** whether the minapps open link external is enabled */
const { minappsOpenLinkExternal } = useSettings()
const { isLeftNavbar } = useNavbarPosition()
@ -347,7 +344,7 @@ const MinappPopupContainer: React.FC = () => {
/** set the open external status */
const handleToggleOpenExternal = () => {
dispatch(setMinappsOpenLinkExternal(!minappsOpenLinkExternal))
setMinappsOpenLinkExternal(!minappsOpenLinkExternal)
}
/** navigate back in webview history */

View File

@ -5,7 +5,6 @@ import useAvatar from '@renderer/hooks/useAvatar'
import ImageStorage from '@renderer/services/ImageStorage'
import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime'
import { setUserName } from '@renderer/store/settings'
import { compressImage, isEmoji } from '@renderer/utils'
import { Avatar, Dropdown, Input, Modal, Popover, Upload } from 'antd'
import React, { useState } from 'react'
@ -21,11 +20,12 @@ interface Props {
}
const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [userName, setUserName] = usePreference('app.user.name')
const [open, setOpen] = useState(true)
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false)
const [dropdownOpen, setDropdownOpen] = useState(false)
const { t } = useTranslation()
const [userName] = usePreference('app.user.name')
const dispatch = useAppDispatch()
const avatar = useAvatar()
@ -170,7 +170,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<Input
placeholder={t('settings.general.user_name.placeholder')}
value={userName}
onChange={(e) => dispatch(setUserName(e.target.value.trim()))}
onChange={(e) => setUserName(e.target.value.trim())}
style={{ flex: 1, textAlign: 'center', width: '100%' }}
maxLength={30}
/>

View File

@ -1,68 +1,56 @@
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
import store, { useAppSelector } from '@renderer/store'
import {
setAutoCheckUpdate as _setAutoCheckUpdate,
setLaunchOnBoot,
setLaunchToTray,
setSendMessageShortcut as _setSendMessageShortcut,
setTargetLanguage,
setTestChannel as _setTestChannel,
setTestPlan as _setTestPlan,
SettingsState,
setTray as _setTray,
setTrayOnClose
SettingsState
// setWindowStyle
} from '@renderer/store/settings'
import { TranslateLanguageCode } from '@renderer/types'
import { UpgradeChannel } from '@shared/config/constant'
import type { SendMessageShortcut } from '@shared/data/preferenceTypes'
export function useSettings() {
const settings = useAppSelector((state) => state.settings)
const dispatch = useAppDispatch()
// const dispatch = useAppDispatch()
return {
...settings,
setSendMessageShortcut(shortcut: SendMessageShortcut) {
dispatch(_setSendMessageShortcut(shortcut))
},
...settings
// setSendMessageShortcut(shortcut: SendMessageShortcut) {
// dispatch(_setSendMessageShortcut(shortcut))
// },
setLaunch(isLaunchOnBoot: boolean | undefined, isLaunchToTray: boolean | undefined = undefined) {
if (isLaunchOnBoot !== undefined) {
dispatch(setLaunchOnBoot(isLaunchOnBoot))
window.api.setLaunchOnBoot(isLaunchOnBoot)
}
// setLaunch(isLaunchOnBoot: boolean | undefined, isLaunchToTray: boolean | undefined = undefined) {
// if (isLaunchOnBoot !== undefined) {
// dispatch(setLaunchOnBoot(isLaunchOnBoot))
// window.api.setLaunchOnBoot(isLaunchOnBoot)
// }
if (isLaunchToTray !== undefined) {
dispatch(setLaunchToTray(isLaunchToTray))
window.api.setLaunchToTray(isLaunchToTray)
}
},
// if (isLaunchToTray !== undefined) {
// dispatch(setLaunchToTray(isLaunchToTray))
// window.api.setLaunchToTray(isLaunchToTray)
// }
// },
setTray(isShowTray: boolean | undefined, isTrayOnClose: boolean | undefined = undefined) {
if (isShowTray !== undefined) {
dispatch(_setTray(isShowTray))
window.api.setTray(isShowTray)
}
if (isTrayOnClose !== undefined) {
dispatch(setTrayOnClose(isTrayOnClose))
window.api.setTrayOnClose(isTrayOnClose)
}
},
// setTray(isShowTray: boolean | undefined, isTrayOnClose: boolean | undefined = undefined) {
// if (isShowTray !== undefined) {
// dispatch(_setTray(isShowTray))
// window.api.setTray(isShowTray)
// }
// if (isTrayOnClose !== undefined) {
// dispatch(setTrayOnClose(isTrayOnClose))
// window.api.setTrayOnClose(isTrayOnClose)
// }
// },
setAutoCheckUpdate(isAutoUpdate: boolean) {
dispatch(_setAutoCheckUpdate(isAutoUpdate))
window.api.setAutoUpdate(isAutoUpdate)
},
// setAutoCheckUpdate(isAutoUpdate: boolean) {
// dispatch(_setAutoCheckUpdate(isAutoUpdate))
// window.api.setAutoUpdate(isAutoUpdate)
// },
setTestPlan(isTestPlan: boolean) {
dispatch(_setTestPlan(isTestPlan))
window.api.setTestPlan(isTestPlan)
},
// setTestPlan(isTestPlan: boolean) {
// dispatch(_setTestPlan(isTestPlan))
// window.api.setTestPlan(isTestPlan)
// },
setTestChannel(channel: UpgradeChannel) {
dispatch(_setTestChannel(channel))
window.api.setTestChannel(channel)
},
// setTestChannel(channel: UpgradeChannel) {
// dispatch(_setTestChannel(channel))
// window.api.setTestChannel(channel)
// },
// setTheme(theme: ThemeMode) {
// dispatch(setTheme(theme))
@ -70,9 +58,9 @@ export function useSettings() {
// setWindowStyle(windowStyle: 'transparent' | 'opaque') {
// dispatch(setWindowStyle(windowStyle))
// },
setTargetLanguage(targetLanguage: TranslateLanguageCode) {
dispatch(setTargetLanguage(targetLanguage))
}
// setTargetLanguage(targetLanguage: TranslateLanguageCode) {
// dispatch(setTargetLanguage(targetLanguage))
// }
// setTopicPosition(topicPosition: 'left' | 'right') {
// dispatch(setTopicPosition(topicPosition))
// },

View File

@ -7,8 +7,6 @@ import { modelGenerating } from '@renderer/hooks/useRuntime'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppDispatch } from '@renderer/store'
import { setNarrowMode } from '@renderer/store/settings'
import { Assistant, Topic } from '@renderer/types'
import { Tooltip } from 'antd'
import { t } from 'i18next'
@ -30,14 +28,13 @@ interface Props {
}
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTopic, setActiveTopic }) => {
const [topicPosition] = usePreference('topic.position')
const [narrowMode, setNarrowMode] = usePreference('chat.narrow_mode')
const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants()
const [topicPosition] = usePreference('topic.position')
const [narrowMode] = usePreference('chat.narrow_mode')
const { showTopics, toggleShowTopics } = useShowTopics()
const dispatch = useAppDispatch()
useShortcut('toggle_show_assistants', toggleShowAssistants)
@ -55,7 +52,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
const handleNarrowModeToggle = async () => {
await modelGenerating()
dispatch(setNarrowMode(!narrowMode))
setNarrowMode(!narrowMode)
}
const onShowAssistantsDrawer = () => {

View File

@ -1,31 +1,26 @@
import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { setFoldDisplayMode } from '@renderer/store/settings'
import type { Model } from '@renderer/types'
import { AssistantMessageStatus, type Message } from '@renderer/types/newMessage'
import { lightbulbSoftVariants } from '@renderer/utils/motionVariants'
import type { MultiModelFoldDisplayMode } from '@shared/data/preferenceTypes'
import { Avatar, Segmented as AntdSegmented, Tooltip } from 'antd'
import { motion } from 'motion/react'
import { FC, memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface MessageGroupModelListProps {
messages: Message[]
selectMessageId: string
setSelectedMessage: (message: Message) => void
}
type DisplayMode = 'compact' | 'expanded'
const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selectMessageId, setSelectedMessage }) => {
const dispatch = useAppDispatch()
const [foldDisplayMode, setFoldDisplayMode] = usePreference('chat.message.multi_model.fold_display_mode')
const { t } = useTranslation()
const { foldDisplayMode } = useSettings()
const isCompact = foldDisplayMode === 'compact'
const isMessageProcessing = useCallback((message: Message) => {
@ -80,7 +75,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
mouseLeaveDelay={0}>
<DisplayModeToggle
displayMode={foldDisplayMode}
onClick={() => dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}>
onClick={() => setFoldDisplayMode(isCompact ? 'expanded' : 'compact')}>
{isCompact ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
</DisplayModeToggle>
</Tooltip>
@ -115,7 +110,7 @@ const Container = styled(HStack)`
margin-left: 4px;
`
const DisplayModeToggle = styled.div<{ displayMode: DisplayMode }>`
const DisplayModeToggle = styled.div<{ displayMode: MultiModelFoldDisplayMode }>`
display: flex;
cursor: pointer;
padding: 2px 6px 3px 6px;
@ -128,7 +123,7 @@ const DisplayModeToggle = styled.div<{ displayMode: DisplayMode }>`
}
`
const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
const ModelsContainer = styled(Scrollbar)<{ $displayMode: MultiModelFoldDisplayMode }>`
display: flex;
flex-direction: ${(props) => (props.$displayMode === 'expanded' ? 'column' : 'row')};
justify-content: ${(props) => (props.$displayMode === 'expanded' ? 'space-between' : 'flex-start')};

View File

@ -1,20 +1,18 @@
import { SettingOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import Selector from '@renderer/components/Selector'
import { useSettings } from '@renderer/hooks/useSettings'
import { SettingDivider } from '@renderer/pages/settings'
import { SettingRow } from '@renderer/pages/settings'
import { useAppDispatch } from '@renderer/store'
import { setGridColumns, setGridPopoverTrigger } from '@renderer/store/settings'
import { Col, Row, Slider } from 'antd'
import { Popover } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
const MessageGroupSettings: FC = () => {
const dispatch = useAppDispatch()
const [gridPopoverTrigger, setGridPopoverTrigger] = usePreference('chat.message.multi_model.grid_popover_trigger')
const [gridColumns, setGridColumns] = usePreference('chat.message.multi_model.grid_columns')
const { t } = useTranslation()
const { gridColumns, gridPopoverTrigger } = useSettings()
const [gridColumnsValue, setGridColumnsValue] = useState(gridColumns)
return (
@ -28,7 +26,7 @@ const MessageGroupSettings: FC = () => {
<Selector
size={14}
value={gridPopoverTrigger || 'hover'}
onChange={(value) => dispatch(setGridPopoverTrigger(value as 'hover' | 'click'))}
onChange={(value) => setGridPopoverTrigger(value as 'hover' | 'click')}
options={[
{ label: t('settings.messages.grid_popover_trigger.hover'), value: 'hover' },
{ label: t('settings.messages.grid_popover_trigger.click'), value: 'click' }
@ -45,7 +43,7 @@ const MessageGroupSettings: FC = () => {
value={gridColumnsValue}
style={{ width: '100%' }}
onChange={(value) => setGridColumnsValue(value)}
onChangeComplete={(value) => dispatch(setGridColumns(value))}
onChangeComplete={(value) => setGridColumns(value)}
min={2}
max={6}
step={1}

View File

@ -1,3 +1,4 @@
import { usePreference } from '@data/hooks/usePreference'
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import SearchPopup from '@renderer/components/Popups/SearchPopup'
@ -5,12 +6,9 @@ import { isMac } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { modelGenerating } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppDispatch } from '@renderer/store'
import { setNarrowMode } from '@renderer/store/settings'
import { Assistant, Topic } from '@renderer/types'
import { Tooltip } from 'antd'
import { t } from 'i18next'
@ -22,7 +20,6 @@ import styled from 'styled-components'
import AssistantsDrawer from './components/AssistantsDrawer'
import SelectModelButton from './components/SelectModelButton'
import UpdateAppButton from './components/UpdateAppButton'
interface Props {
activeAssistant: Assistant
activeTopic: Topic
@ -32,12 +29,13 @@ interface Props {
}
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTopic, setActiveTopic }) => {
const [narrowMode, setNarrowMode] = usePreference('chat.narrow_mode')
const [topicPosition] = usePreference('topic.position')
const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants()
const isFullscreen = useFullscreen()
const { topicPosition, narrowMode } = useSettings()
const { showTopics, toggleShowTopics } = useShowTopics()
const dispatch = useAppDispatch()
useShortcut('toggle_show_assistants', toggleShowAssistants)
@ -55,7 +53,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
const handleNarrowModeToggle = async () => {
await modelGenerating()
dispatch(setNarrowMode(!narrowMode))
setNarrowMode(!narrowMode)
}
const onShowAssistantsDrawer = () => {

View File

@ -1,3 +1,4 @@
import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference'
import EditableNumber from '@renderer/components/EditableNumber'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
@ -9,41 +10,11 @@ import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useProvider } from '@renderer/hooks/useProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import useTranslate from '@renderer/hooks/useTranslate'
import { SettingDivider, SettingRow, SettingRowTitle } from '@renderer/pages/settings'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup'
import { getDefaultModel } from '@renderer/services/AssistantService'
import { useAppDispatch } from '@renderer/store'
import {
setAutoTranslateWithSpace,
setCodeCollapsible,
setCodeEditor,
setCodeExecution,
setCodeImageTools,
setCodeShowLineNumbers,
setCodeViewer,
setCodeWrappable,
setConfirmDeleteMessage,
setConfirmRegenerateMessage,
setEnableQuickPanelTriggers,
setFontSize,
setMathEnableSingleDollar,
setMathEngine,
setMessageFont,
setMessageNavigation,
setMessageStyle,
setMultiModelMessageStyle,
setPasteLongTextAsFile,
setPasteLongTextThreshold,
setRenderInputMessageAsMarkdown,
setShowInputEstimatedTokens,
setShowMessageOutline,
setShowPrompt,
setShowTranslateConfirm,
setThoughtAutoCollapse
} from '@renderer/store/settings'
import { Assistant, AssistantSettings, CodeStyleVarious, MathEngine } from '@renderer/types'
import { modalConfirm } from '@renderer/utils'
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
@ -56,16 +27,64 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import OpenAISettingsGroup from './components/OpenAISettingsGroup'
interface Props {
assistant: Assistant
}
const SettingsTab: FC<Props> = (props) => {
const [messageStyle, setMessageStyle] = usePreference('chat.message.style')
const [fontSize, setFontSize] = usePreference('chat.message.font_size')
const [language] = usePreference('app.language')
const [targetLanguage, setTargetLanguage] = usePreference('feature.translate.target_language')
const [sendMessageShortcut, setSendMessageShortcut] = usePreference('chat.input.send_message_shortcut')
const [messageFont, setMessageFont] = usePreference('chat.message.font')
const [showPrompt, setShowPrompt] = usePreference('chat.message.show_prompt')
const [confirmDeleteMessage, setConfirmDeleteMessage] = usePreference('chat.message.confirm_delete')
const [confirmRegenerateMessage, setConfirmRegenerateMessage] = usePreference('chat.message.confirm_regenerate')
const [showTranslateConfirm, setShowTranslateConfirm] = usePreference('chat.input.translate.show_confirm')
const [enableQuickPanelTriggers, setEnableQuickPanelTriggers] = usePreference(
'chat.input.quick_panel.triggers_enabled'
)
const [messageNavigation, setMessageNavigation] = usePreference('chat.message.navigation_mode')
const [thoughtAutoCollapse, setThoughtAutoCollapse] = usePreference('chat.message.thought.auto_collapse')
const [multiModelMessageStyle, setMultiModelMessageStyle] = usePreference('chat.message.multi_model.style')
const [pasteLongTextAsFile, setPasteLongTextAsFile] = usePreference('chat.input.paste_long_text_as_file')
const [pasteLongTextThreshold, setPasteLongTextThreshold] = usePreference('chat.input.paste_long_text_threshold')
const [mathEngine, setMathEngine] = usePreference('chat.message.math.engine')
const [mathEnableSingleDollar, setMathEnableSingleDollar] = usePreference('chat.message.math.single_dollar')
const [showInputEstimatedTokens, setShowInputEstimatedTokens] = usePreference('chat.input.show_estimated_tokens')
const [renderInputMessageAsMarkdown, setRenderInputMessageAsMarkdown] = usePreference(
'chat.message.render_as_markdown'
)
const [autoTranslateWithSpace, setAutoTranslateWithSpace] = usePreference(
'chat.input.translate.auto_translate_with_space'
)
const [showMessageOutline, setShowMessageOutline] = usePreference('chat.message.show_outline')
const [codeShowLineNumbers, setCodeShowLineNumbers] = usePreference('chat.code.show_line_numbers')
const [codeCollapsible, setCodeCollapsible] = usePreference('chat.code.collapsible')
const [codeWrappable, setCodeWrappable] = usePreference('chat.code.wrappable')
const [codeImageTools, setCodeImageTools] = usePreference('chat.code.image_tools')
const [codeEditor, setCodeEditor] = useMultiplePreferences({
enabled: 'chat.code.editor.enabled',
themeLight: 'chat.code.editor.theme_light',
themeDark: 'chat.code.editor.theme_dark',
highlightActiveLine: 'chat.code.editor.highlight_active_line',
foldGutter: 'chat.code.editor.fold_gutter',
autocompletion: 'chat.code.editor.autocompletion',
keymap: 'chat.code.editor.keymap'
})
const [codeViewer, setCodeViewer] = useMultiplePreferences({
themeLight: 'chat.code.viewer.theme_light',
themeDark: 'chat.code.viewer.theme_dark'
})
const [codeExecution, setCodeExecution] = useMultiplePreferences({
enabled: 'chat.code.execution.enabled',
timeoutMinutes: 'chat.code.execution.timeout_minutes'
})
const { assistant, updateAssistantSettings } = useAssistant(props.assistant.id)
const { provider } = useProvider(assistant.model.provider)
const { messageStyle, fontSize, language } = useSettings()
const { theme } = useTheme()
const { themeNames } = useCodeStyle()
@ -80,39 +99,6 @@ const SettingsTab: FC<Props> = (props) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const {
showPrompt,
messageFont,
showInputEstimatedTokens,
sendMessageShortcut,
setSendMessageShortcut,
targetLanguage,
setTargetLanguage,
pasteLongTextAsFile,
renderInputMessageAsMarkdown,
codeShowLineNumbers,
codeCollapsible,
codeWrappable,
codeEditor,
codeViewer,
codeImageTools,
codeExecution,
mathEngine,
mathEnableSingleDollar,
autoTranslateWithSpace,
pasteLongTextThreshold,
multiModelMessageStyle,
thoughtAutoCollapse,
messageNavigation,
enableQuickPanelTriggers,
showTranslateConfirm,
showMessageOutline,
confirmDeleteMessage,
confirmRegenerateMessage
} = useSettings()
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
updateAssistantSettings(settings)
}
@ -156,9 +142,9 @@ const SettingsTab: FC<Props> = (props) => {
(value: CodeStyleVarious) => {
const field = theme === ThemeMode.light ? 'themeLight' : 'themeDark'
const action = codeEditor.enabled ? setCodeEditor : setCodeViewer
dispatch(action({ [field]: value }))
action({ [field]: value })
},
[dispatch, theme, codeEditor.enabled]
[theme, codeEditor.enabled, setCodeEditor, setCodeViewer]
)
useEffect(() => {
@ -313,7 +299,7 @@ const SettingsTab: FC<Props> = (props) => {
<SettingGroup>
<SettingRow>
<SettingRowTitleSmall>{t('settings.messages.prompt')}</SettingRowTitleSmall>
<Switch size="small" checked={showPrompt} onChange={(checked) => dispatch(setShowPrompt(checked))} />
<Switch size="small" checked={showPrompt} onChange={(checked) => setShowPrompt(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
@ -321,7 +307,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={messageFont === 'serif'}
onChange={(checked) => dispatch(setMessageFont(checked ? 'serif' : 'system'))}
onChange={(checked) => setMessageFont(checked ? 'serif' : 'system')}
/>
</SettingRow>
<SettingDivider />
@ -335,24 +321,20 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={thoughtAutoCollapse}
onChange={(checked) => dispatch(setThoughtAutoCollapse(checked))}
onChange={(checked) => setThoughtAutoCollapse(checked)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('settings.messages.show_message_outline')}</SettingRowTitleSmall>
<Switch
size="small"
checked={showMessageOutline}
onChange={(checked) => dispatch(setShowMessageOutline(checked))}
/>
<Switch size="small" checked={showMessageOutline} onChange={(checked) => setShowMessageOutline(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('message.message.style.label')}</SettingRowTitleSmall>
<Selector
value={messageStyle}
onChange={(value) => dispatch(setMessageStyle(value as 'plain' | 'bubble'))}
onChange={(value) => setMessageStyle(value as 'plain' | 'bubble')}
options={[
{ value: 'plain', label: t('message.message.style.plain') },
{ value: 'bubble', label: t('message.message.style.bubble') }
@ -364,7 +346,7 @@ const SettingsTab: FC<Props> = (props) => {
<SettingRowTitleSmall>{t('message.message.multi_model_style.label')}</SettingRowTitleSmall>
<Selector
value={multiModelMessageStyle}
onChange={(value) => dispatch(setMultiModelMessageStyle(value))}
onChange={(value) => setMultiModelMessageStyle(value)}
options={[
{ value: 'fold', label: t('message.message.multi_model_style.fold.label') },
{ value: 'vertical', label: t('message.message.multi_model_style.vertical') },
@ -378,7 +360,7 @@ const SettingsTab: FC<Props> = (props) => {
<SettingRowTitleSmall>{t('settings.messages.navigation.label')}</SettingRowTitleSmall>
<Selector
value={messageNavigation}
onChange={(value) => dispatch(setMessageNavigation(value as 'none' | 'buttons' | 'anchor'))}
onChange={(value) => setMessageNavigation(value as 'none' | 'buttons' | 'anchor')}
options={[
{ value: 'none', label: t('settings.messages.navigation.none') },
{ value: 'buttons', label: t('settings.messages.navigation.buttons') },
@ -395,7 +377,7 @@ const SettingsTab: FC<Props> = (props) => {
<Slider
value={fontSizeValue}
onChange={(value) => setFontSizeValue(value)}
onChangeComplete={(value) => dispatch(setFontSize(value))}
onChangeComplete={(value) => setFontSize(value)}
min={12}
max={22}
step={1}
@ -416,7 +398,7 @@ const SettingsTab: FC<Props> = (props) => {
<SettingRowTitleSmall>{t('settings.math.engine.label')}</SettingRowTitleSmall>
<Selector
value={mathEngine}
onChange={(value) => dispatch(setMathEngine(value as MathEngine))}
onChange={(value) => setMathEngine(value as MathEngine)}
options={[
{ value: 'KaTeX', label: 'KaTeX' },
{ value: 'MathJax', label: 'MathJax' },
@ -435,7 +417,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={mathEnableSingleDollar}
onChange={(checked) => dispatch(setMathEnableSingleDollar(checked))}
onChange={(checked) => setMathEnableSingleDollar(checked)}
/>
</SettingRow>
<SettingDivider />
@ -465,7 +447,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeExecution.enabled}
onChange={(checked) => dispatch(setCodeExecution({ enabled: checked }))}
onChange={(checked) => setCodeExecution({ enabled: checked })}
/>
</SettingRow>
{codeExecution.enabled && (
@ -484,7 +466,7 @@ const SettingsTab: FC<Props> = (props) => {
max={60}
step={1}
value={codeExecution.timeoutMinutes}
onChange={(value) => dispatch(setCodeExecution({ timeoutMinutes: value ?? 1 }))}
onChange={(value) => setCodeExecution({ timeoutMinutes: value ?? 1 })}
style={{ width: 80 }}
/>
</SettingRow>
@ -496,7 +478,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeEditor.enabled}
onChange={(checked) => dispatch(setCodeEditor({ enabled: checked }))}
onChange={(checked) => setCodeEditor({ enabled: checked })}
/>
</SettingRow>
{codeEditor.enabled && (
@ -507,7 +489,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeEditor.highlightActiveLine}
onChange={(checked) => dispatch(setCodeEditor({ highlightActiveLine: checked }))}
onChange={(checked) => setCodeEditor({ highlightActiveLine: checked })}
/>
</SettingRow>
<SettingDivider />
@ -516,7 +498,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeEditor.foldGutter}
onChange={(checked) => dispatch(setCodeEditor({ foldGutter: checked }))}
onChange={(checked) => setCodeEditor({ foldGutter: checked })}
/>
</SettingRow>
<SettingDivider />
@ -525,7 +507,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeEditor.autocompletion}
onChange={(checked) => dispatch(setCodeEditor({ autocompletion: checked }))}
onChange={(checked) => setCodeEditor({ autocompletion: checked })}
/>
</SettingRow>
<SettingDivider />
@ -534,7 +516,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeEditor.keymap}
onChange={(checked) => dispatch(setCodeEditor({ keymap: checked }))}
onChange={(checked) => setCodeEditor({ keymap: checked })}
/>
</SettingRow>
</>
@ -545,31 +527,23 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={codeShowLineNumbers}
onChange={(checked) => dispatch(setCodeShowLineNumbers(checked))}
onChange={(checked) => setCodeShowLineNumbers(checked)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('chat.settings.code_collapsible')}</SettingRowTitleSmall>
<Switch
size="small"
checked={codeCollapsible}
onChange={(checked) => dispatch(setCodeCollapsible(checked))}
/>
<Switch size="small" checked={codeCollapsible} onChange={(checked) => setCodeCollapsible(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('chat.settings.code_wrappable')}</SettingRowTitleSmall>
<Switch size="small" checked={codeWrappable} onChange={(checked) => dispatch(setCodeWrappable(checked))} />
<Switch size="small" checked={codeWrappable} onChange={(checked) => setCodeWrappable(checked)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('chat.settings.code_image_tools')}</SettingRowTitleSmall>
<Switch
size="small"
checked={codeImageTools}
onChange={(checked) => dispatch(setCodeImageTools(checked))}
/>
<Switch size="small" checked={codeImageTools} onChange={(checked) => setCodeImageTools(checked)} />
</SettingRow>
</SettingGroup>
<SettingDivider />
@ -581,7 +555,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={showInputEstimatedTokens}
onChange={(checked) => dispatch(setShowInputEstimatedTokens(checked))}
onChange={(checked) => setShowInputEstimatedTokens(checked)}
/>
</SettingRow>
<SettingDivider />
@ -590,7 +564,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={pasteLongTextAsFile}
onChange={(checked) => dispatch(setPasteLongTextAsFile(checked))}
onChange={(checked) => setPasteLongTextAsFile(checked)}
/>
</SettingRow>
{pasteLongTextAsFile && (
@ -604,7 +578,7 @@ const SettingsTab: FC<Props> = (props) => {
max={10000}
step={100}
value={pasteLongTextThreshold}
onChange={(value) => dispatch(setPasteLongTextThreshold(value ?? 500))}
onChange={(value) => setPasteLongTextThreshold(value ?? 500)}
style={{ width: 80 }}
/>
</SettingRow>
@ -616,18 +590,18 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={renderInputMessageAsMarkdown}
onChange={(checked) => dispatch(setRenderInputMessageAsMarkdown(checked))}
onChange={(checked) => setRenderInputMessageAsMarkdown(checked)}
/>
</SettingRow>
<SettingDivider />
{!language.startsWith('en') && (
{!(language || navigator.language).startsWith('en') && (
<>
<SettingRow>
<SettingRowTitleSmall>{t('settings.input.auto_translate_with_space')}</SettingRowTitleSmall>
<Switch
size="small"
checked={autoTranslateWithSpace}
onChange={(checked) => dispatch(setAutoTranslateWithSpace(checked))}
onChange={(checked) => setAutoTranslateWithSpace(checked)}
/>
</SettingRow>
<SettingDivider />
@ -638,7 +612,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={showTranslateConfirm}
onChange={(checked) => dispatch(setShowTranslateConfirm(checked))}
onChange={(checked) => setShowTranslateConfirm(checked)}
/>
</SettingRow>
<SettingDivider />
@ -647,7 +621,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={enableQuickPanelTriggers}
onChange={(checked) => dispatch(setEnableQuickPanelTriggers(checked))}
onChange={(checked) => setEnableQuickPanelTriggers(checked)}
/>
</SettingRow>
<SettingDivider />
@ -656,7 +630,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={confirmDeleteMessage}
onChange={(checked) => dispatch(setConfirmDeleteMessage(checked))}
onChange={(checked) => setConfirmDeleteMessage(checked)}
/>
</SettingRow>
<SettingDivider />
@ -665,7 +639,7 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={confirmRegenerateMessage}
onChange={(checked) => dispatch(setConfirmRegenerateMessage(checked))}
onChange={(checked) => setConfirmRegenerateMessage(checked)}
/>
</SettingRow>
<SettingDivider />

View File

@ -1,14 +1,8 @@
import { UndoOutlined } from '@ant-design/icons' // 导入重置图标
import { usePreference } from '@data/hooks/usePreference'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useSettings } from '@renderer/hooks/useSettings'
import { SettingDescription, SettingDivider, SettingRowTitle, SettingTitle } from '@renderer/pages/settings'
import { useAppDispatch } from '@renderer/store'
import {
setMaxKeepAliveMinapps,
setMinappsOpenLinkExternal,
setShowOpenedMinappsInSidebar
} from '@renderer/store/settings'
import { Button, message, Slider, Switch, Tooltip } from 'antd'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -21,8 +15,13 @@ const DEFAULT_MAX_KEEPALIVE = 3
const MiniAppSettings: FC = () => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const { maxKeepAliveMinapps, showOpenedMinappsInSidebar, minappsOpenLinkExternal } = useSettings()
const [maxKeepAliveMinapps, setMaxKeepAliveMinapps] = usePreference('feature.minapp.max_keep_alive')
const [showOpenedMinappsInSidebar, setShowOpenedMinappsInSidebar] = usePreference(
'feature.minapp.show_opened_in_sidebar'
)
const [minappsOpenLinkExternal, setMinappsOpenLinkExternal] = usePreference('feature.minapp.open_link_external')
const { minapps, disabled, updateMinapps, updateDisabledMinapps } = useMinapps()
const [visibleMiniApps, setVisibleMiniApps] = useState(minapps)
@ -45,14 +44,14 @@ const MiniAppSettings: FC = () => {
// 恢复默认缓存数量
const handleResetCacheLimit = useCallback(() => {
dispatch(setMaxKeepAliveMinapps(DEFAULT_MAX_KEEPALIVE))
setMaxKeepAliveMinapps(DEFAULT_MAX_KEEPALIVE)
messageApi.info(t('settings.miniapps.cache_change_notice'))
}, [dispatch, messageApi, t])
}, [messageApi, t, setMaxKeepAliveMinapps])
// 处理缓存数量变更
const handleCacheChange = useCallback(
(value: number) => {
dispatch(setMaxKeepAliveMinapps(value))
setMaxKeepAliveMinapps(value)
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
@ -63,7 +62,7 @@ const MiniAppSettings: FC = () => {
debounceTimerRef.current = null
}, 500)
},
[dispatch, messageApi, t]
[messageApi, t, setMaxKeepAliveMinapps]
)
// 组件卸载时清除定时器
@ -97,10 +96,7 @@ const MiniAppSettings: FC = () => {
<SettingLabelGroup>
<SettingRowTitle>{t('settings.miniapps.open_link_external.title')}</SettingRowTitle>
</SettingLabelGroup>
<Switch
checked={minappsOpenLinkExternal}
onChange={(checked) => dispatch(setMinappsOpenLinkExternal(checked))}
/>
<Switch checked={minappsOpenLinkExternal} onChange={(checked) => setMinappsOpenLinkExternal(checked)} />
</SettingRow>
<SettingDivider />
{/* 缓存小程序数量设置 */}
@ -137,10 +133,7 @@ const MiniAppSettings: FC = () => {
<SettingRowTitle>{t('settings.miniapps.sidebar_title')}</SettingRowTitle>
<SettingDescription>{t('settings.miniapps.sidebar_description')}</SettingDescription>
</SettingLabelGroup>
<Switch
checked={showOpenedMinappsInSidebar}
onChange={(checked) => dispatch(setShowOpenedMinappsInSidebar(checked))}
/>
<Switch checked={showOpenedMinappsInSidebar} onChange={(checked) => setShowOpenedMinappsInSidebar(checked)} />
</SettingRow>
</Container>
)

View File

@ -1,11 +1,11 @@
import { GithubOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { HStack } from '@renderer/components/Layout'
import { APP_NAME, AppLogo } from '@renderer/config/env'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { handleSaveData, useAppDispatch } from '@renderer/store'
import { setUpdateState } from '@renderer/store/runtime'
@ -25,10 +25,13 @@ import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitle } from '.'
const AboutSettings: FC = () => {
const [autoCheckUpdate, setAutoCheckUpdate] = usePreference('app.dist.auto_update.enabled')
const [testPlan, setTestPlan] = usePreference('app.dist.test_plan.enabled')
const [testChannel, setTestChannel] = usePreference('app.dist.test_plan.channel')
const [version, setVersion] = useState('')
const [isPortable, setIsPortable] = useState(false)
const { t } = useTranslation()
const { autoCheckUpdate, setAutoCheckUpdate, testPlan, setTestPlan, testChannel, setTestChannel } = useSettings()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const { update } = useRuntime()

View File

@ -1,27 +1,31 @@
import { useMultiplePreferences } from '@data/hooks/usePreference'
import { useTheme } from '@renderer/context/ThemeProvider'
import { RootState, useAppDispatch } from '@renderer/store'
import { setExportMenuOptions } from '@renderer/store/settings'
import { Switch } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
const ExportMenuOptions: FC = () => {
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const exportMenuOptions = useSelector((state: RootState) => state.settings.exportMenuOptions)
const [exportMenuOptions, setExportMenuOptions] = useMultiplePreferences({
image: 'data.export.menus.image',
markdown: 'data.export.menus.markdown',
markdown_reason: 'data.export.menus.markdown_reason',
notion: 'data.export.menus.notion',
yuque: 'data.export.menus.yuque',
joplin: 'data.export.menus.joplin',
obsidian: 'data.export.menus.obsidian',
siyuan: 'data.export.menus.siyuan',
docx: 'data.export.menus.docx',
plain_text: 'data.export.menus.plain_text'
})
const handleToggleOption = (option: string, checked: boolean) => {
dispatch(
setExportMenuOptions({
...exportMenuOptions,
[option]: checked
})
)
setExportMenuOptions({
[option]: checked
})
}
return (

View File

@ -1,33 +1,30 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setJoplinExportReasoning, setJoplinToken, setJoplinUrl } from '@renderer/store/settings'
import { Button, Space, Switch, Tooltip } from 'antd'
import { Input } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
const JoplinSettings: FC = () => {
const [joplinToken, setJoplinToken] = usePreference('data.integration.joplin.token')
const [joplinUrl, setJoplinUrl] = usePreference('data.integration.joplin.url')
const [joplinExportReasoning, setJoplinExportReasoning] = usePreference('data.integration.joplin.export_reasoning')
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const { openMinapp } = useMinappPopup()
const joplinToken = useSelector((state: RootState) => state.settings.joplinToken)
const joplinUrl = useSelector((state: RootState) => state.settings.joplinUrl)
const joplinExportReasoning = useSelector((state: RootState) => state.settings.joplinExportReasoning)
const handleJoplinTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setJoplinToken(e.target.value))
setJoplinToken(e.target.value)
}
const handleJoplinUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setJoplinUrl(e.target.value))
setJoplinUrl(e.target.value)
}
const handleJoplinUrlBlur = (e: React.FocusEvent<HTMLInputElement>) => {
@ -35,7 +32,7 @@ const JoplinSettings: FC = () => {
// 确保URL以/结尾,但只在失去焦点时执行
if (url && !url.endsWith('/')) {
url = `${url}/`
dispatch(setJoplinUrl(url))
setJoplinUrl(url)
}
}
@ -74,7 +71,7 @@ const JoplinSettings: FC = () => {
}
const handleToggleJoplinExportReasoning = (checked: boolean) => {
dispatch(setJoplinExportReasoning(checked))
setJoplinExportReasoning(checked)
}
return (

View File

@ -1,20 +1,13 @@
import { DeleteOutlined, FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { LocalBackupManager } from '@renderer/components/LocalBackupManager'
import { LocalBackupModal, useLocalBackupModal } from '@renderer/components/LocalBackupModals'
import Selector from '@renderer/components/Selector'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
setLocalBackupAutoSync,
setLocalBackupDir as _setLocalBackupDir,
setLocalBackupMaxBackups as _setLocalBackupMaxBackups,
setLocalBackupSkipBackupFile as _setLocalBackupSkipBackupFile,
setLocalBackupSyncInterval as _setLocalBackupSyncInterval
} from '@renderer/store/settings'
import { useAppSelector } from '@renderer/store'
import { AppInfo } from '@renderer/types'
import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
@ -22,27 +15,18 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
const logger = loggerService.withContext('LocalBackupSettings')
const LocalBackupSettings: React.FC = () => {
const dispatch = useAppDispatch()
const [, setLocalBackupAutoSync] = usePreference('data.backup.local.auto_sync')
const [localBackupDir, setLocalBackupDir] = usePreference('data.backup.local.dir')
const [localBackupMaxBackups, setLocalBackupMaxBackups] = usePreference('data.backup.local.max_backups')
const [localBackupSkipBackupFile, setLocalBackupSkipBackupFile] = usePreference('data.backup.local.skip_backup_file')
const [localBackupSyncInterval, setLocalBackupSyncInterval] = usePreference('data.backup.local.sync_interval')
const {
localBackupDir: localBackupDirSetting,
localBackupSyncInterval: localBackupSyncIntervalSetting,
localBackupMaxBackups: localBackupMaxBackupsSetting,
localBackupSkipBackupFile: localBackupSkipBackupFileSetting
} = useSettings()
const [localBackupDir, setLocalBackupDir] = useState<string | undefined>(localBackupDirSetting)
const [resolvedLocalBackupDir, setResolvedLocalBackupDir] = useState<string | undefined>(undefined)
const [localBackupSkipBackupFile, setLocalBackupSkipBackupFile] = useState<boolean>(localBackupSkipBackupFileSetting)
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
const [syncInterval, setSyncInterval] = useState<number>(localBackupSyncIntervalSetting)
const [maxBackups, setMaxBackups] = useState<number>(localBackupMaxBackupsSetting)
const [appInfo, setAppInfo] = useState<AppInfo>()
useEffect(() => {
@ -50,10 +34,10 @@ const LocalBackupSettings: React.FC = () => {
}, [])
useEffect(() => {
if (localBackupDirSetting) {
window.api.resolvePath(localBackupDirSetting).then(setResolvedLocalBackupDir)
if (localBackupDir) {
window.api.resolvePath(localBackupDir).then(setResolvedLocalBackupDir)
}
}, [localBackupDirSetting])
}, [localBackupDir])
const { theme } = useTheme()
@ -62,13 +46,12 @@ const LocalBackupSettings: React.FC = () => {
const { localBackupSync } = useAppSelector((state) => state.backup)
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(_setLocalBackupSyncInterval(value))
setLocalBackupSyncInterval(value)
if (value === 0) {
dispatch(setLocalBackupAutoSync(false))
setLocalBackupAutoSync(false)
stopAutoSync('local')
} else {
dispatch(setLocalBackupAutoSync(true))
setLocalBackupAutoSync(true)
startAutoSync(false, 'local')
}
}
@ -105,7 +88,7 @@ const LocalBackupSettings: React.FC = () => {
}
const handleLocalBackupDirChange = async (value: string) => {
if (value === localBackupDirSetting) {
if (value === localBackupDir) {
return
}
@ -115,29 +98,26 @@ const LocalBackupSettings: React.FC = () => {
}
if (await checkLocalBackupDirValid(value)) {
setLocalBackupDir(value)
dispatch(_setLocalBackupDir(value))
await setLocalBackupDir(value)
setResolvedLocalBackupDir(await window.api.resolvePath(value))
dispatch(setLocalBackupAutoSync(true))
await setLocalBackupAutoSync(true)
startAutoSync(true, 'local')
return
}
if (localBackupDirSetting) {
setLocalBackupDir(localBackupDirSetting)
if (localBackupDir) {
await setLocalBackupDir(localBackupDir)
return
}
}
const onMaxBackupsChange = (value: number) => {
setMaxBackups(value)
dispatch(_setLocalBackupMaxBackups(value))
setLocalBackupMaxBackups(value)
}
const onSkipBackupFilesChange = (value: boolean) => {
setLocalBackupSkipBackupFile(value)
dispatch(_setLocalBackupSkipBackupFile(value))
}
const handleBrowseDirectory = async () => {
@ -157,10 +137,9 @@ const LocalBackupSettings: React.FC = () => {
}
}
const handleClearDirectory = () => {
setLocalBackupDir('')
dispatch(_setLocalBackupDir(''))
dispatch(setLocalBackupAutoSync(false))
const handleClearDirectory = async () => {
await setLocalBackupDir('')
await setLocalBackupAutoSync(false)
stopAutoSync('local')
}
@ -238,7 +217,7 @@ const LocalBackupSettings: React.FC = () => {
<SettingRowTitle>{t('settings.data.local.autoSync.label')}</SettingRowTitle>
<Selector
size={14}
value={syncInterval}
value={localBackupSyncInterval}
onChange={onSyncIntervalChange}
disabled={!localBackupDir}
options={[
@ -260,7 +239,7 @@ const LocalBackupSettings: React.FC = () => {
<SettingRowTitle>{t('settings.data.local.maxBackups.label')}</SettingRowTitle>
<Selector
size={14}
value={maxBackups}
value={localBackupMaxBackups}
onChange={onMaxBackupsChange}
disabled={!localBackupDir}
options={[
@ -282,7 +261,7 @@ const LocalBackupSettings: React.FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.backup.skip_file_data_help')}</SettingHelpText>
</SettingRow>
{localBackupSync && syncInterval > 0 && (
{localBackupSync && localBackupSyncInterval > 0 && (
<>
<SettingDivider />
<SettingRow>

View File

@ -1,70 +1,69 @@
import { DeleteOutlined, FolderOpenOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { RootState, useAppDispatch } from '@renderer/store'
import {
setExcludeCitationsInExport,
setForceDollarMathInMarkdown,
setmarkdownExportPath,
setShowModelNameInMarkdown,
setShowModelProviderInMarkdown,
setStandardizeCitationsInExport,
setUseTopicNamingForMessageTitle
} from '@renderer/store/settings'
import { Button, Switch } 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 forceDollarMathInMarkdown = useSelector((state: RootState) => state.settings.forceDollarMathInMarkdown)
const useTopicNamingForMessageTitle = useSelector((state: RootState) => state.settings.useTopicNamingForMessageTitle)
const showModelNameInExport = useSelector((state: RootState) => state.settings.showModelNameInMarkdown)
const showModelProviderInMarkdown = useSelector((state: RootState) => state.settings.showModelProviderInMarkdown)
const excludeCitationsInExport = useSelector((state: RootState) => state.settings.excludeCitationsInExport)
const standardizeCitationsInExport = useSelector((state: RootState) => state.settings.standardizeCitationsInExport)
const [markdownExportPath, setmarkdownExportPath] = usePreference('data.export.markdown.path')
const [forceDollarMathInMarkdown, setForceDollarMathInMarkdown] = usePreference(
'data.export.markdown.force_dollar_math'
)
const [useTopicNamingForMessageTitle, setUseTopicNamingForMessageTitle] = usePreference(
'data.export.markdown.use_topic_naming_for_message_title'
)
const [showModelNameInExport, setShowModelNameInMarkdown] = usePreference('data.export.markdown.show_model_name')
const [showModelProviderInMarkdown, setShowModelProviderInMarkdown] = usePreference(
'data.export.markdown.show_model_provider'
)
const [excludeCitationsInExport, setExcludeCitationsInExport] = usePreference(
'data.export.markdown.exclude_citations'
)
const [standardizeCitationsInExport, setStandardizeCitationsInExport] = usePreference(
'data.export.markdown.standardize_citations'
)
const handleSelectFolder = async () => {
const path = await window.api.file.selectFolder()
if (path) {
dispatch(setmarkdownExportPath(path))
setmarkdownExportPath(path)
}
}
const handleClearPath = () => {
dispatch(setmarkdownExportPath(null))
setmarkdownExportPath(null)
}
const handleToggleForceDollarMath = (checked: boolean) => {
dispatch(setForceDollarMathInMarkdown(checked))
setForceDollarMathInMarkdown(checked)
}
const handleToggleTopicNaming = (checked: boolean) => {
dispatch(setUseTopicNamingForMessageTitle(checked))
setUseTopicNamingForMessageTitle(checked)
}
const handleToggleShowModelName = (checked: boolean) => {
dispatch(setShowModelNameInMarkdown(checked))
setShowModelNameInMarkdown(checked)
}
const handleToggleShowModelProvider = (checked: boolean) => {
dispatch(setShowModelProviderInMarkdown(checked))
setShowModelProviderInMarkdown(checked)
}
const handleToggleExcludeCitations = (checked: boolean) => {
dispatch(setExcludeCitationsInExport(checked))
setExcludeCitationsInExport(checked)
}
const handleToggleStandardizeCitations = (checked: boolean) => {
dispatch(setStandardizeCitationsInExport(checked))
setStandardizeCitationsInExport(checked)
}
return (

View File

@ -1,43 +1,35 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { Client } from '@notionhq/client'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import {
setNotionApiKey,
setNotionDatabaseID,
setNotionExportReasoning,
setNotionPageNameKey
} from '@renderer/store/settings'
import { Button, Space, Switch, Tooltip } from 'antd'
import { Input } from 'antd'
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 [notionApiKey, setNotionApiKey] = usePreference('data.integration.notion.api_key')
const [notionDatabaseID, setNotionDatabaseID] = usePreference('data.integration.notion.database_id')
const [notionPageNameKey, setNotionPageNameKey] = usePreference('data.integration.notion.page_name_key')
const [notionExportReasoning, setNotionExportReasoning] = usePreference('data.integration.notion.export_reasoning')
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const { openMinapp } = useMinappPopup()
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 notionExportReasoning = useSelector((state: RootState) => state.settings.notionExportReasoning)
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionApiKey(e.target.value))
setNotionApiKey(e.target.value)
}
const handleNotionDatabaseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionDatabaseID(e.target.value))
setNotionDatabaseID(e.target.value)
}
const handleNotionPageNameKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionPageNameKey(e.target.value))
setNotionPageNameKey(e.target.value)
}
const handleNotionConnectionCheck = () => {
@ -75,7 +67,7 @@ const NotionSettings: FC = () => {
}
const handleNotionExportReasoningChange = (checked: boolean) => {
dispatch(setNotionExportReasoning(checked))
setNotionExportReasoning(checked)
}
return (

View File

@ -1,15 +1,13 @@
import { FolderOpenOutlined, InfoCircleOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { S3BackupManager } from '@renderer/components/S3BackupManager'
import { S3BackupModal, useS3BackupModal } from '@renderer/components/S3Modals'
import Selector from '@renderer/components/Selector'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useSettings } from '@renderer/hooks/useSettings'
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setS3Partial } from '@renderer/store/settings'
import { S3Config } from '@renderer/types'
import { useAppSelector } from '@renderer/store'
import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react'
@ -18,45 +16,32 @@ import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
const S3Settings: FC = () => {
const { s3 = {} as S3Config } = useSettings()
const [, setS3AutoSync] = usePreference('data.backup.s3.auto_sync')
const [s3Endpoint, setS3Endpoint] = usePreference('data.backup.s3.endpoint')
const [s3Region, setS3Region] = usePreference('data.backup.s3.region')
const [s3Bucket, setS3Bucket] = usePreference('data.backup.s3.bucket')
const [s3AccessKeyId, setS3AccessKeyId] = usePreference('data.backup.s3.access_key_id')
const [s3SecretAccessKey, setS3SecretAccessKey] = usePreference('data.backup.s3.secret_access_key')
const [s3Root, setS3Root] = usePreference('data.backup.s3.root')
const [s3SkipBackupFile, setS3SkipBackupFile] = usePreference('data.backup.s3.skip_backup_file')
const [s3SyncInterval, setS3SyncInterval] = usePreference('data.backup.s3.sync_interval')
const [s3MaxBackups, setS3MaxBackups] = usePreference('data.backup.s3.max_backups')
const {
endpoint: s3EndpointInit = '',
region: s3RegionInit = '',
bucket: s3BucketInit = '',
accessKeyId: s3AccessKeyIdInit = '',
secretAccessKey: s3SecretAccessKeyInit = '',
root: s3RootInit = '',
syncInterval: s3SyncIntervalInit = 0,
maxBackups: s3MaxBackupsInit = 5,
skipBackupFile: s3SkipBackupFileInit = false
} = s3
const [endpoint, setEndpoint] = useState<string | undefined>(s3EndpointInit)
const [region, setRegion] = useState<string | undefined>(s3RegionInit)
const [bucket, setBucket] = useState<string | undefined>(s3BucketInit)
const [accessKeyId, setAccessKeyId] = useState<string | undefined>(s3AccessKeyIdInit)
const [secretAccessKey, setSecretAccessKey] = useState<string | undefined>(s3SecretAccessKeyInit)
const [root, setRoot] = useState<string | undefined>(s3RootInit)
const [skipBackupFile, setSkipBackupFile] = useState<boolean>(s3SkipBackupFileInit)
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
const [syncInterval, setSyncInterval] = useState<number>(s3SyncIntervalInit)
const [maxBackups, setMaxBackups] = useState<number>(s3MaxBackupsInit)
const dispatch = useAppDispatch()
const { theme } = useTheme()
const { t } = useTranslation()
const { openMinapp } = useMinappPopup()
const { s3Sync } = useAppSelector((state) => state.backup)
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(setS3Partial({ syncInterval: value, autoSync: value !== 0 }))
const onSyncIntervalChange = async (value: number) => {
setS3SyncInterval(value)
if (value === 0) {
await setS3AutoSync(false)
stopAutoSync('s3')
} else {
await setS3AutoSync(true)
startAutoSync(false, 's3')
}
}
@ -70,17 +55,15 @@ const S3Settings: FC = () => {
}
const onMaxBackupsChange = (value: number) => {
setMaxBackups(value)
dispatch(setS3Partial({ maxBackups: value }))
setS3MaxBackups(value)
}
const onSkipBackupFilesChange = (value: boolean) => {
setSkipBackupFile(value)
dispatch(setS3Partial({ skipBackupFile: value }))
setS3SkipBackupFile(value)
}
const renderSyncStatus = () => {
if (!endpoint) return null
if (!s3Endpoint) return null
if (!s3Sync?.lastSyncTime && !s3Sync?.syncing && !s3Sync?.lastSyncError) {
return <span style={{ color: 'var(--text-secondary)' }}>{t('settings.data.s3.syncStatus.noSync')}</span>
@ -128,11 +111,11 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.endpoint.label')}</SettingRowTitle>
<Input
placeholder={t('settings.data.s3.endpoint.placeholder')}
value={endpoint}
onChange={(e) => setEndpoint(e.target.value)}
value={s3Endpoint}
onChange={(e) => setS3Endpoint(e.target.value)}
style={{ width: 250 }}
type="url"
onBlur={() => dispatch(setS3Partial({ endpoint: endpoint || '' }))}
onBlur={(e) => setS3Endpoint(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -140,10 +123,10 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.region.label')}</SettingRowTitle>
<Input
placeholder={t('settings.data.s3.region.placeholder')}
value={region}
onChange={(e) => setRegion(e.target.value)}
value={s3Region}
onChange={(e) => setS3Region(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(setS3Partial({ region: region || '' }))}
onBlur={(e) => setS3Region(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -151,10 +134,10 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.bucket.label')}</SettingRowTitle>
<Input
placeholder={t('settings.data.s3.bucket.placeholder')}
value={bucket}
onChange={(e) => setBucket(e.target.value)}
value={s3Bucket}
onChange={(e) => setS3Bucket(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(setS3Partial({ bucket: bucket || '' }))}
onBlur={(e) => setS3Bucket(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -162,10 +145,10 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.accessKeyId.label')}</SettingRowTitle>
<Input
placeholder={t('settings.data.s3.accessKeyId.placeholder')}
value={accessKeyId}
onChange={(e) => setAccessKeyId(e.target.value)}
value={s3AccessKeyId}
onChange={(e) => setS3AccessKeyId(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(setS3Partial({ accessKeyId: accessKeyId || '' }))}
onBlur={(e) => setS3AccessKeyId(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -173,10 +156,10 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.secretAccessKey.label')}</SettingRowTitle>
<Input.Password
placeholder={t('settings.data.s3.secretAccessKey.placeholder')}
value={secretAccessKey}
onChange={(e) => setSecretAccessKey(e.target.value)}
value={s3SecretAccessKey}
onChange={(e) => setS3SecretAccessKey(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(setS3Partial({ secretAccessKey: secretAccessKey || '' }))}
onBlur={(e) => setS3SecretAccessKey(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -184,10 +167,10 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.root.label')}</SettingRowTitle>
<Input
placeholder={t('settings.data.s3.root.placeholder')}
value={root}
onChange={(e) => setRoot(e.target.value)}
value={s3Root}
onChange={(e) => setS3Root(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(setS3Partial({ root: root || '' }))}
onBlur={(e) => setS3Root(e.target.value)}
/>
</SettingRow>
<SettingDivider />
@ -198,13 +181,13 @@ const S3Settings: FC = () => {
onClick={showBackupModal}
icon={<SaveOutlined />}
loading={backuping}
disabled={!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey}>
disabled={!s3Endpoint || !s3Region || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey}>
{t('settings.data.s3.backup.button')}
</Button>
<Button
onClick={showBackupManager}
icon={<FolderOpenOutlined />}
disabled={!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey}>
disabled={!s3Endpoint || !s3Region || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey}>
{t('settings.data.s3.backup.manager.button')}
</Button>
</HStack>
@ -214,9 +197,9 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.autoSync.label')}</SettingRowTitle>
<Selector
size={14}
value={syncInterval}
value={s3SyncInterval}
onChange={onSyncIntervalChange}
disabled={!endpoint || !accessKeyId || !secretAccessKey}
disabled={!s3Endpoint || !s3AccessKeyId || !s3SecretAccessKey}
options={[
{ label: t('settings.data.s3.autoSync.off'), value: 0 },
{ label: t('settings.data.s3.autoSync.minute', { count: 1 }), value: 1 },
@ -236,9 +219,9 @@ const S3Settings: FC = () => {
<SettingRowTitle>{t('settings.data.s3.maxBackups.label')}</SettingRowTitle>
<Selector
size={14}
value={maxBackups}
value={s3MaxBackups}
onChange={onMaxBackupsChange}
disabled={!endpoint || !accessKeyId || !secretAccessKey}
disabled={!s3Endpoint || !s3AccessKeyId || !s3SecretAccessKey}
options={[
{ label: t('settings.data.s3.maxBackups.unlimited'), value: 0 },
{ label: '1', value: 1 },
@ -253,12 +236,12 @@ const S3Settings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.s3.skipBackupFile.label')}</SettingRowTitle>
<Switch checked={skipBackupFile} onChange={onSkipBackupFilesChange} />
<Switch checked={s3SkipBackupFile} onChange={onSkipBackupFilesChange} />
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.s3.skipBackupFile.help')}</SettingHelpText>
</SettingRow>
{syncInterval > 0 && (
{s3SyncInterval > 0 && (
<>
<SettingDivider />
<SettingRow>
@ -281,12 +264,12 @@ const S3Settings: FC = () => {
visible={backupManagerVisible}
onClose={closeBackupManager}
s3Config={{
endpoint,
region,
bucket,
accessKeyId,
secretAccessKey,
root
endpoint: s3Endpoint,
region: s3Region,
bucket: s3Bucket,
accessKeyId: s3AccessKeyId,
secretAccessKey: s3SecretAccessKey,
root: s3Root
}}
/>
</>

View File

@ -1,45 +1,42 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setSiyuanApiUrl, setSiyuanBoxId, setSiyuanRootPath, setSiyuanToken } from '@renderer/store/settings'
import { Button, Space, Tooltip } from 'antd'
import { Input } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
const logger = loggerService.withContext('SiyuanSettings')
const SiyuanSettings: FC = () => {
const [siyuanApiUrl, setSiyuanApiUrl] = usePreference('data.integration.siyuan.api_url')
const [siyuanToken, setSiyuanToken] = usePreference('data.integration.siyuan.token')
const [siyuanBoxId, setSiyuanBoxId] = usePreference('data.integration.siyuan.box_id')
const [siyuanRootPath, setSiyuanRootPath] = usePreference('data.integration.siyuan.root_path')
const { openMinapp } = useMinappPopup()
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const siyuanApiUrl = useSelector((state: RootState) => state.settings.siyuanApiUrl)
const siyuanToken = useSelector((state: RootState) => state.settings.siyuanToken)
const siyuanBoxId = useSelector((state: RootState) => state.settings.siyuanBoxId)
const siyuanRootPath = useSelector((state: RootState) => state.settings.siyuanRootPath)
const handleApiUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanApiUrl(e.target.value))
setSiyuanApiUrl(e.target.value)
}
const handleTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanToken(e.target.value))
setSiyuanToken(e.target.value)
}
const handleBoxIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanBoxId(e.target.value))
setSiyuanBoxId(e.target.value)
}
const handleRootPathChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanRootPath(e.target.value))
setSiyuanRootPath(e.target.value)
}
const handleSiyuanHelpClick = () => {

View File

@ -1,23 +1,12 @@
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import Selector from '@renderer/components/Selector'
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
setWebdavAutoSync,
setWebdavDisableStream as _setWebdavDisableStream,
setWebdavHost as _setWebdavHost,
setWebdavMaxBackups as _setWebdavMaxBackups,
setWebdavPass as _setWebdavPass,
setWebdavPath as _setWebdavPath,
setWebdavSkipBackupFile as _setWebdavSkipBackupFile,
setWebdavSyncInterval as _setWebdavSyncInterval,
setWebdavUser as _setWebdavUser
} from '@renderer/store/settings'
import { useAppSelector } from '@renderer/store'
import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react'
@ -26,29 +15,18 @@ import { useTranslation } from 'react-i18next'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
const WebDavSettings: FC = () => {
const {
webdavHost: webDAVHost,
webdavUser: webDAVUser,
webdavPass: webDAVPass,
webdavPath: webDAVPath,
webdavSyncInterval: webDAVSyncInterval,
webdavMaxBackups: webDAVMaxBackups,
webdavSkipBackupFile: webdDAVSkipBackupFile,
webdavDisableStream: webDAVDisableStream
} = useSettings()
const [, setWebdavAutoSync] = usePreference('data.backup.webdav.auto_sync')
const [webdavDisableStream, setWebdavDisableStream] = usePreference('data.backup.webdav.disable_stream')
const [webdavHost, setWebdavHost] = usePreference('data.backup.webdav.host')
const [webdavMaxBackups, setWebdavMaxBackups] = usePreference('data.backup.webdav.max_backups')
const [webdavPass, setWebdavPass] = usePreference('data.backup.webdav.pass')
const [webdavPath, setWebdavPath] = usePreference('data.backup.webdav.path')
const [webdavSkipBackupFile, setWebdavSkipBackupFile] = usePreference('data.backup.webdav.skip_backup_file')
const [webdavSyncInterval, setWebdavSyncInterval] = usePreference('data.backup.webdav.sync_interval')
const [webdavUser, setWebdavUser] = usePreference('data.backup.webdav.user')
const [webdavHost, setWebdavHost] = useState<string | undefined>(webDAVHost)
const [webdavUser, setWebdavUser] = useState<string | undefined>(webDAVUser)
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
const [webdavSkipBackupFile, setWebdavSkipBackupFile] = useState<boolean>(webdDAVSkipBackupFile)
const [webdavDisableStream, setWebdavDisableStream] = useState<boolean>(webDAVDisableStream)
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
const [maxBackups, setMaxBackups] = useState<number>(webDAVMaxBackups)
const dispatch = useAppDispatch()
const { theme } = useTheme()
const { t } = useTranslation()
@ -57,31 +35,27 @@ const WebDavSettings: FC = () => {
// 把之前备份的文件定时上传到 webdav首先先配置 webdav 的 host, port, user, pass, path
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(_setWebdavSyncInterval(value))
const onSyncIntervalChange = async (value: number) => {
setWebdavSyncInterval(value)
if (value === 0) {
dispatch(setWebdavAutoSync(false))
await setWebdavAutoSync(false)
stopAutoSync('webdav')
} else {
dispatch(setWebdavAutoSync(true))
await setWebdavAutoSync(true)
startAutoSync(false, 'webdav')
}
}
const onMaxBackupsChange = (value: number) => {
setMaxBackups(value)
dispatch(_setWebdavMaxBackups(value))
setWebdavMaxBackups(value)
}
const onSkipBackupFilesChange = (value: boolean) => {
setWebdavSkipBackupFile(value)
dispatch(_setWebdavSkipBackupFile(value))
}
const onDisableStreamChange = (value: boolean) => {
setWebdavDisableStream(value)
dispatch(_setWebdavDisableStream(value))
}
const renderSyncStatus = () => {
@ -131,7 +105,7 @@ const WebDavSettings: FC = () => {
onChange={(e) => setWebdavHost(e.target.value)}
style={{ width: 250 }}
type="url"
onBlur={() => dispatch(_setWebdavHost(webdavHost || ''))}
onBlur={() => setWebdavHost(webdavHost || '')}
/>
</SettingRow>
<SettingDivider />
@ -142,7 +116,7 @@ const WebDavSettings: FC = () => {
value={webdavUser}
onChange={(e) => setWebdavUser(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(_setWebdavUser(webdavUser || ''))}
onBlur={() => setWebdavUser(webdavUser || '')}
/>
</SettingRow>
<SettingDivider />
@ -153,7 +127,7 @@ const WebDavSettings: FC = () => {
value={webdavPass}
onChange={(e) => setWebdavPass(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(_setWebdavPass(webdavPass || ''))}
onBlur={() => setWebdavPass(webdavPass || '')}
/>
</SettingRow>
<SettingDivider />
@ -164,7 +138,7 @@ const WebDavSettings: FC = () => {
value={webdavPath}
onChange={(e) => setWebdavPath(e.target.value)}
style={{ width: 250 }}
onBlur={() => dispatch(_setWebdavPath(webdavPath || ''))}
onBlur={() => setWebdavPath(webdavPath || '')}
/>
</SettingRow>
<SettingDivider />
@ -184,7 +158,7 @@ const WebDavSettings: FC = () => {
<SettingRowTitle>{t('settings.data.webdav.autoSync.label')}</SettingRowTitle>
<Selector
size={14}
value={syncInterval}
value={webdavSyncInterval}
onChange={onSyncIntervalChange}
disabled={!webdavHost}
options={[
@ -206,7 +180,7 @@ const WebDavSettings: FC = () => {
<SettingRowTitle>{t('settings.data.webdav.maxBackups')}</SettingRowTitle>
<Selector
size={14}
value={maxBackups}
value={webdavMaxBackups}
onChange={onMaxBackupsChange}
disabled={!webdavHost}
options={[
@ -236,7 +210,7 @@ const WebDavSettings: FC = () => {
<SettingRow>
<SettingHelpText>{t('settings.data.webdav.disableStream.help')}</SettingHelpText>
</SettingRow>
{webdavSync && syncInterval > 0 && (
{webdavSync && webdavSyncInterval > 0 && (
<>
<SettingDivider />
<SettingRow>

View File

@ -1,32 +1,30 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setYuqueRepoId, setYuqueToken, setYuqueUrl } from '@renderer/store/settings'
import { Button, Space, Tooltip } from 'antd'
import { Input } from 'antd'
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 { openMinapp } = useMinappPopup()
const yuqueToken = useSelector((state: RootState) => state.settings.yuqueToken)
const yuqueUrl = useSelector((state: RootState) => state.settings.yuqueUrl)
const [yuqueToken, setYuqueToken] = usePreference('data.integration.yuque.token')
const [yuqueUrl, setYuqueUrl] = usePreference('data.integration.yuque.url')
const [, setYuqueRepoId] = usePreference('data.integration.yuque.repo_id')
const handleYuqueTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setYuqueToken(e.target.value))
setYuqueToken(e.target.value)
}
const handleYuqueRepoUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setYuqueUrl(e.target.value))
setYuqueUrl(e.target.value)
}
const handleYuqueConnectionCheck = async () => {
@ -60,7 +58,7 @@ const YuqueSettings: FC = () => {
return
}
const data = await repoIDResponse.json()
dispatch(setYuqueRepoId(data.data.id))
setYuqueRepoId(data.data.id)
window.message.success(t('settings.data.yuque.check.success'))
}

View File

@ -1,22 +1,11 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference'
import InfoTooltip from '@renderer/components/InfoTooltip'
import { HStack } from '@renderer/components/Layout'
import Selector from '@renderer/components/Selector'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { useTimer } from '@renderer/hooks/useTimer'
import i18n from '@renderer/i18n'
import { RootState, useAppDispatch } from '@renderer/store'
import {
setEnableDataCollection,
setEnableSpellCheck,
setNotificationSettings,
setProxyBypassRules as _setProxyBypassRules,
setProxyMode,
setProxyUrl as _setProxyUrl,
setSpellCheckLanguages
} from '@renderer/store/settings'
import { NotificationSource } from '@renderer/types/notification'
import { isValidProxyUrl } from '@renderer/utils'
import { defaultByPassRules, defaultLanguage } from '@shared/config/constant'
@ -24,32 +13,33 @@ import { LanguageVarious } from '@shared/data/preferenceTypes'
import { Flex, Input, Switch, Tooltip } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '.'
const GeneralSettings: FC = () => {
const {
proxyUrl: storeProxyUrl,
proxyBypassRules: storeProxyBypassRules,
setLaunch,
setTray,
launchOnBoot,
launchToTray,
trayOnClose,
tray,
proxyMode: storeProxyMode,
enableDataCollection,
enableSpellCheck
} = useSettings()
const [language, setLanguage] = usePreference('app.language')
const [disableHardwareAcceleration, setDisableHardwareAcceleration] = usePreference(
'app.disable_hardware_acceleration'
)
const [enableDeveloperMode, setEnableDeveloperMode] = usePreference('app.developer_mode.enabled')
const [launchOnBoot, setLaunchOnBoot] = usePreference('app.launch_on_boot')
const [launchToTray, setLaunchToTray] = usePreference('app.tray.on_launch')
const [trayOnClose, setTrayOnClose] = usePreference('app.tray.on_close')
const [tray, setTray] = usePreference('app.tray.enabled')
const [enableDataCollection, setEnableDataCollection] = usePreference('app.privacy.data_collection.enabled')
const [storeProxyMode, setProxyMode] = usePreference('app.proxy.mode')
const [storeProxyBypassRules, _setProxyBypassRules] = usePreference('app.proxy.bypass_rules')
const [storeProxyUrl, _setProxyUrl] = usePreference('app.proxy.url')
const [enableSpellCheck, setEnableSpellCheck] = usePreference('app.spell_check.enabled')
const [spellCheckLanguages, setSpellCheckLanguages] = usePreference('app.spell_check.languages')
const [notificationSettings, setNotificationSettings] = useMultiplePreferences({
assistant: 'app.notification.assistant.enabled',
backup: 'app.notification.backup.enabled',
knowledge: 'app.notification.knowledge.enabled'
})
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const [proxyBypassRules, setProxyBypassRules] = useState<string | undefined>(storeProxyBypassRules)
const [proxyUrl, setProxyUrl] = useState<string>(storeProxyUrl)
const [proxyBypassRules, setProxyBypassRules] = useState<string>(storeProxyBypassRules)
const { theme } = useTheme()
const { setTimeoutTimer } = useTimer()
@ -63,7 +53,7 @@ const GeneralSettings: FC = () => {
}
const updateTrayOnClose = (isTrayOnClose: boolean) => {
setTray(undefined, isTrayOnClose)
setTrayOnClose(isTrayOnClose)
//in case tray is not enabled, enable it
if (isTrayOnClose && !tray) {
updateTray(true)
@ -71,17 +61,17 @@ const GeneralSettings: FC = () => {
}
const updateLaunchOnBoot = (isLaunchOnBoot: boolean) => {
setLaunch(isLaunchOnBoot)
setLaunchOnBoot(isLaunchOnBoot)
}
const updateLaunchToTray = (isLaunchToTray: boolean) => {
setLaunch(undefined, isLaunchToTray)
setLaunchToTray(isLaunchToTray)
if (isLaunchToTray && !tray) {
updateTray(true)
}
}
const dispatch = useAppDispatch()
// const dispatch = useAppDispatch()
const { t } = useTranslation()
const onSelectLanguage = (value: LanguageVarious) => {
@ -93,7 +83,7 @@ const GeneralSettings: FC = () => {
}
const handleSpellCheckChange = (checked: boolean) => {
dispatch(setEnableSpellCheck(checked))
setEnableSpellCheck(checked)
window.api.setEnableSpellCheck(checked)
}
@ -103,11 +93,11 @@ const GeneralSettings: FC = () => {
return
}
dispatch(_setProxyUrl(proxyUrl))
_setProxyUrl(proxyUrl)
}
const onSetProxyBypassRules = () => {
dispatch(_setProxyBypassRules(proxyBypassRules))
_setProxyBypassRules(proxyBypassRules)
}
const proxyModeOptions: { value: 'system' | 'custom' | 'none'; label: string }[] = [
@ -117,7 +107,7 @@ const GeneralSettings: FC = () => {
]
const onProxyModeChange = (mode: 'system' | 'custom' | 'none') => {
dispatch(setProxyMode(mode))
setProxyMode(mode)
}
const languagesOptions: { value: LanguageVarious; label: string; flag: string }[] = [
@ -132,11 +122,8 @@ const GeneralSettings: FC = () => {
{ value: 'pt-PT', label: 'Português', flag: '🇵🇹' }
]
const notificationSettings = useSelector((state: RootState) => state.settings.notification)
const spellCheckLanguages = useSelector((state: RootState) => state.settings.spellCheckLanguages)
const handleNotificationChange = (type: NotificationSource, value: boolean) => {
dispatch(setNotificationSettings({ ...notificationSettings, [type]: value }))
setNotificationSettings({ [type]: value })
}
// Define available spell check languages with display names (only commonly supported languages)
@ -153,8 +140,7 @@ const GeneralSettings: FC = () => {
]
const handleSpellCheckLanguagesChange = (selectedLanguages: string[]) => {
dispatch(setSpellCheckLanguages(selectedLanguages))
window.api.setSpellCheckLanguages(selectedLanguages)
setSpellCheckLanguages(selectedLanguages)
}
const handleHardwareAccelerationChange = (checked: boolean) => {
@ -339,7 +325,7 @@ const GeneralSettings: FC = () => {
<Switch
value={enableDataCollection}
onChange={(v) => {
dispatch(setEnableDataCollection(v))
setEnableDataCollection(v)
window.api.config.set('enableDataCollection', v)
}}
/>

View File

@ -1,9 +1,7 @@
import { QuestionCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import { ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { setEnableTopicNaming, setTopicNamingPrompt } from '@renderer/store/settings'
import { Button, Divider, Flex, Input, Modal, Popover, Switch } from 'antd'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -16,10 +14,11 @@ interface Props {
}
const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [enableTopicNaming, setEnableTopicNaming] = usePreference('topic.naming.enabled')
const [topicNamingPrompt, setTopicNamingPrompt] = usePreference('topic.naming_prompt')
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const { enableTopicNaming, topicNamingPrompt } = useSettings()
const dispatch = useAppDispatch()
const onOk = () => {
setOpen(false)
@ -34,8 +33,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
}
const handleReset = useCallback(() => {
dispatch(setTopicNamingPrompt(''))
}, [dispatch])
setTopicNamingPrompt('')
}, [setTopicNamingPrompt])
TopicNamingModalPopup.hide = onCancel
@ -58,7 +57,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<Flex vertical align="stretch" gap={8}>
<HStack style={{ gap: 16 }} alignItems="center">
<div>{t('settings.models.topic_naming.auto')}</div>
<Switch checked={enableTopicNaming} onChange={(v) => dispatch(setEnableTopicNaming(v))} />
<Switch checked={enableTopicNaming} onChange={(v) => setEnableTopicNaming(v)} />
</HStack>
<Divider style={{ margin: 0 }} />
<div>
@ -72,7 +71,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<Input.TextArea
autoSize={{ minRows: 3, maxRows: 10 }}
value={topicNamingPrompt || t('prompts.title')}
onChange={(e) => dispatch(setTopicNamingPrompt(e.target.value))}
onChange={(e) => setTopicNamingPrompt(e.target.value)}
placeholder={t('prompts.title')}
style={{ width: '100%' }}
/>

View File

@ -1,16 +1,11 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { usePreference } from '@data/hooks/usePreference'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistants, useDefaultAssistant, useDefaultModel } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setQuickAssistantId } from '@renderer/store/llm'
import {
setClickTrayToShowQuickAssistant,
setEnableQuickAssistant,
setReadClipboardAtStartup
} from '@renderer/store/settings'
import HomeWindow from '@renderer/windows/mini/home/HomeWindow'
import { Button, Select, Switch, Tooltip } from 'antd'
import { FC } from 'react'
@ -20,9 +15,17 @@ import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '.'
const QuickAssistantSettings: FC = () => {
const [enableQuickAssistant, setEnableQuickAssistant] = usePreference('feature.quick_assistant.enabled')
const [clickTrayToShowQuickAssistant, setClickTrayToShowQuickAssistant] = usePreference(
'feature.quick_assistant.click_tray_to_show'
)
const [readClipboardAtStartup, setReadClipboardAtStartup] = usePreference(
'feature.quick_assistant.read_clipboard_at_startup'
)
const [, setTray] = usePreference('app.tray.enabled')
const { t } = useTranslation()
const { theme } = useTheme()
const { enableQuickAssistant, clickTrayToShowQuickAssistant, setTray, readClipboardAtStartup } = useSettings()
const dispatch = useAppDispatch()
const { assistants } = useAssistants()
const { quickAssistantId } = useAppSelector((state) => state.llm)
@ -30,8 +33,7 @@ const QuickAssistantSettings: FC = () => {
const { defaultModel } = useDefaultModel()
const handleEnableQuickAssistant = async (enable: boolean) => {
dispatch(setEnableQuickAssistant(enable))
await window.api.config.set('enableQuickAssistant', enable, true)
await setEnableQuickAssistant(enable)
!enable && window.api.miniWindow.close()
@ -50,14 +52,12 @@ const QuickAssistantSettings: FC = () => {
}
const handleClickTrayToShowQuickAssistant = async (checked: boolean) => {
dispatch(setClickTrayToShowQuickAssistant(checked))
await window.api.config.set('clickTrayToShowQuickAssistant', checked)
await setClickTrayToShowQuickAssistant(checked)
checked && setTray(true)
}
const handleClickReadClipboardAtStartup = async (checked: boolean) => {
dispatch(setReadClipboardAtStartup(checked))
await window.api.config.set('readClipboardAtStartup', checked)
await setReadClipboardAtStartup(checked)
window.api.miniWindow.close()
}

View File

@ -26,7 +26,7 @@ import ocr from './ocr'
import paintings from './paintings'
import preprocess from './preprocess'
import runtime from './runtime'
// import selectionStore from './selectionStore'
import selectionStore from './selectionStore'
import settings from './settings'
import shortcuts from './shortcuts'
import tabs from './tabs'
@ -52,7 +52,7 @@ const rootReducer = combineReducers({
mcp,
memory,
copilot,
// selectionStore,
selectionStore,
tabs,
preprocess,
messages: newMessagesReducer,

View File

@ -1,7 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { SelectionActionItem, SelectionFilterMode, SelectionTriggerMode } from '@shared/data/preferenceTypes'
interface SelectionState {
export interface SelectionState {
selectionEnabled: boolean
triggerMode: SelectionTriggerMode
isCompact: boolean

View File

@ -863,133 +863,133 @@ const settingsSlice = createSlice({
})
export const {
setShowModelNameInMarkdown,
setShowModelProviderInMarkdown,
// setShowModelNameInMarkdown,
// setShowModelProviderInMarkdown,
// setShowAssistants,
// toggleShowAssistants,
// setShowTopics,
// toggleShowTopics,
setAssistantsTabSortType,
setSendMessageShortcut,
setLanguage,
setTargetLanguage,
setProxyMode,
setProxyUrl,
setProxyBypassRules,
setUserName,
setShowPrompt,
setShowMessageDivider,
setMessageFont,
setShowInputEstimatedTokens,
setLaunchOnBoot,
setLaunchToTray,
setTrayOnClose,
setTray,
setTheme,
setUserTheme,
setFontSize,
setWindowStyle,
setTopicPosition,
setShowTopicTime,
setPinTopicsToTop,
setAssistantIconType,
setPasteLongTextAsFile,
setAutoCheckUpdate,
setTestPlan,
setTestChannel,
setRenderInputMessageAsMarkdown,
setClickAssistantToShowTopic,
// setAssistantsTabSortType,
// setSendMessageShortcut,
// setLanguage,
// setTargetLanguage,
// setProxyMode,
// setProxyUrl,
// setProxyBypassRules,
// setUserName,
// setShowPrompt,
// setShowMessageDivider,
// setMessageFont,
// setShowInputEstimatedTokens,
// setLaunchOnBoot,
// setLaunchToTray,
// setTrayOnClose,
// setTray,
// setTheme,
// setUserTheme,
// setFontSize,
// setWindowStyle,
// setTopicPosition,
// setShowTopicTime,
// setPinTopicsToTop,
// setAssistantIconType,
// setPasteLongTextAsFile,
// setAutoCheckUpdate,
// setTestPlan,
// setTestChannel,
// setRenderInputMessageAsMarkdown,
// setClickAssistantToShowTopic,
setSkipBackupFile,
setWebdavHost,
setWebdavUser,
setWebdavPass,
setWebdavPath,
setWebdavAutoSync,
setWebdavSyncInterval,
setWebdavMaxBackups,
setWebdavSkipBackupFile,
setWebdavDisableStream,
setCodeExecution,
setCodeEditor,
setCodeViewer,
setCodeShowLineNumbers,
setCodeCollapsible,
setCodeWrappable,
setCodeImageTools,
setMathEngine,
setMathEnableSingleDollar,
setFoldDisplayMode,
setGridColumns,
setGridPopoverTrigger,
setMessageStyle,
// setWebdavHost,
// setWebdavUser,
// setWebdavPass,
// setWebdavPath,
// setWebdavAutoSync,
// setWebdavSyncInterval,
// setWebdavMaxBackups,
// setWebdavSkipBackupFile,
// setWebdavDisableStream,
// setCodeExecution,
// setCodeEditor,
// setCodeViewer,
// setCodeShowLineNumbers,
// setCodeCollapsible,
// setCodeWrappable,
// setCodeImageTools,
// setMathEngine,
// setMathEnableSingleDollar,
// setFoldDisplayMode,
// setGridColumns,
// setGridPopoverTrigger,
// setMessageStyle,
setTranslateModelPrompt,
setAutoTranslateWithSpace,
setShowTranslateConfirm,
setEnableTopicNaming,
setPasteLongTextThreshold,
setCustomCss,
setTopicNamingPrompt,
setSidebarIcons,
setNarrowMode,
setClickTrayToShowQuickAssistant,
setEnableQuickAssistant,
setReadClipboardAtStartup,
setMultiModelMessageStyle,
setNotionDatabaseID,
setNotionApiKey,
setNotionPageNameKey,
setmarkdownExportPath,
setForceDollarMathInMarkdown,
setUseTopicNamingForMessageTitle,
setThoughtAutoCollapse,
setNotionExportReasoning,
setExcludeCitationsInExport,
setStandardizeCitationsInExport,
setYuqueToken,
setYuqueRepoId,
setYuqueUrl,
setJoplinToken,
setJoplinUrl,
setJoplinExportReasoning,
// setAutoTranslateWithSpace,
// setShowTranslateConfirm,
// setEnableTopicNaming,
// setPasteLongTextThreshold,
// setCustomCss,
// setTopicNamingPrompt,
// setSidebarIcons,
// setNarrowMode,
// setClickTrayToShowQuickAssistant,
// setEnableQuickAssistant,
// setReadClipboardAtStartup,
// setMultiModelMessageStyle,
// setNotionDatabaseID,
// setNotionApiKey,
// setNotionPageNameKey,
// setmarkdownExportPath,
// setForceDollarMathInMarkdown,
// setUseTopicNamingForMessageTitle,
// setThoughtAutoCollapse,
// setNotionExportReasoning,
// setExcludeCitationsInExport,
// setStandardizeCitationsInExport,
// setYuqueToken,
// setYuqueRepoId,
// setYuqueUrl,
// setJoplinToken,
// setJoplinUrl,
// setJoplinExportReasoning,
setMessageNavigation,
setDefaultObsidianVault,
setDefaultAgent,
setSiyuanApiUrl,
setSiyuanToken,
setSiyuanBoxId,
// setSiyuanApiUrl,
// setSiyuanToken,
// setSiyuanBoxId,
setAgentssubscribeUrl,
setSiyuanRootPath,
setMaxKeepAliveMinapps,
setShowOpenedMinappsInSidebar,
setMinappsOpenLinkExternal,
setEnableDataCollection,
setEnableSpellCheck,
setSpellCheckLanguages,
setExportMenuOptions,
setEnableQuickPanelTriggers,
setConfirmDeleteMessage,
setConfirmRegenerateMessage,
// setSiyuanRootPath,
// setMaxKeepAliveMinapps,
// setShowOpenedMinappsInSidebar,
// setMinappsOpenLinkExternal,
// setEnableDataCollection,
// setEnableSpellCheck,
// setSpellCheckLanguages,
// setExportMenuOptions,
// setEnableQuickPanelTriggers,
// setConfirmDeleteMessage,
// setConfirmRegenerateMessage,
// setDisableHardwareAcceleration,
setOpenAISummaryText,
setOpenAIVerbosity,
setNotificationSettings,
// setNotificationSettings,
// Local backup settings
setLocalBackupDir,
setLocalBackupAutoSync,
setLocalBackupSyncInterval,
setLocalBackupMaxBackups,
setLocalBackupSkipBackupFile,
// setLocalBackupDir,
// setLocalBackupAutoSync,
// setLocalBackupSyncInterval,
// setLocalBackupMaxBackups,
// setLocalBackupSkipBackupFile,
setDefaultPaintingProvider,
setS3,
setS3Partial,
setEnableDeveloperMode,
setNavbarPosition,
setShowMessageOutline,
// setS3,
// setS3Partial,
// setEnableDeveloperMode,
// setNavbarPosition,
// setShowMessageOutline,
// API Server actions
setApiServerEnabled,
setApiServerPort,
setApiServerApiKey,
setShowWorkspace,
// setShowWorkspace,
toggleShowWorkspace
} = settingsSlice.actions