mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-20 23:22:05 +08:00
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:
parent
5d789ef394
commit
1b57ffeb56
@ -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'
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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))
|
||||
// },
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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')};
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -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%' }}
|
||||
/>
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user