diff --git a/packages/shared/data/preferenceTypes.ts b/packages/shared/data/preferenceTypes.ts index c9fa7af058..c5bc52dd7d 100644 --- a/packages/shared/data/preferenceTypes.ts +++ b/packages/shared/data/preferenceTypes.ts @@ -65,3 +65,11 @@ export enum UpgradeChannel { RC = 'rc', // 公测版本 BETA = 'beta' // 预览版本 } + +export type ChatMessageStyle = 'plain' | 'bubble' + +export type ChatMessageNavigationMode = 'none' | 'buttons' | 'anchor' + +export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' + +export type MultiModelGridPopoverTrigger = 'hover' | 'click' diff --git a/packages/shared/data/preferences.ts b/packages/shared/data/preferences.ts index d44284a37d..50c15f4967 100644 --- a/packages/shared/data/preferences.ts +++ b/packages/shared/data/preferences.ts @@ -13,7 +13,12 @@ import { TRANSLATE_PROMPT } from '@shared/config/prompts' import type { AssistantIconType, AssistantTabSortType, + ChatMessageNavigationMode, + ChatMessageStyle, + LanguageVarious, MultiModelFoldDisplayMode, + MultiModelGridPopoverTrigger, + MultiModelMessageStyle, ProxyMode, SelectionActionItem, SelectionFilterMode, @@ -22,7 +27,7 @@ import type { SidebarIcon, WindowStyle } from '@shared/data/preferenceTypes' -import { LanguageVarious, ThemeMode, UpgradeChannel } from '@shared/data/preferenceTypes' +import { ThemeMode, UpgradeChannel } from '@shared/data/preferenceTypes' /* eslint @typescript-eslint/member-ordering: ["error", { "interfaces": { "order": "alphabetically" }, @@ -148,11 +153,11 @@ export interface PreferencesType { // redux/settings/gridColumns 'chat.message.multi_model.grid_columns': number // redux/settings/gridPopoverTrigger - 'chat.message.multi_model.grid_popover_trigger': string + 'chat.message.multi_model.grid_popover_trigger': MultiModelGridPopoverTrigger // redux/settings/multiModelMessageStyle - 'chat.message.multi_model.style': string + 'chat.message.multi_model.style': MultiModelMessageStyle // redux/settings/messageNavigation - 'chat.message.navigation_mode': string + 'chat.message.navigation_mode': ChatMessageNavigationMode // redux/settings/renderInputMessageAsMarkdown 'chat.message.render_as_markdown': boolean // redux/settings/showMessageDivider @@ -162,7 +167,7 @@ export interface PreferencesType { // redux/settings/showPrompt 'chat.message.show_prompt': boolean // redux/settings/messageStyle - 'chat.message.style': string + 'chat.message.style': ChatMessageStyle // redux/settings/thoughtAutoCollapse 'chat.message.thought.auto_collapse': boolean // redux/settings/narrowMode diff --git a/src/renderer/src/components/ObsidianExportDialog.tsx b/src/renderer/src/components/ObsidianExportDialog.tsx index fba858763a..76b796319a 100644 --- a/src/renderer/src/components/ObsidianExportDialog.tsx +++ b/src/renderer/src/components/ObsidianExportDialog.tsx @@ -1,6 +1,6 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import i18n from '@renderer/i18n' -import store from '@renderer/store' import type { Topic } from '@renderer/types' import type { Message } from '@renderer/types/newMessage' import { @@ -12,7 +12,6 @@ import { } from '@renderer/utils/export' import { Alert, Empty, Form, Input, Modal, Select, Spin, Switch, TreeSelect } from 'antd' import React, { useEffect, useState } from 'react' - const logger = loggerService.withContext('ObsidianExportDialog') const { Option } = Select @@ -142,7 +141,7 @@ const PopupContainer: React.FC = ({ messages, topic }) => { - const defaultObsidianVault = store.getState().settings.defaultObsidianVault + const [defaultObsidianVault, setDefaultObsidianVault] = usePreference('data.integration.obsidian.default_vault') const [state, setState] = useState({ title, tags: obsidianTags || '', @@ -202,7 +201,7 @@ const PopupContainer: React.FC = ({ } } fetchVaults() - }, [defaultObsidianVault]) + }, [defaultObsidianVault, setDefaultObsidianVault]) useEffect(() => { if (selectedVault) { @@ -232,9 +231,9 @@ const PopupContainer: React.FC = ({ if (topic) { markdown = await topicToMarkdown(topic, exportReasoning) } else if (messages && messages.length > 0) { - markdown = messagesToMarkdown(messages, exportReasoning) + markdown = await messagesToMarkdown(messages, exportReasoning) } else if (message) { - markdown = exportReasoning ? messageToMarkdownWithReasoning(message) : messageToMarkdown(message) + markdown = exportReasoning ? await messageToMarkdownWithReasoning(message) : await messageToMarkdown(message) } else { markdown = '' } diff --git a/src/renderer/src/components/Popups/BackupPopup.tsx b/src/renderer/src/components/Popups/BackupPopup.tsx index c16cafa70a..4eef77c213 100644 --- a/src/renderer/src/components/Popups/BackupPopup.tsx +++ b/src/renderer/src/components/Popups/BackupPopup.tsx @@ -1,7 +1,7 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { getBackupProgressLabel } from '@renderer/i18n/label' import { backup } from '@renderer/services/BackupService' -import store from '@renderer/store' import { IpcChannel } from '@shared/IpcChannel' import { Modal, Progress } from 'antd' import { useEffect, useState } from 'react' @@ -27,7 +27,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [progressData, setProgressData] = useState() const { t } = useTranslation() - const skipBackupFile = store.getState().settings.skipBackupFile + const [skipBackupFile] = usePreference('data.backup.general.skip_backup_file') useEffect(() => { const removeListener = window.electron.ipcRenderer.on(IpcChannel.BackupProgress, (_, data: ProgressData) => { diff --git a/src/renderer/src/data/PreferenceService.ts b/src/renderer/src/data/PreferenceService.ts index 3db38f7ddb..abe37fa445 100644 --- a/src/renderer/src/data/PreferenceService.ts +++ b/src/renderer/src/data/PreferenceService.ts @@ -210,9 +210,9 @@ export class PreferenceService { } /** - * Get multiple preferences at once + * Get multiple preferences at once, return is Partial */ - public async getMultiple(keys: PreferenceKeyType[]): Promise> { + public async getMultipleRaw(keys: PreferenceKeyType[]): Promise> { // Check which keys are already cached const cachedResults: Partial = {} const uncachedKeys: PreferenceKeyType[] = [] @@ -258,6 +258,23 @@ export class PreferenceService { return cachedResults } + + /** + * Get multiple preferences at once and return them as a record of key-value pairs + */ + public async getMultiple>( + keys: T + ): Promise<{ [P in keyof T]: PreferenceDefaultScopeType[T[P]] }> { + const values = await this.getMultipleRaw(Object.values(keys)) + const result = {} as { [P in keyof T]: PreferenceDefaultScopeType[T[P]] } + + for (const key in keys) { + result[key] = values[keys[key]]! + } + + return result + } + /** * Set multiple preferences at once with configurable update strategy */ @@ -454,7 +471,7 @@ export class PreferenceService { if (uncachedKeys.length > 0) { try { - const values = await this.getMultiple(uncachedKeys) + const values = await this.getMultipleRaw(uncachedKeys) logger.debug(`Preloaded ${Object.keys(values).length} preferences`) } catch (error) { logger.error('Failed to preload preferences:', error as Error) diff --git a/src/renderer/src/data/hooks/usePreference.ts b/src/renderer/src/data/hooks/usePreference.ts index f8b8ba5461..e0675fd285 100644 --- a/src/renderer/src/data/hooks/usePreference.ts +++ b/src/renderer/src/data/hooks/usePreference.ts @@ -315,7 +315,7 @@ export function useMultiplePreferences 0) { - preferenceService.getMultiple(uncachedKeys).catch((error) => { + preferenceService.getMultipleRaw(uncachedKeys).catch((error) => { logger.error('Failed to load initial preferences:', error as Error) }) } diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index cf73d69f51..451f9f5789 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -21,25 +21,19 @@ import { useEffect } from 'react' import { useDefaultModel } from './useAssistant' import useFullScreenNotice from './useFullScreenNotice' import { useRuntime } from './useRuntime' -import { useSettings } from './useSettings' import useUpdateHandler from './useUpdateHandler' const logger = loggerService.withContext('useAppInit') export function useAppInit() { const dispatch = useAppDispatch() - const { - proxyUrl, - proxyBypassRules, - // language, - // windowStyle, - autoCheckUpdate, - proxyMode, - // customCss, - enableDataCollection - } = useSettings() const [language] = usePreference('app.language') const [windowStyle] = usePreference('ui.window_style') const [customCss] = usePreference('ui.custom_css') + const [proxyUrl] = usePreference('app.proxy.url') + const [proxyBypassRules] = usePreference('app.proxy.bypass_rules') + const [autoCheckUpdate] = usePreference('app.dist.auto_update.enabled') + const [proxyMode] = usePreference('app.proxy.mode') + const [enableDataCollection] = usePreference('app.privacy.data_collection.enabled') const { minappShow } = useRuntime() const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel() diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index 3be090d86b..0d8a328bbd 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -1,8 +1,10 @@ -import store, { useAppSelector } from '@renderer/store' -import { - SettingsState - // setWindowStyle -} from '@renderer/store/settings' +//TODO data refactor +// this file will be removed + +import { usePreference } from '@data/hooks/usePreference' +import { useAppSelector } from '@renderer/store' +import store from '@renderer/store' +import { SettingsState } from '@renderer/store/settings' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -87,7 +89,7 @@ export function useSettings() { } export function useMessageStyle() { - const { messageStyle } = useSettings() + const [messageStyle] = usePreference('chat.message.style') const isBubbleStyle = messageStyle === 'bubble' return { @@ -112,6 +114,6 @@ export const getStoreSetting = (key: keyof SettingsState) => { // } // } -export const getEnableDeveloperMode = () => { - return store.getState().settings.enableDeveloperMode -} +// export const getEnableDeveloperMode = () => { +// return store.getState().settings.enableDeveloperMode +// } diff --git a/src/renderer/src/hooks/useTopic.ts b/src/renderer/src/hooks/useTopic.ts index d2a71622fd..4e30745d49 100644 --- a/src/renderer/src/hooks/useTopic.ts +++ b/src/renderer/src/hooks/useTopic.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import db from '@renderer/databases' import i18n from '@renderer/i18n' import { fetchMessagesSummary } from '@renderer/services/ApiService' @@ -13,7 +14,6 @@ import { find, isEmpty } from 'lodash' import { useEffect, useState } from 'react' import { useAssistant } from './useAssistant' -import { getStoreSetting } from './useSettings' let _activeTopic: Topic let _setActiveTopic: (topic: Topic) => void @@ -109,7 +109,7 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) => topicRenamingLocks.add(topicId) const topic = await getTopicById(topicId) - const enableTopicNaming = getStoreSetting('enableTopicNaming') + const enableTopicNaming = await preferenceService.get('topic.naming.enabled') if (isEmpty(topic.messages)) { return diff --git a/src/renderer/src/hooks/useTranslate.ts b/src/renderer/src/hooks/useTranslate.ts index 4a4dacd53c..907bedb0f2 100644 --- a/src/renderer/src/hooks/useTranslate.ts +++ b/src/renderer/src/hooks/useTranslate.ts @@ -1,6 +1,6 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { builtinLanguages, UNKNOWN } from '@renderer/config/translate' -import { useAppSelector } from '@renderer/store' import { TranslateLanguage } from '@renderer/types' import { runAsyncFunction } from '@renderer/utils' import { getTranslateOptions } from '@renderer/utils/translate' @@ -16,7 +16,7 @@ const logger = loggerService.withContext('useTranslate') * - getLanguageByLangcode: 通过语言代码获取语言对象 */ export default function useTranslate() { - const prompt = useAppSelector((state) => state.settings.translateModelPrompt) + const [prompt] = usePreference('feature.translate.model_prompt') const [translateLanguages, setTranslateLanguages] = useState(builtinLanguages) const [isLoaded, setIsLoaded] = useState(false) diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts index f9ce442b50..456961f913 100644 --- a/src/renderer/src/init.ts +++ b/src/renderer/src/init.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import KeyvStorage from '@kangfenmao/keyv-storage' import { loggerService } from '@logger' @@ -5,8 +6,6 @@ import { startAutoSync } from './services/BackupService' import { startNutstoreAutoSync } from './services/NutstoreService' import storeSyncService from './services/StoreSyncService' import { webTraceService } from './services/WebTraceService' -import store from './store' - loggerService.initWindowSource('mainWindow') function initKeyv() { @@ -15,13 +14,18 @@ function initKeyv() { } function initAutoSync() { - setTimeout(() => { - const { webdavAutoSync, localBackupAutoSync, s3 } = store.getState().settings - const { nutstoreAutoSync } = store.getState().nutstore - if (webdavAutoSync || (s3 && s3.autoSync) || localBackupAutoSync) { + setTimeout(async () => { + const autoSyncStates = await preferenceService.getMultiple({ + webdav: 'data.backup.webdav.auto_sync', + local: 'data.backup.local.auto_sync', + s3: 'data.backup.s3.auto_sync', + nutstore: 'data.backup.nutstore.auto_sync' + }) + + if (autoSyncStates.webdav || autoSyncStates.s3 || autoSyncStates.local) { startAutoSync() } - if (nutstoreAutoSync) { + if (autoSyncStates.nutstore) { startNutstoreAutoSync() } }, 8000) diff --git a/src/renderer/src/pages/history/components/TopicMessages.tsx b/src/renderer/src/pages/history/components/TopicMessages.tsx index 9f5111a254..741933edad 100644 --- a/src/renderer/src/pages/history/components/TopicMessages.tsx +++ b/src/renderer/src/pages/history/components/TopicMessages.tsx @@ -1,9 +1,9 @@ import { MessageOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { HStack } from '@renderer/components/Layout' import SearchPopup from '@renderer/components/Popups/SearchPopup' import { MessageEditingProvider } from '@renderer/context/MessageEditingContext' import useScrollPosition from '@renderer/hooks/useScrollPosition' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { getTopicById } from '@renderer/hooks/useTopic' import { getAssistantById } from '@renderer/services/AssistantService' @@ -19,7 +19,6 @@ import { FC, useEffect, useState } from 'react' import styled from 'styled-components' import { default as MessageItem } from '../../home/Messages/Message' - interface Props extends React.HTMLAttributes { topic?: Topic } @@ -27,7 +26,7 @@ interface Props extends React.HTMLAttributes { const TopicMessages: FC = ({ topic: _topic, ...props }) => { const navigate = NavigationService.navigate! const { handleScroll, containerRef } = useScrollPosition('TopicMessages') - const { messageStyle } = useSettings() + const [messageStyle] = usePreference('chat.message.style') const { setTimeoutTimer } = useTimer() const [topic, setTopic] = useState(_topic) diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 85483d4b59..97c010263f 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -1,3 +1,4 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSearch' import { HStack } from '@renderer/components/Layout' @@ -6,7 +7,6 @@ import { QuickPanelProvider } from '@renderer/components/QuickPanel' import { useAssistant } from '@renderer/hooks/useAssistant' import { useChatContext } from '@renderer/hooks/useChatContext' import { useNavbarPosition } from '@renderer/hooks/useNavbar' -import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { useTimer } from '@renderer/hooks/useTimer' @@ -36,7 +36,9 @@ interface Props { const Chat: FC = (props) => { const { assistant } = useAssistant(props.assistant.id) - const { topicPosition, messageStyle, messageNavigation } = useSettings() + const [topicPosition] = usePreference('topic.position') + const [messageStyle] = usePreference('chat.message.style') + const [messageNavigation] = usePreference('chat.message.navigation_mode') const { showTopics } = useShowTopics() const { isMultiSelectMode } = useChatContext(props.activeTopic) const { isTopNavbar } = useNavbarPosition() @@ -178,7 +180,8 @@ const Chat: FC = (props) => { } export const useChatMaxWidth = () => { - const { showTopics, topicPosition } = useSettings() + const [showTopics] = usePreference('topic.tab.show') + const [topicPosition] = usePreference('topic.position') const { isLeftNavbar } = useNavbarPosition() const { showAssistants } = useShowAssistants() const showRightTopics = showTopics && topicPosition === 'right' diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index e5dca58f4d..fd42951f81 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,7 +1,7 @@ +import { usePreference } from '@data/hooks/usePreference' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { useAssistants } from '@renderer/hooks/useAssistant' import { useNavbarPosition } from '@renderer/hooks/useNavbar' -import { useSettings } from '@renderer/hooks/useSettings' import { useActiveTopic } from '@renderer/hooks/useTopic' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import NavigationService from '@renderer/services/NavigationService' @@ -30,7 +30,9 @@ const HomePage: FC = () => { const [activeAssistant, _setActiveAssistant] = useState(state?.assistant || _activeAssistant || assistants[0]) const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id, state?.topic) - const { showAssistants, showTopics, topicPosition } = useSettings() + const [showAssistants] = usePreference('assistant.tab.show') + const [showTopics] = usePreference('topic.tab.show') + const [topicPosition] = usePreference('topic.position') const dispatch = useDispatch() _activeAssistant = activeAssistant diff --git a/src/renderer/src/pages/home/Inputbar/TokenCount.tsx b/src/renderer/src/pages/home/Inputbar/TokenCount.tsx index d1d8f5f06e..8cf5e73316 100644 --- a/src/renderer/src/pages/home/Inputbar/TokenCount.tsx +++ b/src/renderer/src/pages/home/Inputbar/TokenCount.tsx @@ -1,6 +1,6 @@ +import { usePreference } from '@data/hooks/usePreference' import { HStack, VStack } from '@renderer/components/Layout' import MaxContextCount from '@renderer/components/MaxContextCount' -import { useSettings } from '@renderer/hooks/useSettings' import { Divider, Popover } from 'antd' import { ArrowUp, MenuIcon } from 'lucide-react' import { FC } from 'react' @@ -16,7 +16,7 @@ type Props = { const TokenCount: FC = ({ estimateTokenCount, inputTokenCount, contextCount }) => { const { t } = useTranslation() - const { showInputEstimatedTokens } = useSettings() + const [showInputEstimatedTokens] = usePreference('chat.input.show_estimated_tokens') if (!showInputEstimatedTokens) { return null diff --git a/src/renderer/src/pages/home/Markdown/Markdown.tsx b/src/renderer/src/pages/home/Markdown/Markdown.tsx index 6ff47e57a8..4ead23f46a 100644 --- a/src/renderer/src/pages/home/Markdown/Markdown.tsx +++ b/src/renderer/src/pages/home/Markdown/Markdown.tsx @@ -3,9 +3,9 @@ import 'katex/dist/contrib/copy-tex' import 'katex/dist/contrib/mhchem' import 'remark-github-blockquote-alert/alert.css' +import { usePreference } from '@data/hooks/usePreference' import ImageViewer from '@renderer/components/ImageViewer' import MarkdownShadowDOMRenderer from '@renderer/components/MarkdownShadowDOMRenderer' -import { useSettings } from '@renderer/hooks/useSettings' import { useSmoothStream } from '@renderer/hooks/useSmoothStream' import type { MainTextMessageBlock, ThinkingMessageBlock, TranslationMessageBlock } from '@renderer/types/newMessage' import { removeSvgEmptyLines } from '@renderer/utils/formats' @@ -46,7 +46,8 @@ interface Props { const Markdown: FC = ({ block, postProcess }) => { const { t } = useTranslation() - const { mathEngine, mathEnableSingleDollar } = useSettings() + const [mathEngine] = usePreference('chat.message.math.engine') + const [mathEnableSingleDollar] = usePreference('chat.message.math.single_dollar') const isTrulyDone = 'status' in block && block.status === 'success' const [displayedContent, setDisplayedContent] = useState(postProcess ? postProcess(block.content) : block.content) diff --git a/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx index d16e07a2b6..35bea65dd6 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/MainTextBlock.tsx @@ -1,4 +1,4 @@ -import { useSettings } from '@renderer/hooks/useSettings' +import { usePreference } from '@data/hooks/usePreference' import { getModelUniqId } from '@renderer/services/ModelService' import type { RootState } from '@renderer/store' import { selectFormattedCitationsByBlockId } from '@renderer/store/messageBlock' @@ -21,7 +21,7 @@ interface Props { const MainTextBlock: React.FC = ({ block, citationBlockId, role, mentions = [] }) => { // Use the passed citationBlockId directly in the selector - const { renderInputMessageAsMarkdown } = useSettings() + const [renderInputMessageAsMarkdown] = usePreference('chat.message.render_as_markdown') const rawCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId)) diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index 96f439fa2e..2252594cbc 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -1,7 +1,7 @@ import { CheckOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import ThinkingEffect from '@renderer/components/ThinkingEffect' -import { useSettings } from '@renderer/hooks/useSettings' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' import { Collapse, message as antdMessage, Tooltip } from 'antd' @@ -19,7 +19,9 @@ interface Props { const ThinkingBlock: React.FC = ({ block }) => { const [copied, setCopied] = useTemporaryValue(false, 2000) const { t } = useTranslation() - const { messageFont, fontSize, thoughtAutoCollapse } = useSettings() + const [messageFont] = usePreference('chat.message.font') + const [fontSize] = usePreference('chat.message.font_size') + const [thoughtAutoCollapse] = usePreference('chat.message.thought.auto_collapse') const [activeKey, setActiveKey] = useState<'thought' | ''>(thoughtAutoCollapse ? '' : 'thought') const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status]) diff --git a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx index 9cd1db2163..80d024ab60 100644 --- a/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx +++ b/src/renderer/src/pages/home/Messages/ChatFlowHistory.tsx @@ -1,12 +1,12 @@ import '@xyflow/react/dist/style.css' import { RobotOutlined, UserOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import { getModelLogo } from '@renderer/config/models' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { RootState } from '@renderer/store' @@ -205,7 +205,7 @@ const ChatFlowHistory: FC = ({ conversationId }) => { const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const [loading, setLoading] = useState(true) - const { userName } = useSettings() + const [userName] = usePreference('app.user.name') const { settedTheme } = useTheme() const topicId = conversationId diff --git a/src/renderer/src/pages/home/Messages/ChatNavigation.tsx b/src/renderer/src/pages/home/Messages/ChatNavigation.tsx index 7694942b4d..fc99d76ab1 100644 --- a/src/renderer/src/pages/home/Messages/ChatNavigation.tsx +++ b/src/renderer/src/pages/home/Messages/ChatNavigation.tsx @@ -6,7 +6,7 @@ import { VerticalAlignBottomOutlined, VerticalAlignTopOutlined } from '@ant-design/icons' -import { useSettings } from '@renderer/hooks/useSettings' +import { usePreference } from '@data/hooks/usePreference' import { RootState } from '@renderer/store' // import { selectCurrentTopicId } from '@renderer/store/newMessage' import { Button, Drawer, Tooltip } from 'antd' @@ -44,7 +44,8 @@ const ChatNavigation: FC = ({ containerId }) => { const [manuallyClosedUntil, setManuallyClosedUntil] = useState(null) const currentTopicId = useSelector((state: RootState) => state.messages.currentTopicId) const lastMoveTime = useRef(0) - const { topicPosition, showTopics } = useSettings() + const [topicPosition] = usePreference('topic.position') + const [showTopics] = usePreference('topic.tab.show') const showRightTopics = topicPosition === 'right' && showTopics // Reset hide timer and make buttons visible diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 48627224ca..ce8ac5254e 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -1,3 +1,4 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { useMessageEditing } from '@renderer/context/MessageEditingContext' @@ -5,7 +6,6 @@ import { useAssistant } from '@renderer/hooks/useAssistant' import { useChatContext } from '@renderer/hooks/useChatContext' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' import { useModel } from '@renderer/hooks/useModel' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getMessageModelId } from '@renderer/services/MessagesService' @@ -67,7 +67,12 @@ const MessageItem: FC = ({ const { assistant, setModel } = useAssistant(message.assistantId) const { isMultiSelectMode } = useChatContext(topic) const model = useModel(getMessageModelId(message), message.model?.provider) || message.model - const { messageFont, fontSize, messageStyle, showMessageOutline } = useSettings() + + const [messageFont] = usePreference('chat.message.font') + const [fontSize] = usePreference('chat.message.font_size') + const [messageStyle] = usePreference('chat.message.style') + const [showMessageOutline] = usePreference('chat.message.show_outline') + const { editMessageBlocks, resendUserMessageWithEdit, editMessage } = useMessageOperations(topic) const messageContainerRef = useRef(null) const { editingMessageId, stopEditing } = useMessageEditing() diff --git a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx index f4ac909b91..cfd48ca0c2 100644 --- a/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx +++ b/src/renderer/src/pages/home/Messages/MessageAnchorLine.tsx @@ -1,9 +1,9 @@ +import { usePreference } from '@data/hooks/usePreference' import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env' import { getModelLogo } from '@renderer/config/models' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { getMessageModelId } from '@renderer/services/MessagesService' import { getModelName } from '@renderer/services/ModelService' @@ -33,7 +33,7 @@ const MessageAnchorLine: FC = ({ messages }) => { const avatar = useAvatar() const { theme } = useTheme() const dispatch = useAppDispatch() - const { userName } = useSettings() + const [userName] = usePreference('app.user.name') const { setTimeoutTimer } = useTimer() const messagesListRef = useRef(null) diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 155312a368..6cb9dd4224 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -1,9 +1,9 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import CustomTag from '@renderer/components/Tags/CustomTag' import TranslateButton from '@renderer/components/TranslateButton' import { isGenerateImageModel, isVisionModel } from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import FileManager from '@renderer/services/FileManager' import PasteService from '@renderer/services/PasteService' @@ -45,7 +45,10 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC const [isFileDragging, setIsFileDragging] = useState(false) const { assistant } = useAssistant(message.assistantId) const model = assistant.model || assistant.defaultModel - const { pasteLongTextThreshold, fontSize, sendMessageShortcut, enableSpellCheck } = useSettings() + const [pasteLongTextThreshold] = usePreference('chat.input.paste_long_text_threshold') + const [fontSize] = usePreference('chat.message.font_size') + const [sendMessageShortcut] = usePreference('chat.input.send_message_shortcut') + const [enableSpellCheck] = usePreference('app.spell_check.enabled') const { t } = useTranslation() const textareaRef = useRef(null) const attachmentButtonRef = useRef(null) diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index 4632c9ffb9..afb45eec01 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -1,15 +1,15 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { MessageEditingProvider } from '@renderer/context/MessageEditingContext' import { useChatContext } from '@renderer/hooks/useChatContext' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { MultiModelMessageStyle } from '@renderer/store/settings' import type { Topic } from '@renderer/types' import type { Message } from '@renderer/types/newMessage' import { classNames } from '@renderer/utils' +import type { MultiModelMessageStyle } from '@shared/data/preferenceTypes' import { Popover } from 'antd' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import styled from 'styled-components' @@ -30,7 +30,9 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => { // Hooks const { editMessage } = useMessageOperations(topic) - const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings() + const [multiModelMessageStyleSetting] = usePreference('chat.message.multi_model.style') + const [gridColumns] = usePreference('chat.message.multi_model.grid_columns') + const [gridPopoverTrigger] = usePreference('chat.message.multi_model.grid_popover_trigger') const { isMultiSelectMode } = useChatContext(topic) const maxWidth = useChatMaxWidth() const { setTimeoutTimer } = useTimer() diff --git a/src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx b/src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx index 0e7b7ab289..f1d0b6d9e0 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroupMenuBar.tsx @@ -9,11 +9,11 @@ import { import { HStack } from '@renderer/components/Layout' import { useAssistant } from '@renderer/hooks/useAssistant' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' -import { MultiModelMessageStyle } from '@renderer/store/settings' import type { Topic } from '@renderer/types' import type { Message } from '@renderer/types/newMessage' import { AssistantMessageStatus } from '@renderer/types/newMessage' import { getMainTextContent } from '@renderer/utils/messageUtils/find' +import { MultiModelMessageStyle } from '@shared/data/preferenceTypes' import { Button, Tooltip } from 'antd' import { FC, memo } from 'react' import { useTranslation } from 'react-i18next' diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index faa147ef98..e7a5ecdc85 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -1,5 +1,6 @@ // import { InfoCircleOutlined } from '@ant-design/icons' import { usePreference } from '@data/hooks/usePreference' +import { useMultiplePreferences } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons' import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup' @@ -16,7 +17,7 @@ import useTranslate from '@renderer/hooks/useTranslate' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getMessageTitle } from '@renderer/services/MessagesService' import { translateText } from '@renderer/services/TranslateService' -import store, { RootState, useAppDispatch } from '@renderer/store' +import store, { useAppDispatch } from '@renderer/store' import { messageBlocksSelectors, removeOneBlock } from '@renderer/store/messageBlock' import { selectMessagesForTopic } from '@renderer/store/newMessage' import { TraceIcon } from '@renderer/trace/pages/Component' @@ -107,7 +108,19 @@ const MessageMenubar: FC = (props) => { const isUserMessage = message.role === 'user' - const exportMenuOptions = useSelector((state: RootState) => state.settings.exportMenuOptions) + const [exportMenuOptions] = 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 dispatch = useAppDispatch() // const processedMessage = useMemo(() => { @@ -263,7 +276,7 @@ const MessageMenubar: FC = (props) => { key: 'clipboard', onClick: async () => { const title = await getMessageTitle(message) - const markdown = messageToMarkdown(message) + const markdown = await messageToMarkdown(message) exportMessageToNotes(title, markdown, notesPath) } } @@ -315,7 +328,7 @@ const MessageMenubar: FC = (props) => { label: t('chat.topics.export.word'), key: 'word', onClick: async () => { - const markdown = messageToMarkdown(message) + const markdown = await messageToMarkdown(message) const title = await getMessageTitle(message) window.api.export.toWord(markdown, title) } @@ -325,7 +338,7 @@ const MessageMenubar: FC = (props) => { key: 'notion', onClick: async () => { const title = await getMessageTitle(message) - const markdown = messageToMarkdown(message) + const markdown = await messageToMarkdown(message) exportMessageToNotion(title, markdown, message) } }, @@ -334,7 +347,7 @@ const MessageMenubar: FC = (props) => { key: 'yuque', onClick: async () => { const title = await getMessageTitle(message) - const markdown = messageToMarkdown(message) + const markdown = await messageToMarkdown(message) exportMarkdownToYuque(title, markdown) } }, @@ -359,7 +372,7 @@ const MessageMenubar: FC = (props) => { key: 'siyuan', onClick: async () => { const title = await getMessageTitle(message) - const markdown = messageToMarkdown(message) + const markdown = await messageToMarkdown(message) exportMarkdownToSiyuan(title, markdown) } } diff --git a/src/renderer/src/pages/home/Messages/MessageTools.tsx b/src/renderer/src/pages/home/Messages/MessageTools.tsx index 2a5077f48d..e293124ae2 100644 --- a/src/renderer/src/pages/home/Messages/MessageTools.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTools.tsx @@ -1,8 +1,8 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { CopyIcon, LoadingIcon } from '@renderer/components/Icons' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useMCPServers } from '@renderer/hooks/useMCPServers' -import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import type { ToolMessageBlock } from '@renderer/types/newMessage' import { isToolAutoApproved } from '@renderer/utils/mcp-tools' @@ -49,7 +49,8 @@ const MessageTools: FC = ({ block }) => { const [copiedMap, setCopiedMap] = useState>({}) const [countdown, setCountdown] = useState(COUNTDOWN_TIME) const { t } = useTranslation() - const { messageFont, fontSize } = useSettings() + const [messageFont] = usePreference('chat.message.font') + const [fontSize] = usePreference('chat.message.font_size') const { mcpServers, updateMCPServer } = useMCPServers() const [expandedResponse, setExpandedResponse] = useState<{ content: string; title: string } | null>(null) const [progress, setProgress] = useState(0) diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index eb13f81621..d160bf4b88 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -1,3 +1,4 @@ +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import ContextMenu from '@renderer/components/ContextMenu' import { LoadingIcon } from '@renderer/components/Icons' @@ -7,7 +8,6 @@ import { useAssistant } from '@renderer/hooks/useAssistant' import { useChatContext } from '@renderer/hooks/useChatContext' import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations' import useScrollPosition from '@renderer/hooks/useScrollPosition' -import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' import { useTimer } from '@renderer/hooks/useTimer' import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic' @@ -62,7 +62,8 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const [isProcessingContext, setIsProcessingContext] = useState(false) const { updateTopic, addTopic } = useAssistant(assistant.id) - const { showPrompt, messageNavigation } = useSettings() + const [showPrompt] = usePreference('chat.message.show_prompt') + const [messageNavigation] = usePreference('chat.message.navigation_mode') const { t } = useTranslation() const dispatch = useAppDispatch() const messages = useTopicMessages(topic.id) diff --git a/src/renderer/src/pages/home/Messages/NarrowLayout.tsx b/src/renderer/src/pages/home/Messages/NarrowLayout.tsx index 7a7c641670..50506a417a 100644 --- a/src/renderer/src/pages/home/Messages/NarrowLayout.tsx +++ b/src/renderer/src/pages/home/Messages/NarrowLayout.tsx @@ -1,4 +1,4 @@ -import { useSettings } from '@renderer/hooks/useSettings' +import { usePreference } from '@data/hooks/usePreference' import { FC, HTMLAttributes } from 'react' import styled from 'styled-components' @@ -7,7 +7,7 @@ interface Props extends HTMLAttributes { } const NarrowLayout: FC = ({ children, ...props }) => { - const { narrowMode } = useSettings() + const [narrowMode] = usePreference('chat.narrow_mode') return ( diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index 0c3cfd951d..8f2639b226 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -1,4 +1,5 @@ import { usePreference } from '@data/hooks/usePreference' +import { useMultiplePreferences } from '@data/hooks/usePreference' import { DraggableVirtualList } from '@renderer/components/DraggableList' import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons' import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup' @@ -191,7 +192,18 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic, [setActiveTopic] ) - const exportMenuOptions = useSelector((state: RootState) => state.settings.exportMenuOptions) + const [exportMenuOptions] = 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 [_targetTopic, setTargetTopic] = useState(null) const targetTopic = useDeferredValue(_targetTopic) diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 843f734e70..2bf0101e13 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -1,7 +1,7 @@ +import { usePreference } from '@data/hooks/usePreference' import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useNavbarPosition } from '@renderer/hooks/useNavbar' -import { useSettings } from '@renderer/hooks/useSettings' import { useShowTopics } from '@renderer/hooks/useStore' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { Assistant, Topic } from '@renderer/types' @@ -39,7 +39,7 @@ const HomeTabs: FC = ({ }) => { const { addAssistant } = useAssistants() const [tab, setTab] = useState(position === 'left' ? _tab || 'assistants' : 'topic') - const { topicPosition } = useSettings() + const [topicPosition] = usePreference('topic.position') const { defaultAssistant } = useDefaultAssistant() const { toggleShowTopics } = useShowTopics() const { isLeftNavbar } = useNavbarPosition() diff --git a/src/renderer/src/pages/home/components/UpdateAppButton.tsx b/src/renderer/src/pages/home/components/UpdateAppButton.tsx index 997590ef10..ba0ad1c20d 100644 --- a/src/renderer/src/pages/home/components/UpdateAppButton.tsx +++ b/src/renderer/src/pages/home/components/UpdateAppButton.tsx @@ -1,6 +1,6 @@ import { SyncOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' import { Button } from 'antd' import { FC } from 'react' import { useTranslation } from 'react-i18next' @@ -8,7 +8,7 @@ import styled from 'styled-components' const UpdateAppButton: FC = () => { const { update } = useRuntime() - const { autoCheckUpdate } = useSettings() + const [autoCheckUpdate] = usePreference('app.dist.auto_update.enabled') const { t } = useTranslation() if (!update) { diff --git a/src/renderer/src/pages/knowledge/components/QuotaTag.tsx b/src/renderer/src/pages/knowledge/components/QuotaTag.tsx index b6f29ce5d1..9db551aab6 100644 --- a/src/renderer/src/pages/knowledge/components/QuotaTag.tsx +++ b/src/renderer/src/pages/knowledge/components/QuotaTag.tsx @@ -1,6 +1,6 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { usePreprocessProvider } from '@renderer/hooks/usePreprocess' -import { getStoreSetting } from '@renderer/hooks/useSettings' import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' import { KnowledgeBase, PreprocessProviderId } from '@renderer/types' import { Tag } from 'antd' @@ -28,7 +28,7 @@ const QuotaTag: FC<{ base: KnowledgeBase; providerId: PreprocessProviderId; quot return } if (quota === undefined) { - const userId = getStoreSetting('userId') + const userId = await preferenceService.get('app.user.id') const baseParams = getKnowledgeBaseParams(base) try { const response = await window.api.knowledgeBase.checkQuota({ diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index e9bbac452a..006dcee201 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg' @@ -13,7 +14,6 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' import { getProviderLabel } from '@renderer/i18n/label' import FileManager from '@renderer/services/FileManager' import { translateText } from '@renderer/services/TranslateService' @@ -94,7 +94,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { const { generating } = useRuntime() const navigate = useNavigate() const location = useLocation() - const { autoTranslateWithSpace } = useSettings() + const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space') const spaceClickTimer = useRef(null) const aihubmixProvider = providers.find((p) => p.id === 'aihubmix')! diff --git a/src/renderer/src/pages/paintings/NewApiPage.tsx b/src/renderer/src/pages/paintings/NewApiPage.tsx index 30bcad5f6b..518b146446 100644 --- a/src/renderer/src/pages/paintings/NewApiPage.tsx +++ b/src/renderer/src/pages/paintings/NewApiPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg' @@ -12,7 +13,6 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' import { getPaintingsBackgroundOptionsLabel, getPaintingsImageSizeOptionsLabel, @@ -85,7 +85,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { const { generating } = useRuntime() const navigate = useNavigate() const location = useLocation() - const { autoTranslateWithSpace } = useSettings() + const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space') const spaceClickTimer = useRef(null) const newApiProvider = providers.find((p) => p.id === 'new-api')! diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index 4b4f2bb95c..32a9eee79e 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import ImageSize1_1 from '@renderer/assets/images/paintings/image-size-1-1.svg' @@ -17,7 +18,6 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' import { getProviderLabel } from '@renderer/i18n/label' import { getProviderByModel } from '@renderer/services/AssistantService' import FileManager from '@renderer/services/FileManager' @@ -303,7 +303,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { setCurrentImageIndex(0) } - const { autoTranslateWithSpace } = useSettings() + const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space') const [spaceClickCount, setSpaceClickCount] = useState(0) const [isTranslating, setIsTranslating] = useState(false) const spaceClickTimer = useRef(null) diff --git a/src/renderer/src/pages/paintings/TokenFluxPage.tsx b/src/renderer/src/pages/paintings/TokenFluxPage.tsx index 0860b62ee9..26a188d38a 100644 --- a/src/renderer/src/pages/paintings/TokenFluxPage.tsx +++ b/src/renderer/src/pages/paintings/TokenFluxPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' +import { usePreference } from '@data/hooks/usePreference' import { loggerService } from '@logger' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' import Scrollbar from '@renderer/components/Scrollbar' @@ -9,7 +10,6 @@ import { LanguagesEnum } from '@renderer/config/translate' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' -import { useSettings } from '@renderer/hooks/useSettings' import { getProviderLabel } from '@renderer/i18n/label' import FileManager from '@renderer/services/FileManager' import { translateText } from '@renderer/services/TranslateService' @@ -74,7 +74,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { const { generating } = useRuntime() const navigate = useNavigate() const location = useLocation() - const { autoTranslateWithSpace } = useSettings() + const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space') const spaceClickTimer = useRef(null) const tokenfluxProvider = providers.find((p) => p.id === 'tokenflux')! const textareaRef = useRef(null) diff --git a/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts b/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts index 7911646630..bb50514b66 100644 --- a/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts @@ -1,6 +1,6 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' -import store from '@renderer/store' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse, WebSearchProviderResult } from '@renderer/types' import { createAbortPromise } from '@renderer/utils/abortController' @@ -30,7 +30,7 @@ export default class LocalSearchProvider extends BaseWebSearchProvider { httpOptions?: RequestInit ): Promise { const uid = nanoid() - const language = store.getState().settings.language + const language = await preferenceService.get('app.language') try { if (!query.trim()) { throw new Error('Search query cannot be empty') diff --git a/src/renderer/src/queue/KnowledgeQueue.ts b/src/renderer/src/queue/KnowledgeQueue.ts index 276e7f22cb..ec443beb67 100644 --- a/src/renderer/src/queue/KnowledgeQueue.ts +++ b/src/renderer/src/queue/KnowledgeQueue.ts @@ -1,6 +1,6 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import db from '@renderer/databases' -import { getStoreSetting } from '@renderer/hooks/useSettings' import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' import { NotificationService } from '@renderer/services/NotificationService' import store from '@renderer/store' @@ -96,7 +96,7 @@ class KnowledgeQueue { private async processItem(baseId: string, item: KnowledgeItem): Promise { const notificationService = NotificationService.getInstance() - const userId = getStoreSetting('userId') + const userId = await preferenceService.get('app.user.id') try { if (item.retryCount && item.retryCount >= this.MAX_RETRIES) { logger.info(`Item ${item.id} has reached max retries, skipping`) diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index 8604b3e520..c7ee3e0cba 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { CompletionsParams } from '@renderer/aiCore/middleware/schemas' import { SYSTEM_PROMPT_THRESHOLD } from '@renderer/config/constant' @@ -12,7 +13,6 @@ import { isWebSearchModel } from '@renderer/config/models' import { getModel } from '@renderer/hooks/useModel' -import { getStoreSetting } from '@renderer/hooks/useSettings' import i18n from '@renderer/i18n' import { currentSpan, withSpanResult } from '@renderer/services/SpanManagerService' import store from '@renderer/store' @@ -680,7 +680,7 @@ export async function fetchLanguageDetection({ text, onResponse }: FetchLanguage } export async function fetchMessagesSummary({ messages, assistant }: { messages: Message[]; assistant: Assistant }) { - let prompt = (getStoreSetting('topicNamingPrompt') as string) || i18n.t('prompts.title') + let prompt = (await preferenceService.get('topic.naming_prompt')) || i18n.t('prompts.title') const model = getQuickModel() || assistant.model || getDefaultModel() if (prompt && containsSupportedVariables(prompt)) { diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index fe4c040d0d..500a009fd8 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { DEFAULT_CONTEXTCOUNT, @@ -52,7 +53,10 @@ export function getDefaultAssistant(): Assistant { } } -export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, text: string): TranslateAssistant { +export async function getDefaultTranslateAssistant( + targetLanguage: TranslateLanguage, + text: string +): Promise { const model = getTranslateModel() const assistant: Assistant = getDefaultAssistant() @@ -77,10 +81,8 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, prompt = '' } else { content = 'follow system instruction' - prompt = store - .getState() - .settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value) - .replaceAll('{{text}}', text) + const translateModelPrompt = await preferenceService.get('feature.translate.model_prompt') + prompt = translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value).replaceAll('{{text}}', text) } const translateAssistant = { diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index 6216d2ae38..838bda68bb 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -1,3 +1,7 @@ +//TODO Data Refactor +// The code is messy, need to refactor all the backup related code + +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import db from '@renderer/databases' import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades' @@ -164,7 +168,16 @@ export async function backupToWebdav({ webdavMaxBackups, webdavSkipBackupFile, webdavDisableStream - } = store.getState().settings + } = await preferenceService.getMultiple({ + webdavHost: 'data.backup.webdav.host', + webdavUser: 'data.backup.webdav.user', + webdavPass: 'data.backup.webdav.pass', + webdavPath: 'data.backup.webdav.path', + webdavMaxBackups: 'data.backup.webdav.max_backups', + webdavSkipBackupFile: 'data.backup.webdav.skip_backup_file', + webdavDisableStream: 'data.backup.webdav.disable_stream' + }) + let deviceType = 'unknown' let hostname = 'unknown' try { @@ -294,7 +307,12 @@ export async function backupToWebdav({ // 从 webdav 恢复 export async function restoreFromWebdav(fileName?: string) { - const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings + const { webdavHost, webdavUser, webdavPass, webdavPath } = await preferenceService.getMultiple({ + webdavHost: 'data.backup.webdav.host', + webdavUser: 'data.backup.webdav.user', + webdavPass: 'data.backup.webdav.pass', + webdavPath: 'data.backup.webdav.path' + }) let data = '' try { @@ -334,7 +352,18 @@ export async function backupToS3({ store.dispatch(setS3SyncState({ syncing: true, lastSyncError: null })) - const s3Config = store.getState().settings.s3 + const s3Config = await preferenceService.getMultiple({ + autoSync: 'data.backup.s3.auto_sync', + accessKeyId: 'data.backup.s3.access_key_id', + secretAccessKey: 'data.backup.s3.secret_access_key', + endpoint: 'data.backup.s3.endpoint', + bucket: 'data.backup.s3.bucket', + region: 'data.backup.s3.region', + root: 'data.backup.s3.root', + maxBackups: 'data.backup.s3.max_backups', + skipBackupFile: 'data.backup.s3.skip_backup_file', + syncInterval: 'data.backup.s3.sync_interval' + }) let deviceType = 'unknown' let hostname = 'unknown' try { @@ -445,7 +474,18 @@ export async function backupToS3({ // 从 S3 恢复 export async function restoreFromS3(fileName?: string) { - const s3Config = store.getState().settings.s3 + const s3Config = await preferenceService.getMultiple({ + autoSync: 'data.backup.s3.auto_sync', + accessKeyId: 'data.backup.s3.access_key_id', + secretAccessKey: 'data.backup.s3.secret_access_key', + endpoint: 'data.backup.s3.endpoint', + bucket: 'data.backup.s3.bucket', + region: 'data.backup.s3.region', + root: 'data.backup.s3.root', + maxBackups: 'data.backup.s3.max_backups', + skipBackupFile: 'data.backup.s3.skip_backup_file', + syncInterval: 'data.backup.s3.sync_interval' + }) if (!fileName) { const files = await window.api.backup.listS3Files(s3Config) @@ -481,12 +521,22 @@ let isLocalAutoBackupRunning = false type BackupType = 'webdav' | 's3' | 'local' -export function startAutoSync(immediate = false, type?: BackupType) { +export async function startAutoSync(immediate = false, type?: BackupType) { // 如果没有指定类型,启动所有配置的自动同步 if (!type) { - const settings = store.getState().settings - const { webdavAutoSync, webdavHost, localBackupAutoSync, localBackupDir } = settings - const s3Settings = settings.s3 + const { webdavAutoSync, webdavHost, localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({ + webdavAutoSync: 'data.backup.webdav.auto_sync', + webdavHost: 'data.backup.webdav.host', + localBackupAutoSync: 'data.backup.local.auto_sync', + localBackupDir: 'data.backup.local.dir' + }) + const s3Settings = await preferenceService.getMultiple({ + autoSync: 'data.backup.s3.auto_sync', + endpoint: 'data.backup.s3.endpoint', + bucket: 'data.backup.s3.bucket', + region: 'data.backup.s3.region', + root: 'data.backup.s3.root' + }) if (webdavAutoSync && webdavHost) { startAutoSync(immediate, 'webdav') @@ -506,8 +556,10 @@ export function startAutoSync(immediate = false, type?: BackupType) { return } - const settings = store.getState().settings - const { webdavAutoSync, webdavHost } = settings + const { webdavAutoSync, webdavHost } = await preferenceService.getMultiple({ + webdavAutoSync: 'data.backup.webdav.auto_sync', + webdavHost: 'data.backup.webdav.host' + }) if (!webdavAutoSync || !webdavHost) { logger.info('[WebdavAutoSync] Invalid sync settings, auto sync disabled') @@ -522,8 +574,10 @@ export function startAutoSync(immediate = false, type?: BackupType) { return } - const settings = store.getState().settings - const s3Settings = settings.s3 + const s3Settings = await preferenceService.getMultiple({ + autoSync: 'data.backup.s3.auto_sync', + endpoint: 'data.backup.s3.endpoint' + }) if (!s3Settings?.autoSync || !s3Settings?.endpoint) { logger.verbose('Invalid sync settings, auto sync disabled') @@ -538,8 +592,10 @@ export function startAutoSync(immediate = false, type?: BackupType) { return } - const settings = store.getState().settings - const { localBackupAutoSync, localBackupDir } = settings + const { localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({ + localBackupAutoSync: 'data.backup.local.auto_sync', + localBackupDir: 'data.backup.local.dir' + }) if (!localBackupAutoSync || !localBackupDir) { logger.verbose('Invalid sync settings, auto sync disabled') @@ -551,13 +607,15 @@ export function startAutoSync(immediate = false, type?: BackupType) { scheduleNextBackup(immediate ? 'immediate' : 'fromLastSyncTime', 'local') } - function scheduleNextBackup(scheduleType: 'immediate' | 'fromLastSyncTime' | 'fromNow', backupType: BackupType) { + async function scheduleNextBackup( + scheduleType: 'immediate' | 'fromLastSyncTime' | 'fromNow', + backupType: BackupType + ) { let syncInterval: number let lastSyncTime: number | undefined let logPrefix: string // 根据备份类型获取相应的配置和状态 - const settings = store.getState().settings const backup = store.getState().backup if (backupType === 'webdav') { @@ -565,7 +623,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { clearTimeout(webdavSyncTimeout) webdavSyncTimeout = null } - syncInterval = settings.webdavSyncInterval + syncInterval = await preferenceService.get('data.backup.webdav.sync_interval') lastSyncTime = backup.webdavSync?.lastSyncTime || undefined logPrefix = '[WebdavAutoSync]' } else if (backupType === 's3') { @@ -573,7 +631,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { clearTimeout(s3SyncTimeout) s3SyncTimeout = null } - syncInterval = settings.s3?.syncInterval || 0 + syncInterval = await preferenceService.get('data.backup.s3.sync_interval') lastSyncTime = backup.s3Sync?.lastSyncTime || undefined logPrefix = '[S3AutoSync]' } else if (backupType === 'local') { @@ -581,7 +639,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { clearTimeout(localSyncTimeout) localSyncTimeout = null } - syncInterval = settings.localBackupSyncInterval + syncInterval = await preferenceService.get('data.backup.local.sync_interval') lastSyncTime = backup.localBackupSync?.lastSyncTime || undefined logPrefix = '[LocalAutoSync]' } else { @@ -921,11 +979,12 @@ export async function backupToLocal({ store.dispatch(setLocalBackupSyncState({ syncing: true, lastSyncError: null })) - const { - localBackupDir: localBackupDirSetting, - localBackupMaxBackups, - localBackupSkipBackupFile - } = store.getState().settings + const { localBackupDirSetting, localBackupMaxBackups, localBackupSkipBackupFile } = + await preferenceService.getMultiple({ + localBackupDirSetting: 'data.backup.local.dir', + localBackupMaxBackups: 'data.backup.local.max_backups', + localBackupSkipBackupFile: 'data.backup.local.skip_backup_file' + }) const localBackupDir = await window.api.resolvePath(localBackupDirSetting) let deviceType = 'unknown' let hostname = 'unknown' @@ -1049,7 +1108,7 @@ export async function backupToLocal({ export async function restoreFromLocal(fileName: string) { try { - const { localBackupDir: localBackupDirSetting } = store.getState().settings + const localBackupDirSetting = await preferenceService.get('data.backup.local.dir') const localBackupDir = await window.api.resolvePath(localBackupDirSetting) const restoreData = await window.api.backup.restoreFromLocalBackup(fileName, localBackupDir) const data = JSON.parse(restoreData) diff --git a/src/renderer/src/services/NotificationService.ts b/src/renderer/src/services/NotificationService.ts index cb77cf7d6b..41ed98afc0 100644 --- a/src/renderer/src/services/NotificationService.ts +++ b/src/renderer/src/services/NotificationService.ts @@ -1,5 +1,4 @@ -import store from '@renderer/store' -import { initialState as defaultNotificationSettings } from '@renderer/store/settings' +import { preferenceService } from '@data/PreferenceService' import type { Notification } from '@renderer/types/notification' import { NotificationQueue } from '../queue/NotificationQueue' @@ -25,7 +24,11 @@ export class NotificationService { * @param notification 要发送的通知 */ public async send(notification: Notification): Promise { - const notificationSettings = store.getState().settings.notification || defaultNotificationSettings + const notificationSettings = await preferenceService.getMultiple({ + assistant: 'app.notification.assistant.enabled', + backup: 'app.notification.backup.enabled', + knowledge: 'app.notification.knowledge.enabled' + }) if (notificationSettings[notification.source]) { this.queue.add(notification) diff --git a/src/renderer/src/services/TranslateService.ts b/src/renderer/src/services/TranslateService.ts index 5d09933d46..335c5d2a37 100644 --- a/src/renderer/src/services/TranslateService.ts +++ b/src/renderer/src/services/TranslateService.ts @@ -87,7 +87,7 @@ export const translateText = async ( abortKey?: string ) => { try { - const assistant = getDefaultTranslateAssistant(targetLanguage, text) + const assistant = await getDefaultTranslateAssistant(targetLanguage, text) const translatedText = await fetchTranslate({ assistant, onResponse, abortKey }) diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 7c9cb714fa..b540a7eae3 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -16,7 +16,7 @@ import llm from './llm' import mcp from './mcp' import memory from './memory' import messageBlocksReducer from './messageBlock' -import migrate from './migrate' +// import migrate from './migrate' import minapps from './minapps' import newMessagesReducer from './newMessage' import { setNotesPath } from './note' @@ -68,8 +68,8 @@ const persistedReducer = persistReducer( key: 'cherry-studio', storage, version: 144, - blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'], - migrate + blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'] + // migrate }, rootReducer ) diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 3e3fc98c2a..b2f059c3ce 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1,2346 +1,2346 @@ -import { loggerService } from '@logger' -import { nanoid } from '@reduxjs/toolkit' -import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' -import { - glm45FlashModel, - isFunctionCallingModel, - isNotSupportedTextDelta, - SYSTEM_MODELS -} from '@renderer/config/models' -import { BUILTIN_OCR_PROVIDERS, BUILTIN_OCR_PROVIDERS_MAP, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr' -import { - isSupportArrayContentProvider, - isSupportDeveloperRoleProvider, - isSupportStreamOptionsProvider, - SYSTEM_PROVIDERS -} from '@renderer/config/providers' -// import { DEFAULT_SIDEBAR_ICONS } from '@renderer/config/sidebar' -import db from '@renderer/databases' -import i18n from '@renderer/i18n' -import { DEFAULT_ASSISTANT_SETTINGS } from '@renderer/services/AssistantService' -import { - Assistant, - BuiltinOcrProvider, - isSystemProvider, - Model, - Provider, - ProviderApiOptions, - SystemProviderIds, - TranslateLanguageCode, - WebSearchProvider -} from '@renderer/types' -import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils' -import { defaultByPassRules } from '@shared/config/constant' -import { TRANSLATE_PROMPT } from '@shared/config/prompts' -import { DefaultPreferences } from '@shared/data/preferences' -import { UpgradeChannel } from '@shared/data/preferenceTypes' -import { isEmpty } from 'lodash' -import { createMigrate } from 'redux-persist' - -import { RootState } from '.' -import { DEFAULT_TOOL_ORDER } from './inputTools' -import { initialState as llmInitialState, moveProvider } from './llm' -import { mcpSlice } from './mcp' -import { initialState as notesInitialState } from './note' -// import { defaultActionItems } from './selectionStore' -import { initialState as settingsInitialState } from './settings' -import { initialState as shortcutsInitialState } from './shortcuts' -import { defaultWebSearchProviders } from './websearch' -const logger = loggerService.withContext('Migrate') - -// remove logo base64 data to reduce the size of the state -function removeMiniAppIconsFromState(state: RootState) { - if (state.minapps) { - state.minapps.enabled = state.minapps.enabled.map((app) => ({ ...app, logo: undefined })) - state.minapps.disabled = state.minapps.disabled.map((app) => ({ ...app, logo: undefined })) - state.minapps.pinned = state.minapps.pinned.map((app) => ({ ...app, logo: undefined })) - } -} - -function removeMiniAppFromState(state: RootState, id: string) { - if (state.minapps) { - state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== id) - state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== id) - } -} - -function addMiniApp(state: RootState, id: string) { - if (state.minapps) { - const app = DEFAULT_MIN_APPS.find((app) => app.id === id) - if (app) { - if (!state.minapps.enabled.find((app) => app.id === id)) { - state.minapps.enabled.push(app) - } - } - } -} - -// add provider to state -function addProvider(state: RootState, id: string) { - if (!state.llm.providers.find((p) => p.id === id)) { - const _provider = SYSTEM_PROVIDERS.find((p) => p.id === id) - if (_provider) { - state.llm.providers.push(_provider) - } - } -} - -// add ocr provider -function addOcrProvider(state: RootState, provider: BuiltinOcrProvider) { - if (!state.ocr.providers.find((p) => p.id === provider.id)) { - state.ocr.providers.push(provider) - } -} - -function updateProvider(state: RootState, id: string, provider: Partial) { - if (state.llm.providers) { - const index = state.llm.providers.findIndex((p) => p.id === id) - if (index !== -1) { - state.llm.providers[index] = { ...state.llm.providers[index], ...provider } - } - } -} - -function addWebSearchProvider(state: RootState, id: string) { - if (state.websearch && state.websearch.providers) { - if (!state.websearch.providers.find((p) => p.id === id)) { - const provider = defaultWebSearchProviders.find((p) => p.id === id) - if (provider) { - // Prevent mutating read only property of object - // Otherwise, it will cause the error: Cannot assign to read only property 'apiKey' of object '#' - state.websearch.providers.push({ ...provider }) - } - } - } -} - -function updateWebSearchProvider(state: RootState, provider: Partial) { - if (state.websearch && state.websearch.providers) { - const index = state.websearch.providers.findIndex((p) => p.id === provider.id) - if (index !== -1) { - state.websearch.providers[index] = { - ...state.websearch.providers[index], - ...provider - } - } - } -} - -function addSelectionAction(state: RootState, id: string) { - // if (state.selectionStore && state.selectionStore.actionItems) { - // if (!state.selectionStore.actionItems.some((item) => item.id === id)) { - // const action = defaultActionItems.find((item) => item.id === id) - // if (action) { - // state.selectionStore.actionItems.push(action) - // } - // } - // } - return [state, id] -} - -/** - * Add shortcuts(ids from shortcutsInitialState) after the shortcut(afterId) - * if afterId is 'first', add to the first - * if afterId is 'last', add to the last - */ -function addShortcuts(state: RootState, ids: string[], afterId: string) { - const defaultShortcuts = shortcutsInitialState.shortcuts - - // 确保 state.shortcuts 存在 - if (!state.shortcuts) { - return - } - - // 从 defaultShortcuts 中找到要添加的快捷键 - const shortcutsToAdd = defaultShortcuts.filter((shortcut) => ids.includes(shortcut.key)) - - // 过滤掉已经存在的快捷键 - const existingKeys = state.shortcuts.shortcuts.map((s) => s.key) - const newShortcuts = shortcutsToAdd.filter((shortcut) => !existingKeys.includes(shortcut.key)) - - if (newShortcuts.length === 0) { - return - } - - if (afterId === 'first') { - // 添加到最前面 - state.shortcuts.shortcuts.unshift(...newShortcuts) - } else if (afterId === 'last') { - // 添加到最后面 - state.shortcuts.shortcuts.push(...newShortcuts) - } else { - // 添加到指定快捷键后面 - const afterIndex = state.shortcuts.shortcuts.findIndex((shortcut) => shortcut.key === afterId) - if (afterIndex !== -1) { - state.shortcuts.shortcuts.splice(afterIndex + 1, 0, ...newShortcuts) - } else { - // 如果找不到指定的快捷键,则添加到最后 - state.shortcuts.shortcuts.push(...newShortcuts) - } - } -} - -const migrateConfig = { - '2': (state: RootState) => { - try { - addProvider(state, 'yi') - return state - } catch (error) { - return state - } - }, - '3': (state: RootState) => { - try { - addProvider(state, 'zhipu') - return state - } catch (error) { - return state - } - }, - '4': (state: RootState) => { - try { - addProvider(state, 'ollama') - return state - } catch (error) { - return state - } - }, - '5': (state: RootState) => { - try { - addProvider(state, 'moonshot') - return state - } catch (error) { - return state - } - }, - '6': (state: RootState) => { - try { - addProvider(state, 'openrouter') - return state - } catch (error) { - return state - } - }, - '7': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - language: navigator.language - } - } - } catch (error) { - return state - } - }, - '8': (state: RootState) => { - try { - const fixAssistantName = (assistant: Assistant) => { - // 2025/07/25 这俩键早没了,从远古版本迁移包出错的 - if (isEmpty(assistant.name)) { - assistant.name = i18n.t('chat.default.name') - } - - assistant.topics = assistant.topics.map((topic) => { - if (isEmpty(topic.name)) { - topic.name = i18n.t('chat.default.topic.name') - } - return topic - }) - - return assistant - } - - return { - ...state, - assistants: { - ...state.assistants, - defaultAssistant: fixAssistantName(state.assistants.defaultAssistant), - assistants: state.assistants.assistants.map((assistant) => fixAssistantName(assistant)) - } - } - } catch (error) { - return state - } - }, - '9': (state: RootState) => { - try { - return { - ...state, - llm: { - ...state.llm, - providers: state.llm.providers.map((provider) => { - if (provider.id === 'zhipu' && provider.models[0] && provider.models[0].id === 'llama3-70b-8192') { - provider.models = SYSTEM_MODELS.zhipu - } - return provider - }) - } - } - } catch (error) { - return state - } - }, - '10': (state: RootState) => { - try { - addProvider(state, 'baichuan') - return state - } catch (error) { - return state - } - }, - '11': (state: RootState) => { - try { - addProvider(state, 'dashscope') - addProvider(state, 'anthropic') - return state - } catch (error) { - return state - } - }, - '12': (state: RootState) => { - try { - addProvider(state, 'aihubmix') - return state - } catch (error) { - return state - } - }, - '13': (state: RootState) => { - try { - return { - ...state, - assistants: { - ...state.assistants, - defaultAssistant: { - ...state.assistants.defaultAssistant, - name: ['Default Assistant', '默认助手'].includes(state.assistants.defaultAssistant.name) - ? i18n.t('settings.assistant.label') - : state.assistants.defaultAssistant.name - } - } - } - } catch (error) { - return state - } - }, - '14': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - showAssistants: true, - proxyUrl: undefined - } - } - } catch (error) { - return state - } - }, - '15': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - userName: '', - showMessageDivider: true - } - } - } catch (error) { - return state - } - }, - '16': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - messageFont: 'system', - showInputEstimatedTokens: false - } - } - } catch (error) { - return state - } - }, - '17': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - theme: 'auto' - } - } - } catch (error) { - return state - } - }, - '19': (state: RootState) => { - try { - return { - ...state, - agents: { - agents: [] - }, - llm: { - ...state.llm, - settings: { - ollama: { - keepAliveTime: 5 - } - } - } - } - } catch (error) { - return state - } - }, - '20': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - fontSize: 14 - } - } - } catch (error) { - return state - } - }, - '21': (state: RootState) => { - try { - addProvider(state, 'gemini') - addProvider(state, 'stepfun') - addProvider(state, 'doubao') - return state - } catch (error) { - return state - } - }, - '22': (state: RootState) => { - try { - addProvider(state, 'minimax') - return state - } catch (error) { - return state - } - }, - '23': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - showTopics: true, - windowStyle: 'transparent' - } - } - } catch (error) { - return state - } - }, - '24': (state: RootState) => { - try { - return { - ...state, - assistants: { - ...state.assistants, - assistants: state.assistants.assistants.map((assistant) => ({ - ...assistant, - topics: assistant.topics.map((topic) => ({ - ...topic, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - })) - })) - }, - settings: { - ...state.settings, - topicPosition: 'right' - } - } - } catch (error) { - return state - } - }, - '25': (state: RootState) => { - try { - addProvider(state, 'github') - return state - } catch (error) { - return state - } - }, - '26': (state: RootState) => { - try { - addProvider(state, 'ocoolai') - return state - } catch (error) { - return state - } - }, - '27': (state: RootState) => { - try { - return { - ...state, - settings: { - ...state.settings, - renderInputMessageAsMarkdown: true - } - } - } catch (error) { - return state - } - }, - '28': (state: RootState) => { - try { - addProvider(state, 'together') - addProvider(state, 'fireworks') - addProvider(state, 'zhinao') - addProvider(state, 'hunyuan') - addProvider(state, 'nvidia') - return state - } catch (error) { - return state - } - }, - '29': (state: RootState) => { - try { - return { - ...state, - assistants: { - ...state.assistants, - assistants: state.assistants.assistants.map((assistant) => { - assistant.topics = assistant.topics.map((topic) => ({ - ...topic, - assistantId: assistant.id - })) - return assistant - }) - } - } - } catch (error) { - return state - } - }, - '30': (state: RootState) => { - try { - addProvider(state, 'azure-openai') - return state - } catch (error) { - return state - } - }, - '31': (state: RootState) => { - try { - return { - ...state, - llm: { - ...state.llm, - providers: state.llm.providers.map((provider) => { - if (provider.id === 'azure-openai') { - provider.models = provider.models.map((model) => ({ ...model, provider: 'azure-openai' })) - } - return provider - }) - } - } - } catch (error) { - return state - } - }, - '32': (state: RootState) => { - try { - addProvider(state, 'hunyuan') - return state - } catch (error) { - return state - } - }, - '33': (state: RootState) => { - try { - state.assistants.defaultAssistant.type = 'assistant' - - state.agents.agents.forEach((agent) => { - agent.type = 'agent' - // @ts-ignore eslint-disable-next-line - delete agent.group - }) - - return { - ...state, - assistants: { - ...state.assistants, - assistants: [...state.assistants.assistants].map((assistant) => { - // @ts-ignore eslint-disable-next-line - delete assistant.group - return { - ...assistant, - id: assistant.id.length === 36 ? assistant.id : uuid(), - type: assistant.type === 'system' ? assistant.type : 'assistant' - } - }) - } - } - } catch (error) { - return state - } - }, - '34': (state: RootState) => { - try { - state.assistants.assistants.forEach((assistant) => { - assistant.topics.forEach((topic) => { - topic.assistantId = assistant.id - runAsyncFunction(async () => { - const _topic = await db.topics.get(topic.id) - if (_topic) { - const messages = (_topic?.messages || []).map((message) => ({ ...message, assistantId: assistant.id })) - db.topics.put({ ..._topic, messages }, topic.id) - } - }) - }) - }) - return state - } catch (error) { - return state - } - }, - '35': (state: RootState) => { - try { - state.settings.mathEngine = 'KaTeX' - return state - } catch (error) { - return state - } - }, - '36': (state: RootState) => { - try { - state.settings.topicPosition = 'left' - return state - } catch (error) { - return state - } - }, - '37': (state: RootState) => { - try { - state.settings.messageStyle = 'plain' - return state - } catch (error) { - return state - } - }, - '38': (state: RootState) => { - try { - addProvider(state, 'grok') - addProvider(state, 'hyperbolic') - addProvider(state, 'mistral') - return state - } catch (error) { - return state - } - }, - '39': (state: RootState) => { - try { - // @ts-ignore eslint-disable-next-line - state.settings.codeStyle = 'auto' - return state - } catch (error) { - return state - } - }, - '40': (state: RootState) => { - try { - state.settings.tray = true - return state - } catch (error) { - return state - } - }, - '41': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - if (provider.id === 'gemini') { - provider.type = 'gemini' - } else if (provider.id === 'anthropic') { - provider.type = 'anthropic' - } else { - provider.type = 'openai' - } - }) - return state - } catch (error) { - return state - } - }, - '42': (state: RootState) => { - try { - state.settings.proxyMode = state.settings.proxyUrl ? 'custom' : 'none' - return state - } catch (error) { - return state - } - }, - '43': (state: RootState) => { - try { - if (state.settings.proxyMode === 'none') { - state.settings.proxyMode = 'system' - } - return state - } catch (error) { - return state - } - }, - '44': (state: RootState) => { - try { - state.settings.translateModelPrompt = TRANSLATE_PROMPT - return state - } catch (error) { - return state - } - }, - '45': (state: RootState) => { - state.settings.enableTopicNaming = true - return state - }, - '46': (state: RootState) => { - try { - if ( - state.settings?.translateModelPrompt?.includes( - 'If the target language is the same as the source language, do not translate' - ) - ) { - state.settings.translateModelPrompt = TRANSLATE_PROMPT - } - return state - } catch (error) { - return state - } - }, - '47': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - provider.models.forEach((model) => { - model.group = getDefaultGroupName(model.id) - }) - }) - return state - } catch (error) { - return state - } - }, - '48': (state: RootState) => { - try { - if (state.shortcuts) { - state.shortcuts.shortcuts.forEach((shortcut) => { - shortcut.system = shortcut.key !== 'new_topic' - }) - state.shortcuts.shortcuts.push({ - key: 'toggle_show_assistants', - shortcut: [isMac ? 'Command' : 'Ctrl', '['], - editable: true, - enabled: true, - system: false - }) - state.shortcuts.shortcuts.push({ - key: 'toggle_show_topics', - shortcut: [isMac ? 'Command' : 'Ctrl', ']'], - editable: true, - enabled: true, - system: false - }) - } - return state - } catch (error) { - return state - } - }, - '49': (state: RootState) => { - try { - state.settings.pasteLongTextThreshold = 1500 - if (state.shortcuts) { - state.shortcuts.shortcuts = [ - ...state.shortcuts.shortcuts, - { - key: 'copy_last_message', - shortcut: [isMac ? 'Command' : 'Ctrl', 'Shift', 'C'], - editable: true, - enabled: false, - system: false - } - ] - } - return state - } catch (error) { - return state - } - }, - '50': (state: RootState) => { - try { - addProvider(state, 'jina') - return state - } catch (error) { - return state - } - }, - '51': (state: RootState) => { - state.settings.topicNamingPrompt = '' - return state - }, - '54': (state: RootState) => { - try { - if (state.shortcuts) { - state.shortcuts.shortcuts.push({ - key: 'search_message', - shortcut: [isMac ? 'Command' : 'Ctrl', 'F'], - editable: true, - enabled: true, - system: false - }) - } - state.settings.sidebarIcons = { - visible: DefaultPreferences.default['ui.sidebar.icons.visible'], - disabled: [] - } - return state - } catch (error) { - return state - } - }, - '55': (state: RootState) => { - try { - if (!state.settings.sidebarIcons) { - state.settings.sidebarIcons = { - visible: DefaultPreferences.default['ui.sidebar.icons.visible'], - disabled: [] - } - } - return state - } catch (error) { - return state - } - }, - '57': (state: RootState) => { - try { - if (state.shortcuts) { - state.shortcuts.shortcuts.push({ - key: 'mini_window', - shortcut: [isMac ? 'Command' : 'Ctrl', 'E'], - editable: true, - enabled: false, - system: true - }) - } - - state.llm.providers.forEach((provider) => { - if (provider.id === 'qwenlm') { - provider.type = 'qwenlm' - } - }) - - state.settings.enableQuickAssistant = false - state.settings.clickTrayToShowQuickAssistant = true - - return state - } catch (error) { - return state - } - }, - '58': (state: RootState) => { - try { - if (state.shortcuts) { - state.shortcuts.shortcuts.push( - { - key: 'clear_topic', - shortcut: [isMac ? 'Command' : 'Ctrl', 'L'], - editable: true, - enabled: true, - system: false - }, - { - key: 'toggle_new_context', - shortcut: [isMac ? 'Command' : 'Ctrl', 'R'], - editable: true, - enabled: true, - system: false - } - ) - } - return state - } catch (error) { - return state - } - }, - '59': (state: RootState) => { - try { - addMiniApp(state, 'flowith') - return state - } catch (error) { - return state - } - }, - '60': (state: RootState) => { - try { - state.settings.multiModelMessageStyle = 'fold' - return state - } catch (error) { - return state - } - }, - '61': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - if (provider.id === 'qwenlm') { - provider.type = 'qwenlm' - } - }) - return state - } catch (error) { - return state - } - }, - '62': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - if (provider.id === 'azure-openai') { - provider.type = 'azure-openai' - } - }) - state.settings.translateModelPrompt = TRANSLATE_PROMPT - return state - } catch (error) { - return state - } - }, - '63': (state: RootState) => { - try { - addMiniApp(state, '3mintop') - return state - } catch (error) { - return state - } - }, - '64': (state: RootState) => { - try { - state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'qwenlm') - addProvider(state, 'baidu-cloud') - return state - } catch (error) { - return state - } - }, - '65': (state: RootState) => { - try { - // @ts-ignore expect error - state.settings.targetLanguage = 'english' - return state - } catch (error) { - return state - } - }, - '66': (state: RootState) => { - try { - addProvider(state, 'gitee-ai') - addProvider(state, 'ppio') - addMiniApp(state, 'aistudio') - state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'graphrag-kylin-mountain') - - return state - } catch (error) { - return state - } - }, - '67': (state: RootState) => { - try { - addMiniApp(state, 'xiaoyi') - addProvider(state, 'modelscope') - addProvider(state, 'lmstudio') - addProvider(state, 'perplexity') - addProvider(state, 'infini') - addProvider(state, 'dmxapi') - - state.llm.settings.lmstudio = { - keepAliveTime: 5 - } - - return state - } catch (error) { - return state - } - }, - '68': (state: RootState) => { - try { - addMiniApp(state, 'notebooklm') - addProvider(state, 'modelscope') - addProvider(state, 'lmstudio') - return state - } catch (error) { - return state - } - }, - '69': (state: RootState) => { - try { - addMiniApp(state, 'coze') - state.settings.gridColumns = 2 - state.settings.gridPopoverTrigger = 'hover' - return state - } catch (error) { - return state - } - }, - '70': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - if (provider.id === 'dmxapi') { - provider.apiHost = 'https://www.dmxapi.cn' - } - }) - return state - } catch (error) { - return state - } - }, - '71': (state: RootState) => { - try { - const appIds = ['dify', 'wpslingxi', 'lechat', 'abacus', 'lambdachat', 'baidu-ai-search'] - - if (state.minapps) { - appIds.forEach((id) => { - const app = DEFAULT_MIN_APPS.find((app) => app.id === id) - if (app) { - state.minapps.enabled.push(app) - } - }) - // remove zhihu-zhiada - state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== 'zhihu-zhiada') - state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== 'zhihu-zhiada') - } - - state.settings.thoughtAutoCollapse = true - - return state - } catch (error) { - return state - } - }, - '72': (state: RootState) => { - try { - addMiniApp(state, 'monica') - - // remove duplicate lmstudio providers - const emptyLmStudioProviderIndex = state.llm.providers.findLastIndex( - (provider) => provider.id === 'lmstudio' && provider.models.length === 0 - ) - - if (emptyLmStudioProviderIndex !== -1) { - state.llm.providers.splice(emptyLmStudioProviderIndex, 1) - } - - return state - } catch (error) { - return state - } - }, - '73': (state: RootState) => { - try { - if (state.websearch) { - state.websearch.searchWithTime = true - state.websearch.maxResults = 5 - state.websearch.excludeDomains = [] - } - - addProvider(state, 'lmstudio') - addProvider(state, 'o3') - state.llm.providers = moveProvider(state.llm.providers, 'o3', 2) - - state.assistants.assistants.forEach((assistant) => { - const leadingEmoji = getLeadingEmoji(assistant.name) - if (leadingEmoji) { - assistant.emoji = leadingEmoji - assistant.name = assistant.name.replace(leadingEmoji, '').trim() - } - }) - - state.agents.agents.forEach((agent) => { - const leadingEmoji = getLeadingEmoji(agent.name) - if (leadingEmoji) { - agent.emoji = leadingEmoji - agent.name = agent.name.replace(leadingEmoji, '').trim() - } - }) - - const defaultAssistantEmoji = getLeadingEmoji(state.assistants.defaultAssistant.name) - - if (defaultAssistantEmoji) { - state.assistants.defaultAssistant.emoji = defaultAssistantEmoji - state.assistants.defaultAssistant.name = state.assistants.defaultAssistant.name - .replace(defaultAssistantEmoji, '') - .trim() - } - - return state - } catch (error) { - return state - } - }, - '74': (state: RootState) => { - try { - addProvider(state, 'xirang') - return state - } catch (error) { - return state - } - }, - '75': (state: RootState) => { - try { - addMiniApp(state, 'you') - addMiniApp(state, 'cici') - addMiniApp(state, 'zhihu') - return state - } catch (error) { - return state - } - }, - '76': (state: RootState) => { - try { - addProvider(state, 'tencent-cloud-ti') - return state - } catch (error) { - return state - } - }, - '77': (state: RootState) => { - try { - addWebSearchProvider(state, 'searxng') - addWebSearchProvider(state, 'exa') - if (state.websearch) { - state.websearch.providers.forEach((p) => { - // @ts-ignore eslint-disable-next-line - delete p.enabled - }) - } - return state - } catch (error) { - return state - } - }, - '78': (state: RootState) => { - try { - state.llm.providers = moveProvider(state.llm.providers, 'ppio', 9) - state.llm.providers = moveProvider(state.llm.providers, 'infini', 10) - removeMiniAppIconsFromState(state) - return state - } catch (error) { - return state - } - }, - '79': (state: RootState) => { - try { - addProvider(state, 'gpustack') - return state - } catch (error) { - return state - } - }, - '80': (state: RootState) => { - try { - addProvider(state, 'alayanew') - state.llm.providers = moveProvider(state.llm.providers, 'alayanew', 10) - return state - } catch (error) { - return state - } - }, - '81': (state: RootState) => { - try { - addProvider(state, 'copilot') - return state - } catch (error) { - return state - } - }, - '82': (state: RootState) => { - try { - const runtimeState = state.runtime as any - if (runtimeState?.webdavSync) { - state.backup = state.backup || {} - state.backup = { - ...state.backup, - webdavSync: { - lastSyncTime: runtimeState.webdavSync.lastSyncTime || null, - syncing: runtimeState.webdavSync.syncing || false, - lastSyncError: runtimeState.webdavSync.lastSyncError || null - } - } - delete runtimeState.webdavSync - } - return state - } catch (error) { - return state - } - }, - '83': (state: RootState) => { - try { - state.settings.messageNavigation = 'buttons' - state.settings.launchOnBoot = false - state.settings.launchToTray = false - state.settings.trayOnClose = true - return state - } catch (error) { - logger.error('migrate 83 error', error as Error) - return state - } - }, - '84': (state: RootState) => { - try { - addProvider(state, 'voyageai') - return state - } catch (error) { - logger.error('migrate 84 error', error as Error) - return state - } - }, - '85': (state: RootState) => { - try { - // @ts-ignore eslint-disable-next-line - state.settings.autoCheckUpdate = !state.settings.manualUpdateCheck - // @ts-ignore eslint-disable-next-line - delete state.settings.manualUpdateCheck - state.settings.gridPopoverTrigger = 'click' - return state - } catch (error) { - return state - } - }, - '86': (state: RootState) => { - try { - if (state?.mcp?.servers) { - state.mcp.servers = state.mcp.servers.map((server) => ({ - ...server, - id: nanoid() - })) - } - } catch (error) { - return state - } - return state - }, - '87': (state: RootState) => { - try { - state.settings.maxKeepAliveMinapps = 3 - state.settings.showOpenedMinappsInSidebar = true - return state - } catch (error) { - return state - } - }, - '88': (state: RootState) => { - try { - if (state?.mcp?.servers) { - const hasAutoInstall = state.mcp.servers.some((server) => server.name === '@cherry/mcp-auto-install') - if (!hasAutoInstall) { - const defaultServer = mcpSlice.getInitialState().servers[0] - state.mcp.servers = [{ ...defaultServer, id: nanoid() }, ...state.mcp.servers] - } - } - return state - } catch (error) { - return state - } - }, - '89': (state: RootState) => { - try { - removeMiniAppFromState(state, 'aistudio') - return state - } catch (error) { - return state - } - }, - '90': (state: RootState) => { - try { - state.settings.enableDataCollection = true - return state - } catch (error) { - return state - } - }, - '91': (state: RootState) => { - try { - // @ts-ignore eslint-disable-next-line - state.settings.codeCacheable = false - // @ts-ignore eslint-disable-next-line - state.settings.codeCacheMaxSize = 1000 - // @ts-ignore eslint-disable-next-line - state.settings.codeCacheTTL = 15 - // @ts-ignore eslint-disable-next-line - state.settings.codeCacheThreshold = 2 - addProvider(state, 'qiniu') - return state - } catch (error) { - return state - } - }, - '92': (state: RootState) => { - try { - addMiniApp(state, 'dangbei') - state.llm.providers = moveProvider(state.llm.providers, 'qiniu', 12) - return state - } catch (error) { - return state - } - }, - '93': (state: RootState) => { - try { - if (!state?.settings?.exportMenuOptions) { - state.settings.exportMenuOptions = settingsInitialState.exportMenuOptions - return state - } - return state - } catch (error) { - return state - } - }, - '94': (state: RootState) => { - try { - state.settings.enableQuickPanelTriggers = false - return state - } catch (error) { - return state - } - }, - '95': (state: RootState) => { - try { - addWebSearchProvider(state, 'local-google') - addWebSearchProvider(state, 'local-bing') - addWebSearchProvider(state, 'local-baidu') - - if (state.websearch) { - if (isEmpty(state.websearch.subscribeSources)) { - state.websearch.subscribeSources = [] - } - } - - const qiniuProvider = state.llm.providers.find((provider) => provider.id === 'qiniu') - if (qiniuProvider && isEmpty(qiniuProvider.models)) { - qiniuProvider.models = SYSTEM_MODELS.qiniu - } - return state - } catch (error) { - return state - } - }, - '96': (state: RootState) => { - try { - // @ts-ignore eslint-disable-next-line - state.settings.assistantIconType = state.settings?.showAssistantIcon ? 'model' : 'emoji' - // @ts-ignore eslint-disable-next-line - delete state.settings.showAssistantIcon - return state - } catch (error) { - return state - } - }, - '97': (state: RootState) => { - try { - addMiniApp(state, 'zai') - state.settings.webdavMaxBackups = 0 - if (state.websearch && state.websearch.providers) { - state.websearch.providers.forEach((provider) => { - provider.basicAuthUsername = '' - provider.basicAuthPassword = '' - }) - } - return state - } catch (error) { - return state - } - }, - '98': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - if (provider.type === 'openai' && provider.id !== 'openai') { - // @ts-ignore eslint-disable-next-line - provider.type = 'openai-compatible' - } - }) - return state - } catch (error) { - return state - } - }, - '99': (state: RootState) => { - try { - state.settings.showPrompt = true - - addWebSearchProvider(state, 'bocha') - - updateWebSearchProvider(state, { - id: 'exa', - apiHost: 'https://api.exa.ai' - }) - - updateWebSearchProvider(state, { - id: 'tavily', - apiHost: 'https://api.tavily.com' - }) - - // Remove basic auth fields from exa and tavily - if (state.websearch?.providers) { - state.websearch.providers = state.websearch.providers.map((provider) => { - if (provider.id === 'exa' || provider.id === 'tavily') { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { basicAuthUsername, basicAuthPassword, ...rest } = provider - return rest - } - return provider - }) - } - return state - } catch (error) { - return state - } - }, - '100': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - // @ts-ignore eslint-disable-next-line - if (['openai-compatible', 'openai'].includes(provider.type)) { - provider.type = 'openai' - } - if (provider.id === 'openai') { - provider.type = 'openai-response' - } - }) - state.assistants.assistants.forEach((assistant) => { - assistant.knowledgeRecognition = 'off' - }) - return state - } catch (error) { - logger.error('migrate 100 error', error as Error) - return state - } - }, - '101': (state: RootState) => { - try { - state.assistants.assistants.forEach((assistant) => { - if (assistant.settings) { - // @ts-ignore eslint-disable-next-line - if (assistant.settings.enableToolUse) { - // @ts-ignore eslint-disable-next-line - assistant.settings.toolUseMode = assistant.settings.enableToolUse ? 'function' : 'prompt' - // @ts-ignore eslint-disable-next-line - delete assistant.settings.enableToolUse - } - } - }) - if (state.shortcuts) { - state.shortcuts.shortcuts.push({ - key: 'exit_fullscreen', - shortcut: ['Escape'], - editable: false, - enabled: true, - system: true - }) - } - return state - } catch (error) { - logger.error('migrate 101 error', error as Error) - return state - } - }, - '102': (state: RootState) => { - try { - state.settings.openAI = { - summaryText: 'off', - serviceTier: 'auto', - verbosity: 'medium' - } - - state.settings.codeExecution = { - enabled: false, - timeoutMinutes: 1 - } - state.settings.codeEditor = { - enabled: false, - themeLight: 'auto', - themeDark: 'auto', - highlightActiveLine: false, - foldGutter: false, - autocompletion: true, - keymap: false - } - // @ts-ignore eslint-disable-next-line - state.settings.codePreview = { - themeLight: 'auto', - themeDark: 'auto' - } - - // @ts-ignore eslint-disable-next-line - if (state.settings.codeStyle) { - // @ts-ignore eslint-disable-next-line - state.settings.codePreview.themeLight = state.settings.codeStyle - // @ts-ignore eslint-disable-next-line - state.settings.codePreview.themeDark = state.settings.codeStyle - } - - // @ts-ignore eslint-disable-next-line - delete state.settings.codeStyle - // @ts-ignore eslint-disable-next-line - delete state.settings.codeCacheable - // @ts-ignore eslint-disable-next-line - delete state.settings.codeCacheMaxSize - // @ts-ignore eslint-disable-next-line - delete state.settings.codeCacheTTL - // @ts-ignore eslint-disable-next-line - delete state.settings.codeCacheThreshold - return state - } catch (error) { - logger.error('migrate 102 error', error as Error) - return state - } - }, - '103': (state: RootState) => { - try { - if (state.shortcuts) { - if (!state.shortcuts.shortcuts.find((shortcut) => shortcut.key === 'search_message_in_chat')) { - state.shortcuts.shortcuts.push({ - key: 'search_message_in_chat', - shortcut: [isMac ? 'Command' : 'Ctrl', 'F'], - editable: true, - enabled: true, - system: false - }) - } - const searchMessageShortcut = state.shortcuts.shortcuts.find((shortcut) => shortcut.key === 'search_message') - const targetShortcut = [isMac ? 'Command' : 'Ctrl', 'F'] - if ( - searchMessageShortcut && - Array.isArray(searchMessageShortcut.shortcut) && - searchMessageShortcut.shortcut.length === targetShortcut.length && - searchMessageShortcut.shortcut.every((v, i) => v === targetShortcut[i]) - ) { - searchMessageShortcut.shortcut = [isMac ? 'Command' : 'Ctrl', 'Shift', 'F'] - } - } - return state - } catch (error) { - logger.error('migrate 103 error', error as Error) - return state - } - }, - '104': (state: RootState) => { - try { - addProvider(state, 'burncloud') - state.llm.providers = moveProvider(state.llm.providers, 'burncloud', 10) - return state - } catch (error) { - logger.error('migrate 104 error', error as Error) - return state - } - }, - '105': (state: RootState) => { - try { - state.settings.notification = settingsInitialState.notification - addMiniApp(state, 'google') - if (!state.settings.openAI) { - state.settings.openAI = { - summaryText: 'off', - serviceTier: 'auto', - verbosity: 'medium' - } - } - return state - } catch (error) { - logger.error('migrate 105 error', error as Error) - return state - } - }, - '106': (state: RootState) => { - try { - addProvider(state, 'tokenflux') - state.llm.providers = moveProvider(state.llm.providers, 'tokenflux', 15) - return state - } catch (error) { - logger.error('migrate 106 error', error as Error) - return state - } - }, - '107': (state: RootState) => { - try { - if (state.paintings && !state.paintings.dmxapi_paintings) { - state.paintings.dmxapi_paintings = [] - } - return state - } catch (error) { - logger.error('migrate 107 error', error as Error) - return state - } - }, - '108': (state: RootState) => { - try { - state.inputTools.toolOrder = DEFAULT_TOOL_ORDER - state.inputTools.isCollapsed = false - return state - } catch (error) { - logger.error('migrate 108 error', error as Error) - return state - } - }, - '109': (state: RootState) => { - try { - state.settings.userTheme = settingsInitialState.userTheme - return state - } catch (error) { - logger.error('migrate 109 error', error as Error) - return state - } - }, - '110': (state: RootState) => { - try { - if (state.paintings && !state.paintings.tokenflux_paintings) { - state.paintings.tokenflux_paintings = [] - } - state.settings.testPlan = false - return state - } catch (error) { - logger.error('migrate 110 error', error as Error) - return state - } - }, - '111': (state: RootState) => { - try { - addSelectionAction(state, 'quote') - if ( - state.llm.translateModel.provider === 'silicon' && - state.llm.translateModel.id === 'meta-llama/Llama-3.3-70B-Instruct' - ) { - state.llm.translateModel = SYSTEM_MODELS.defaultModel[2] - } - - // add selection_assistant_toggle and selection_assistant_select_text shortcuts after mini_window - addShortcuts(state, ['selection_assistant_toggle', 'selection_assistant_select_text'], 'mini_window') - - return state - } catch (error) { - logger.error('migrate 111 error', error as Error) - return state - } - }, - '112': (state: RootState) => { - try { - addProvider(state, 'cephalon') - addProvider(state, '302ai') - addProvider(state, 'lanyun') - state.llm.providers = moveProvider(state.llm.providers, 'cephalon', 13) - state.llm.providers = moveProvider(state.llm.providers, '302ai', 14) - state.llm.providers = moveProvider(state.llm.providers, 'lanyun', 15) - return state - } catch (error) { - logger.error('migrate 112 error', error as Error) - return state - } - }, - '113': (state: RootState) => { - try { - addProvider(state, 'vertexai') - if (!state.llm.settings.vertexai) { - state.llm.settings.vertexai = llmInitialState.settings.vertexai - } - updateProvider(state, 'gemini', { - isVertex: false - }) - updateProvider(state, 'vertexai', { - isVertex: true - }) - return state - } catch (error) { - logger.error('migrate 113 error', error as Error) - return state - } - }, - '114': (state: RootState) => { - try { - if (state.settings && state.settings.exportMenuOptions) { - if (typeof state.settings.exportMenuOptions.plain_text === 'undefined') { - state.settings.exportMenuOptions.plain_text = true - } - } - if (state.settings) { - state.settings.enableSpellCheck = false - state.settings.spellCheckLanguages = [] - } - return state - } catch (error) { - logger.error('migrate 114 error', error as Error) - return state - } - }, - '115': (state: RootState) => { - try { - state.assistants.assistants.forEach((assistant) => { - if (!assistant.settings) { - assistant.settings = { - temperature: DEFAULT_TEMPERATURE, - contextCount: DEFAULT_CONTEXTCOUNT, - topP: 1, - toolUseMode: 'prompt', - customParameters: [], - streamOutput: true, - enableMaxTokens: false - } - } - }) - return state - } catch (error) { - logger.error('migrate 115 error', error as Error) - return state - } - }, - '116': (state: RootState) => { - try { - if (state.websearch) { - // migrate contentLimit to cutoffLimit - // @ts-ignore eslint-disable-next-line - if (state.websearch.contentLimit) { - state.websearch.compressionConfig = { - method: 'cutoff', - cutoffUnit: 'char', - // @ts-ignore eslint-disable-next-line - cutoffLimit: state.websearch.contentLimit - } - } else { - state.websearch.compressionConfig = { method: 'none', cutoffUnit: 'char' } - } - - // @ts-ignore eslint-disable-next-line - delete state.websearch.contentLimit - } - if (state.settings) { - state.settings.testChannel = UpgradeChannel.LATEST - } - - return state - } catch (error) { - logger.error('migrate 116 error', error as Error) - return state - } - }, - '117': (state: RootState) => { - try { - const ppioProvider = state.llm.providers.find((provider) => provider.id === 'ppio') - const modelsToRemove = [ - 'qwen/qwen-2.5-72b-instruct', - 'qwen/qwen2.5-32b-instruct', - 'meta-llama/llama-3.1-70b-instruct', - 'meta-llama/llama-3.1-8b-instruct', - '01-ai/yi-1.5-34b-chat', - '01-ai/yi-1.5-9b-chat', - 'thudm/glm-z1-32b-0414', - 'thudm/glm-z1-9b-0414' - ] - if (ppioProvider) { - updateProvider(state, 'ppio', { - models: [ - ...ppioProvider.models.filter((model) => !modelsToRemove.includes(model.id)), - ...SYSTEM_MODELS.ppio.filter( - (systemModel) => !ppioProvider.models.some((existingModel) => existingModel.id === systemModel.id) - ) - ], - apiHost: 'https://api.ppinfra.com/v3/openai/' - }) - } - state.assistants.assistants.forEach((assistant) => { - if (assistant.settings && assistant.settings.streamOutput === undefined) { - assistant.settings = { - ...assistant.settings, - streamOutput: true - } - } - }) - return state - } catch (error) { - logger.error('migrate 117 error', error as Error) - return state - } - }, - '118': (state: RootState) => { - try { - addProvider(state, 'ph8') - state.llm.providers = moveProvider(state.llm.providers, 'ph8', 14) - - if (!state.settings.userId) { - state.settings.userId = uuid() - } - - state.llm.providers.forEach((provider) => { - if (provider.id === 'mistral') { - provider.type = 'mistral' - } - }) - - return state - } catch (error) { - logger.error('migrate 118 error', error as Error) - return state - } - }, - '119': (state: RootState) => { - try { - addProvider(state, 'new-api') - state.llm.providers = moveProvider(state.llm.providers, 'new-api', 16) - state.settings.disableHardwareAcceleration = false - // migrate to enable memory feature on sidebar - if (state.settings && state.settings.sidebarIcons) { - // Check if 'memory' is not already in visible icons - if (!state.settings.sidebarIcons.visible.includes('memory' as any)) { - state.settings.sidebarIcons.visible = [...state.settings.sidebarIcons.visible, 'memory' as any] - } - } - return state - } catch (error) { - logger.error('migrate 119 error', error as Error) - return state - } - }, - '120': (state: RootState) => { - try { - // migrate to remove memory feature from sidebar (moved to settings) - if (state.settings && state.settings.sidebarIcons) { - // Remove 'memory' from visible icons if present - state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.filter( - (icon) => icon !== ('memory' as any) - ) - // Remove 'memory' from disabled icons if present - state.settings.sidebarIcons.disabled = state.settings.sidebarIcons.disabled.filter( - (icon) => icon !== ('memory' as any) - ) - } - - if (!state.settings.s3) { - state.settings.s3 = settingsInitialState.s3 - } - - const langMap: Record = { - english: 'en-us', - chinese: 'zh-cn', - 'chinese-traditional': 'zh-tw', - japanese: 'ja-jp', - russian: 'ru-ru' - } - - const origin = state.settings.targetLanguage - const newLang = langMap[origin] - if (newLang) state.settings.targetLanguage = newLang - else state.settings.targetLanguage = 'en-us' - - state.llm.providers.forEach((provider) => { - if (provider.id === 'azure-openai') { - provider.type = 'azure-openai' - } - }) - - state.settings.localBackupMaxBackups = 0 - state.settings.localBackupSkipBackupFile = false - state.settings.localBackupDir = '' - state.settings.localBackupAutoSync = false - state.settings.localBackupSyncInterval = 0 - return state - } catch (error) { - logger.error('migrate 120 error', error as Error) - return state - } - }, - '121': (state: RootState) => { - try { - const { toolOrder } = state.inputTools - const urlContextKey = 'url_context' - if (!toolOrder.visible.includes(urlContextKey)) { - const webSearchIndex = toolOrder.visible.indexOf('web_search') - const knowledgeBaseIndex = toolOrder.visible.indexOf('knowledge_base') - if (webSearchIndex !== -1) { - toolOrder.visible.splice(webSearchIndex, 0, urlContextKey) - } else if (knowledgeBaseIndex !== -1) { - toolOrder.visible.splice(knowledgeBaseIndex, 0, urlContextKey) - } else { - toolOrder.visible.push(urlContextKey) - } - } - - for (const assistant of state.assistants.assistants) { - if (assistant.settings?.toolUseMode === 'prompt' && isFunctionCallingModel(assistant.model)) { - assistant.settings.toolUseMode = 'function' - } - } - - if (state.settings && typeof state.settings.webdavDisableStream === 'undefined') { - state.settings.webdavDisableStream = false - } - - return state - } catch (error) { - logger.error('migrate 121 error', error as Error) - return state - } - }, - '122': (state: RootState) => { - try { - state.settings.navbarPosition = 'left' - return state - } catch (error) { - logger.error('migrate 122 error', error as Error) - return state - } - }, - '123': (state: RootState) => { - try { - state.llm.providers.forEach((provider) => { - provider.models.forEach((model) => { - if (model.type && Array.isArray(model.type)) { - model.capabilities = model.type.map((t) => ({ - type: t, - isUserSelected: true - })) - delete model.type - } - }) - }) - - const lanyunProvider = state.llm.providers.find((provider) => provider.id === 'lanyun') - if (lanyunProvider && lanyunProvider.models.length === 0) { - updateProvider(state, 'lanyun', { models: SYSTEM_MODELS.lanyun }) - } - - return state - } catch (error) { - logger.error('migrate 123 error', error as Error) - return state - } - }, // 1.5.4 - '124': (state: RootState) => { - try { - state.assistants.assistants.forEach((assistant) => { - if (assistant.settings && !assistant.settings.toolUseMode) { - assistant.settings.toolUseMode = 'prompt' - } - }) - - const updateModelTextDelta = (model?: Model) => { - if (model) { - model.supported_text_delta = true - if (isNotSupportedTextDelta(model)) { - model.supported_text_delta = false - } - } - } - - state.llm.providers.forEach((provider) => { - provider.models.forEach((model) => { - updateModelTextDelta(model) - }) - }) - state.assistants.assistants.forEach((assistant) => { - updateModelTextDelta(assistant.defaultModel) - updateModelTextDelta(assistant.model) - }) - - updateModelTextDelta(state.llm.defaultModel) - updateModelTextDelta(state.llm.topicNamingModel) - updateModelTextDelta(state.llm.translateModel) - - if (state.assistants.defaultAssistant.model) { - updateModelTextDelta(state.assistants.defaultAssistant.model) - updateModelTextDelta(state.assistants.defaultAssistant.defaultModel) - } - - addProvider(state, 'aws-bedrock') - - // 初始化 awsBedrock 设置 - if (!state.llm.settings.awsBedrock) { - state.llm.settings.awsBedrock = llmInitialState.settings.awsBedrock - } - - return state - } catch (error) { - logger.error('migrate 124 error', error as Error) - return state - } - }, - '125': (state: RootState) => { - try { - // Initialize API server configuration if not present - if (!state.settings.apiServer) { - state.settings.apiServer = { - enabled: false, - host: 'localhost', - port: 23333, - apiKey: `cs-sk-${uuid()}` - } - } - return state - } catch (error) { - logger.error('migrate 125 error', error as Error) - return state - } - }, - '126': (state: RootState) => { - try { - state.knowledge.bases.forEach((base) => { - // @ts-ignore eslint-disable-next-line - if (base.preprocessOrOcrProvider) { - // @ts-ignore eslint-disable-next-line - base.preprocessProvider = base.preprocessOrOcrProvider - // @ts-ignore eslint-disable-next-line - delete base.preprocessOrOcrProvider - // @ts-ignore eslint-disable-next-line - if (base.preprocessProvider.type === 'ocr') { - // @ts-ignore eslint-disable-next-line - delete base.preprocessProvider - } - } - }) - return state - } catch (error) { - logger.error('migrate 126 error', error as Error) - return state - } - }, - '127': (state: RootState) => { - try { - addProvider(state, 'poe') - - // 迁移api选项设置 - state.llm.providers.forEach((provider) => { - // 新字段默认支持 - const changes = { - isNotSupportArrayContent: false, - isNotSupportDeveloperRole: false, - isNotSupportStreamOptions: false - } - if (!isSupportArrayContentProvider(provider) || provider.isNotSupportArrayContent) { - // 原本开启了兼容模式的provider不受影响 - changes.isNotSupportArrayContent = true - } - if (!isSupportDeveloperRoleProvider(provider)) { - changes.isNotSupportDeveloperRole = true - } - if (!isSupportStreamOptionsProvider(provider)) { - changes.isNotSupportStreamOptions = true - } - updateProvider(state, provider.id, changes) - }) - - // 迁移以前删除掉的内置提供商 - for (const provider of state.llm.providers) { - if (provider.isSystem && !isSystemProvider(provider)) { - updateProvider(state, provider.id, { isSystem: false }) - } - } - - if (!state.settings.proxyBypassRules) { - state.settings.proxyBypassRules = defaultByPassRules - } - return state - } catch (error) { - logger.error('migrate 127 error', error as Error) - return state - } - }, - '128': (state: RootState) => { - try { - // 迁移 service tier 设置 - const openai = state.llm.providers.find((provider) => provider.id === SystemProviderIds.openai) - const serviceTier = state.settings.openAI.serviceTier - if (openai) { - openai.serviceTier = serviceTier - } - - // @ts-ignore eslint-disable-next-line - if (state.settings.codePreview) { - // @ts-ignore eslint-disable-next-line - state.settings.codeViewer = state.settings.codePreview - } else { - state.settings.codeViewer = { - themeLight: 'auto', - themeDark: 'auto' - } - } - - return state - } catch (error) { - logger.error('migrate 128 error', error as Error) - return state - } - }, - '129': (state: RootState) => { - try { - // 聚合 api options - state.llm.providers.forEach((p) => { - if (isSystemProvider(p)) { - updateProvider(state, p.id, { apiOptions: undefined }) - } else { - const changes: ProviderApiOptions = { - isNotSupportArrayContent: p.isNotSupportArrayContent, - isNotSupportServiceTier: p.isNotSupportServiceTier, - isNotSupportDeveloperRole: p.isNotSupportDeveloperRole, - isNotSupportStreamOptions: p.isNotSupportStreamOptions - } - updateProvider(state, p.id, { apiOptions: changes }) - } - }) - return state - } catch (error) { - logger.error('migrate 129 error', error as Error) - return state - } - }, - '130': (state: RootState) => { - try { - if (state.settings && state.settings.openAI && !state.settings.openAI.verbosity) { - state.settings.openAI.verbosity = 'medium' - } - // 为 nutstore 添加备份数量限制的默认值 - if (state.nutstore && state.nutstore.nutstoreMaxBackups === undefined) { - state.nutstore.nutstoreMaxBackups = 0 - } - return state - } catch (error) { - logger.error('migrate 130 error', error as Error) - return state - } - }, - '131': (state: RootState) => { - try { - state.settings.mathEnableSingleDollar = true - return state - } catch (error) { - logger.error('migrate 131 error', error as Error) - return state - } - }, - '132': (state: RootState) => { - try { - state.llm.providers.forEach((p) => { - // 如果原本是undefined则不做改动,静默从默认支持改为默认不支持 - if (p.apiOptions?.isNotSupportDeveloperRole) { - p.apiOptions.isSupportDeveloperRole = !p.apiOptions.isNotSupportDeveloperRole - } - if (p.apiOptions?.isNotSupportServiceTier) { - p.apiOptions.isSupportServiceTier = !p.apiOptions.isNotSupportServiceTier - } - }) - return state - } catch (error) { - logger.error('migrate 132 error', error as Error) - return state - } - }, - '133': (state: RootState) => { - try { - state.settings.sidebarIcons.visible.push('code_tools') - if (state.codeTools) { - state.codeTools.environmentVariables = { - 'qwen-code': '', - 'claude-code': '', - 'gemini-cli': '' - } - } - return state - } catch (error) { - logger.error('migrate 133 error', error as Error) - return state - } - }, - '134': (state: RootState) => { - try { - state.llm.quickModel = state.llm.topicNamingModel - - return state - } catch (error) { - logger.error('migrate 134 error', error as Error) - return state - } - }, - '135': (state: RootState) => { - try { - if (!state.assistants.defaultAssistant.settings) { - state.assistants.defaultAssistant.settings = DEFAULT_ASSISTANT_SETTINGS - } else if (!state.assistants.defaultAssistant.settings.toolUseMode) { - state.assistants.defaultAssistant.settings.toolUseMode = 'prompt' - } - return state - } catch (error) { - logger.error('migrate 135 error', error as Error) - return state - } - }, - '136': (state: RootState) => { - try { - state.settings.sidebarIcons.visible = [...new Set(state.settings.sidebarIcons.visible)].filter((icon) => - DefaultPreferences.default['ui.sidebar.icons.visible'].includes(icon) - ) - state.settings.sidebarIcons.disabled = [...new Set(state.settings.sidebarIcons.disabled)].filter((icon) => - DefaultPreferences.default['ui.sidebar.icons.visible'].includes(icon) - ) - return state - } catch (error) { - logger.error('migrate 136 error', error as Error) - return state - } - }, - '137': (state: RootState) => { - try { - state.ocr = { - providers: BUILTIN_OCR_PROVIDERS, - imageProviderId: DEFAULT_OCR_PROVIDER.image.id - } - state.translate.translateInput = '' - return state - } catch (error) { - logger.error('migrate 137 error', error as Error) - return state - } - }, - '138': (state: RootState) => { - try { - addOcrProvider(state, BUILTIN_OCR_PROVIDERS_MAP.system) - return state - } catch (error) { - logger.error('migrate 138 error', error as Error) - return state - } - }, - '139': (state: RootState) => { - try { - addProvider(state, 'cherryin') - state.llm.providers = moveProvider(state.llm.providers, 'cherryin', 1) - - const zhipuProvider = state.llm.providers.find((p) => p.id === 'zhipu') - - if (zhipuProvider) { - // Update zhipu model list - if (!zhipuProvider.enabled) { - zhipuProvider.models = SYSTEM_MODELS.zhipu - } - - // Update zhipu model list - if (zhipuProvider.models.length === 0) { - zhipuProvider.models = SYSTEM_MODELS.zhipu - } - - // Add GLM-4.5-Flash model if not exists - const hasGlm45FlashModel = zhipuProvider?.models.find((m) => m.id === 'glm-4.5-flash') - - if (!hasGlm45FlashModel) { - zhipuProvider?.models.push(glm45FlashModel) - } - - // Update default painting provider to zhipu - state.settings.defaultPaintingProvider = 'zhipu' - - // Add zhipu web search provider - addWebSearchProvider(state, 'zhipu') - - // Update zhipu web search provider api key - if (zhipuProvider.apiKey) { - state?.websearch?.providers.forEach((provider) => { - if (provider.id === 'zhipu') { - provider.apiKey = zhipuProvider.apiKey - } - }) - } - } - - return state - } catch (error) { - logger.error('migrate 139 error', error as Error) - return state - } - }, - '140': (state: RootState) => { - try { - state.paintings = { - // @ts-ignore paintings - siliconflow_paintings: state?.paintings?.paintings || [], - // @ts-ignore DMXAPIPaintings - dmxapi_paintings: state?.paintings?.DMXAPIPaintings || [], - // @ts-ignore tokenFluxPaintings - tokenflux_paintings: state?.paintings?.tokenFluxPaintings || [], - zhipu_paintings: [], - // @ts-ignore generate - aihubmix_image_generate: state?.paintings?.generate || [], - // @ts-ignore remix - aihubmix_image_remix: state?.paintings?.remix || [], - // @ts-ignore edit - aihubmix_image_edit: state?.paintings?.edit || [], - // @ts-ignore upscale - aihubmix_image_upscale: state?.paintings?.upscale || [], - openai_image_generate: state?.paintings?.openai_image_generate || [], - openai_image_edit: state?.paintings?.openai_image_edit || [] - } - - return state - } catch (error) { - logger.error('migrate 140 error', error as Error) - return state - } - }, - '141': (state: RootState) => { - try { - if (state.settings && state.settings.sidebarIcons) { - // Check if 'notes' is not already in visible icons - if (!state.settings.sidebarIcons.visible.includes('notes')) { - state.settings.sidebarIcons.visible = [...state.settings.sidebarIcons.visible, 'notes'] - } - } - return state - } catch (error) { - logger.error('migrate 141 error', error as Error) - return state - } - }, - '142': (state: RootState) => { - try { - // Initialize notes settings if not present - if (!state.note) { - state.note = notesInitialState - } - return state - } catch (error) { - logger.error('migrate 142 error', error as Error) - return state - } - }, - '143': (state: RootState) => { - try { - addMiniApp(state, 'longcat') - return state - } catch (error) { - return state - } - }, - '144': (state: RootState) => { - try { - if (state.settings) { - state.settings.confirmDeleteMessage = settingsInitialState.confirmDeleteMessage - state.settings.confirmRegenerateMessage = settingsInitialState.confirmRegenerateMessage - } - return state - } catch (error) { - logger.error('migrate 144 error', error as Error) - return state - } - } -} - -// 注意:添加新迁移时,记得同时更新 persistReducer -// file://./index.ts - -const migrate = createMigrate(migrateConfig as any) - -export default migrate +// import { loggerService } from '@logger' +// import { nanoid } from '@reduxjs/toolkit' +// import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant' +// import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +// import { +// glm45FlashModel, +// isFunctionCallingModel, +// isNotSupportedTextDelta, +// SYSTEM_MODELS +// } from '@renderer/config/models' +// import { BUILTIN_OCR_PROVIDERS, BUILTIN_OCR_PROVIDERS_MAP, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr' +// import { +// isSupportArrayContentProvider, +// isSupportDeveloperRoleProvider, +// isSupportStreamOptionsProvider, +// SYSTEM_PROVIDERS +// } from '@renderer/config/providers' +// // import { DEFAULT_SIDEBAR_ICONS } from '@renderer/config/sidebar' +// import db from '@renderer/databases' +// import i18n from '@renderer/i18n' +// import { DEFAULT_ASSISTANT_SETTINGS } from '@renderer/services/AssistantService' +// import { +// Assistant, +// BuiltinOcrProvider, +// isSystemProvider, +// Model, +// Provider, +// ProviderApiOptions, +// SystemProviderIds, +// TranslateLanguageCode, +// WebSearchProvider +// } from '@renderer/types' +// import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils' +// import { defaultByPassRules } from '@shared/config/constant' +// import { TRANSLATE_PROMPT } from '@shared/config/prompts' +// import { DefaultPreferences } from '@shared/data/preferences' +// import { UpgradeChannel } from '@shared/data/preferenceTypes' +// import { isEmpty } from 'lodash' +// import { createMigrate } from 'redux-persist' + +// import { RootState } from '.' +// import { DEFAULT_TOOL_ORDER } from './inputTools' +// import { initialState as llmInitialState, moveProvider } from './llm' +// import { mcpSlice } from './mcp' +// import { initialState as notesInitialState } from './note' +// // import { defaultActionItems } from './selectionStore' +// import { initialState as settingsInitialState } from './settings' +// import { initialState as shortcutsInitialState } from './shortcuts' +// import { defaultWebSearchProviders } from './websearch' +// const logger = loggerService.withContext('Migrate') + +// // remove logo base64 data to reduce the size of the state +// function removeMiniAppIconsFromState(state: RootState) { +// if (state.minapps) { +// state.minapps.enabled = state.minapps.enabled.map((app) => ({ ...app, logo: undefined })) +// state.minapps.disabled = state.minapps.disabled.map((app) => ({ ...app, logo: undefined })) +// state.minapps.pinned = state.minapps.pinned.map((app) => ({ ...app, logo: undefined })) +// } +// } + +// function removeMiniAppFromState(state: RootState, id: string) { +// if (state.minapps) { +// state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== id) +// state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== id) +// } +// } + +// function addMiniApp(state: RootState, id: string) { +// if (state.minapps) { +// const app = DEFAULT_MIN_APPS.find((app) => app.id === id) +// if (app) { +// if (!state.minapps.enabled.find((app) => app.id === id)) { +// state.minapps.enabled.push(app) +// } +// } +// } +// } + +// // add provider to state +// function addProvider(state: RootState, id: string) { +// if (!state.llm.providers.find((p) => p.id === id)) { +// const _provider = SYSTEM_PROVIDERS.find((p) => p.id === id) +// if (_provider) { +// state.llm.providers.push(_provider) +// } +// } +// } + +// // add ocr provider +// function addOcrProvider(state: RootState, provider: BuiltinOcrProvider) { +// if (!state.ocr.providers.find((p) => p.id === provider.id)) { +// state.ocr.providers.push(provider) +// } +// } + +// function updateProvider(state: RootState, id: string, provider: Partial) { +// if (state.llm.providers) { +// const index = state.llm.providers.findIndex((p) => p.id === id) +// if (index !== -1) { +// state.llm.providers[index] = { ...state.llm.providers[index], ...provider } +// } +// } +// } + +// function addWebSearchProvider(state: RootState, id: string) { +// if (state.websearch && state.websearch.providers) { +// if (!state.websearch.providers.find((p) => p.id === id)) { +// const provider = defaultWebSearchProviders.find((p) => p.id === id) +// if (provider) { +// // Prevent mutating read only property of object +// // Otherwise, it will cause the error: Cannot assign to read only property 'apiKey' of object '#' +// state.websearch.providers.push({ ...provider }) +// } +// } +// } +// } + +// function updateWebSearchProvider(state: RootState, provider: Partial) { +// if (state.websearch && state.websearch.providers) { +// const index = state.websearch.providers.findIndex((p) => p.id === provider.id) +// if (index !== -1) { +// state.websearch.providers[index] = { +// ...state.websearch.providers[index], +// ...provider +// } +// } +// } +// } + +// function addSelectionAction(state: RootState, id: string) { +// // if (state.selectionStore && state.selectionStore.actionItems) { +// // if (!state.selectionStore.actionItems.some((item) => item.id === id)) { +// // const action = defaultActionItems.find((item) => item.id === id) +// // if (action) { +// // state.selectionStore.actionItems.push(action) +// // } +// // } +// // } +// return [state, id] +// } + +// /** +// * Add shortcuts(ids from shortcutsInitialState) after the shortcut(afterId) +// * if afterId is 'first', add to the first +// * if afterId is 'last', add to the last +// */ +// function addShortcuts(state: RootState, ids: string[], afterId: string) { +// const defaultShortcuts = shortcutsInitialState.shortcuts + +// // 确保 state.shortcuts 存在 +// if (!state.shortcuts) { +// return +// } + +// // 从 defaultShortcuts 中找到要添加的快捷键 +// const shortcutsToAdd = defaultShortcuts.filter((shortcut) => ids.includes(shortcut.key)) + +// // 过滤掉已经存在的快捷键 +// const existingKeys = state.shortcuts.shortcuts.map((s) => s.key) +// const newShortcuts = shortcutsToAdd.filter((shortcut) => !existingKeys.includes(shortcut.key)) + +// if (newShortcuts.length === 0) { +// return +// } + +// if (afterId === 'first') { +// // 添加到最前面 +// state.shortcuts.shortcuts.unshift(...newShortcuts) +// } else if (afterId === 'last') { +// // 添加到最后面 +// state.shortcuts.shortcuts.push(...newShortcuts) +// } else { +// // 添加到指定快捷键后面 +// const afterIndex = state.shortcuts.shortcuts.findIndex((shortcut) => shortcut.key === afterId) +// if (afterIndex !== -1) { +// state.shortcuts.shortcuts.splice(afterIndex + 1, 0, ...newShortcuts) +// } else { +// // 如果找不到指定的快捷键,则添加到最后 +// state.shortcuts.shortcuts.push(...newShortcuts) +// } +// } +// } + +// const migrateConfig = { +// '2': (state: RootState) => { +// try { +// addProvider(state, 'yi') +// return state +// } catch (error) { +// return state +// } +// }, +// '3': (state: RootState) => { +// try { +// addProvider(state, 'zhipu') +// return state +// } catch (error) { +// return state +// } +// }, +// '4': (state: RootState) => { +// try { +// addProvider(state, 'ollama') +// return state +// } catch (error) { +// return state +// } +// }, +// '5': (state: RootState) => { +// try { +// addProvider(state, 'moonshot') +// return state +// } catch (error) { +// return state +// } +// }, +// '6': (state: RootState) => { +// try { +// addProvider(state, 'openrouter') +// return state +// } catch (error) { +// return state +// } +// }, +// '7': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// language: navigator.language +// } +// } +// } catch (error) { +// return state +// } +// }, +// '8': (state: RootState) => { +// try { +// const fixAssistantName = (assistant: Assistant) => { +// // 2025/07/25 这俩键早没了,从远古版本迁移包出错的 +// if (isEmpty(assistant.name)) { +// assistant.name = i18n.t('chat.default.name') +// } + +// assistant.topics = assistant.topics.map((topic) => { +// if (isEmpty(topic.name)) { +// topic.name = i18n.t('chat.default.topic.name') +// } +// return topic +// }) + +// return assistant +// } + +// return { +// ...state, +// assistants: { +// ...state.assistants, +// defaultAssistant: fixAssistantName(state.assistants.defaultAssistant), +// assistants: state.assistants.assistants.map((assistant) => fixAssistantName(assistant)) +// } +// } +// } catch (error) { +// return state +// } +// }, +// '9': (state: RootState) => { +// try { +// return { +// ...state, +// llm: { +// ...state.llm, +// providers: state.llm.providers.map((provider) => { +// if (provider.id === 'zhipu' && provider.models[0] && provider.models[0].id === 'llama3-70b-8192') { +// provider.models = SYSTEM_MODELS.zhipu +// } +// return provider +// }) +// } +// } +// } catch (error) { +// return state +// } +// }, +// '10': (state: RootState) => { +// try { +// addProvider(state, 'baichuan') +// return state +// } catch (error) { +// return state +// } +// }, +// '11': (state: RootState) => { +// try { +// addProvider(state, 'dashscope') +// addProvider(state, 'anthropic') +// return state +// } catch (error) { +// return state +// } +// }, +// '12': (state: RootState) => { +// try { +// addProvider(state, 'aihubmix') +// return state +// } catch (error) { +// return state +// } +// }, +// '13': (state: RootState) => { +// try { +// return { +// ...state, +// assistants: { +// ...state.assistants, +// defaultAssistant: { +// ...state.assistants.defaultAssistant, +// name: ['Default Assistant', '默认助手'].includes(state.assistants.defaultAssistant.name) +// ? i18n.t('settings.assistant.label') +// : state.assistants.defaultAssistant.name +// } +// } +// } +// } catch (error) { +// return state +// } +// }, +// '14': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// showAssistants: true, +// proxyUrl: undefined +// } +// } +// } catch (error) { +// return state +// } +// }, +// '15': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// userName: '', +// showMessageDivider: true +// } +// } +// } catch (error) { +// return state +// } +// }, +// '16': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// messageFont: 'system', +// showInputEstimatedTokens: false +// } +// } +// } catch (error) { +// return state +// } +// }, +// '17': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// theme: 'auto' +// } +// } +// } catch (error) { +// return state +// } +// }, +// '19': (state: RootState) => { +// try { +// return { +// ...state, +// agents: { +// agents: [] +// }, +// llm: { +// ...state.llm, +// settings: { +// ollama: { +// keepAliveTime: 5 +// } +// } +// } +// } +// } catch (error) { +// return state +// } +// }, +// '20': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// fontSize: 14 +// } +// } +// } catch (error) { +// return state +// } +// }, +// '21': (state: RootState) => { +// try { +// addProvider(state, 'gemini') +// addProvider(state, 'stepfun') +// addProvider(state, 'doubao') +// return state +// } catch (error) { +// return state +// } +// }, +// '22': (state: RootState) => { +// try { +// addProvider(state, 'minimax') +// return state +// } catch (error) { +// return state +// } +// }, +// '23': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// showTopics: true, +// windowStyle: 'transparent' +// } +// } +// } catch (error) { +// return state +// } +// }, +// '24': (state: RootState) => { +// try { +// return { +// ...state, +// assistants: { +// ...state.assistants, +// assistants: state.assistants.assistants.map((assistant) => ({ +// ...assistant, +// topics: assistant.topics.map((topic) => ({ +// ...topic, +// createdAt: new Date().toISOString(), +// updatedAt: new Date().toISOString() +// })) +// })) +// }, +// settings: { +// ...state.settings, +// topicPosition: 'right' +// } +// } +// } catch (error) { +// return state +// } +// }, +// '25': (state: RootState) => { +// try { +// addProvider(state, 'github') +// return state +// } catch (error) { +// return state +// } +// }, +// '26': (state: RootState) => { +// try { +// addProvider(state, 'ocoolai') +// return state +// } catch (error) { +// return state +// } +// }, +// '27': (state: RootState) => { +// try { +// return { +// ...state, +// settings: { +// ...state.settings, +// renderInputMessageAsMarkdown: true +// } +// } +// } catch (error) { +// return state +// } +// }, +// '28': (state: RootState) => { +// try { +// addProvider(state, 'together') +// addProvider(state, 'fireworks') +// addProvider(state, 'zhinao') +// addProvider(state, 'hunyuan') +// addProvider(state, 'nvidia') +// return state +// } catch (error) { +// return state +// } +// }, +// '29': (state: RootState) => { +// try { +// return { +// ...state, +// assistants: { +// ...state.assistants, +// assistants: state.assistants.assistants.map((assistant) => { +// assistant.topics = assistant.topics.map((topic) => ({ +// ...topic, +// assistantId: assistant.id +// })) +// return assistant +// }) +// } +// } +// } catch (error) { +// return state +// } +// }, +// '30': (state: RootState) => { +// try { +// addProvider(state, 'azure-openai') +// return state +// } catch (error) { +// return state +// } +// }, +// '31': (state: RootState) => { +// try { +// return { +// ...state, +// llm: { +// ...state.llm, +// providers: state.llm.providers.map((provider) => { +// if (provider.id === 'azure-openai') { +// provider.models = provider.models.map((model) => ({ ...model, provider: 'azure-openai' })) +// } +// return provider +// }) +// } +// } +// } catch (error) { +// return state +// } +// }, +// '32': (state: RootState) => { +// try { +// addProvider(state, 'hunyuan') +// return state +// } catch (error) { +// return state +// } +// }, +// '33': (state: RootState) => { +// try { +// state.assistants.defaultAssistant.type = 'assistant' + +// state.agents.agents.forEach((agent) => { +// agent.type = 'agent' +// // @ts-ignore eslint-disable-next-line +// delete agent.group +// }) + +// return { +// ...state, +// assistants: { +// ...state.assistants, +// assistants: [...state.assistants.assistants].map((assistant) => { +// // @ts-ignore eslint-disable-next-line +// delete assistant.group +// return { +// ...assistant, +// id: assistant.id.length === 36 ? assistant.id : uuid(), +// type: assistant.type === 'system' ? assistant.type : 'assistant' +// } +// }) +// } +// } +// } catch (error) { +// return state +// } +// }, +// '34': (state: RootState) => { +// try { +// state.assistants.assistants.forEach((assistant) => { +// assistant.topics.forEach((topic) => { +// topic.assistantId = assistant.id +// runAsyncFunction(async () => { +// const _topic = await db.topics.get(topic.id) +// if (_topic) { +// const messages = (_topic?.messages || []).map((message) => ({ ...message, assistantId: assistant.id })) +// db.topics.put({ ..._topic, messages }, topic.id) +// } +// }) +// }) +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '35': (state: RootState) => { +// try { +// state.settings.mathEngine = 'KaTeX' +// return state +// } catch (error) { +// return state +// } +// }, +// '36': (state: RootState) => { +// try { +// state.settings.topicPosition = 'left' +// return state +// } catch (error) { +// return state +// } +// }, +// '37': (state: RootState) => { +// try { +// state.settings.messageStyle = 'plain' +// return state +// } catch (error) { +// return state +// } +// }, +// '38': (state: RootState) => { +// try { +// addProvider(state, 'grok') +// addProvider(state, 'hyperbolic') +// addProvider(state, 'mistral') +// return state +// } catch (error) { +// return state +// } +// }, +// '39': (state: RootState) => { +// try { +// // @ts-ignore eslint-disable-next-line +// state.settings.codeStyle = 'auto' +// return state +// } catch (error) { +// return state +// } +// }, +// '40': (state: RootState) => { +// try { +// state.settings.tray = true +// return state +// } catch (error) { +// return state +// } +// }, +// '41': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'gemini') { +// provider.type = 'gemini' +// } else if (provider.id === 'anthropic') { +// provider.type = 'anthropic' +// } else { +// provider.type = 'openai' +// } +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '42': (state: RootState) => { +// try { +// state.settings.proxyMode = state.settings.proxyUrl ? 'custom' : 'none' +// return state +// } catch (error) { +// return state +// } +// }, +// '43': (state: RootState) => { +// try { +// if (state.settings.proxyMode === 'none') { +// state.settings.proxyMode = 'system' +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '44': (state: RootState) => { +// try { +// state.settings.translateModelPrompt = TRANSLATE_PROMPT +// return state +// } catch (error) { +// return state +// } +// }, +// '45': (state: RootState) => { +// state.settings.enableTopicNaming = true +// return state +// }, +// '46': (state: RootState) => { +// try { +// if ( +// state.settings?.translateModelPrompt?.includes( +// 'If the target language is the same as the source language, do not translate' +// ) +// ) { +// state.settings.translateModelPrompt = TRANSLATE_PROMPT +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '47': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// provider.models.forEach((model) => { +// model.group = getDefaultGroupName(model.id) +// }) +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '48': (state: RootState) => { +// try { +// if (state.shortcuts) { +// state.shortcuts.shortcuts.forEach((shortcut) => { +// shortcut.system = shortcut.key !== 'new_topic' +// }) +// state.shortcuts.shortcuts.push({ +// key: 'toggle_show_assistants', +// shortcut: [isMac ? 'Command' : 'Ctrl', '['], +// editable: true, +// enabled: true, +// system: false +// }) +// state.shortcuts.shortcuts.push({ +// key: 'toggle_show_topics', +// shortcut: [isMac ? 'Command' : 'Ctrl', ']'], +// editable: true, +// enabled: true, +// system: false +// }) +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '49': (state: RootState) => { +// try { +// state.settings.pasteLongTextThreshold = 1500 +// if (state.shortcuts) { +// state.shortcuts.shortcuts = [ +// ...state.shortcuts.shortcuts, +// { +// key: 'copy_last_message', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'Shift', 'C'], +// editable: true, +// enabled: false, +// system: false +// } +// ] +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '50': (state: RootState) => { +// try { +// addProvider(state, 'jina') +// return state +// } catch (error) { +// return state +// } +// }, +// '51': (state: RootState) => { +// state.settings.topicNamingPrompt = '' +// return state +// }, +// '54': (state: RootState) => { +// try { +// if (state.shortcuts) { +// state.shortcuts.shortcuts.push({ +// key: 'search_message', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'F'], +// editable: true, +// enabled: true, +// system: false +// }) +// } +// state.settings.sidebarIcons = { +// visible: DefaultPreferences.default['ui.sidebar.icons.visible'], +// disabled: [] +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '55': (state: RootState) => { +// try { +// if (!state.settings.sidebarIcons) { +// state.settings.sidebarIcons = { +// visible: DefaultPreferences.default['ui.sidebar.icons.visible'], +// disabled: [] +// } +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '57': (state: RootState) => { +// try { +// if (state.shortcuts) { +// state.shortcuts.shortcuts.push({ +// key: 'mini_window', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'E'], +// editable: true, +// enabled: false, +// system: true +// }) +// } + +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'qwenlm') { +// provider.type = 'qwenlm' +// } +// }) + +// state.settings.enableQuickAssistant = false +// state.settings.clickTrayToShowQuickAssistant = true + +// return state +// } catch (error) { +// return state +// } +// }, +// '58': (state: RootState) => { +// try { +// if (state.shortcuts) { +// state.shortcuts.shortcuts.push( +// { +// key: 'clear_topic', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'L'], +// editable: true, +// enabled: true, +// system: false +// }, +// { +// key: 'toggle_new_context', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'R'], +// editable: true, +// enabled: true, +// system: false +// } +// ) +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '59': (state: RootState) => { +// try { +// addMiniApp(state, 'flowith') +// return state +// } catch (error) { +// return state +// } +// }, +// '60': (state: RootState) => { +// try { +// state.settings.multiModelMessageStyle = 'fold' +// return state +// } catch (error) { +// return state +// } +// }, +// '61': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'qwenlm') { +// provider.type = 'qwenlm' +// } +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '62': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'azure-openai') { +// provider.type = 'azure-openai' +// } +// }) +// state.settings.translateModelPrompt = TRANSLATE_PROMPT +// return state +// } catch (error) { +// return state +// } +// }, +// '63': (state: RootState) => { +// try { +// addMiniApp(state, '3mintop') +// return state +// } catch (error) { +// return state +// } +// }, +// '64': (state: RootState) => { +// try { +// state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'qwenlm') +// addProvider(state, 'baidu-cloud') +// return state +// } catch (error) { +// return state +// } +// }, +// '65': (state: RootState) => { +// try { +// // @ts-ignore expect error +// state.settings.targetLanguage = 'english' +// return state +// } catch (error) { +// return state +// } +// }, +// '66': (state: RootState) => { +// try { +// addProvider(state, 'gitee-ai') +// addProvider(state, 'ppio') +// addMiniApp(state, 'aistudio') +// state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'graphrag-kylin-mountain') + +// return state +// } catch (error) { +// return state +// } +// }, +// '67': (state: RootState) => { +// try { +// addMiniApp(state, 'xiaoyi') +// addProvider(state, 'modelscope') +// addProvider(state, 'lmstudio') +// addProvider(state, 'perplexity') +// addProvider(state, 'infini') +// addProvider(state, 'dmxapi') + +// state.llm.settings.lmstudio = { +// keepAliveTime: 5 +// } + +// return state +// } catch (error) { +// return state +// } +// }, +// '68': (state: RootState) => { +// try { +// addMiniApp(state, 'notebooklm') +// addProvider(state, 'modelscope') +// addProvider(state, 'lmstudio') +// return state +// } catch (error) { +// return state +// } +// }, +// '69': (state: RootState) => { +// try { +// addMiniApp(state, 'coze') +// state.settings.gridColumns = 2 +// state.settings.gridPopoverTrigger = 'hover' +// return state +// } catch (error) { +// return state +// } +// }, +// '70': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'dmxapi') { +// provider.apiHost = 'https://www.dmxapi.cn' +// } +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '71': (state: RootState) => { +// try { +// const appIds = ['dify', 'wpslingxi', 'lechat', 'abacus', 'lambdachat', 'baidu-ai-search'] + +// if (state.minapps) { +// appIds.forEach((id) => { +// const app = DEFAULT_MIN_APPS.find((app) => app.id === id) +// if (app) { +// state.minapps.enabled.push(app) +// } +// }) +// // remove zhihu-zhiada +// state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== 'zhihu-zhiada') +// state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== 'zhihu-zhiada') +// } + +// state.settings.thoughtAutoCollapse = true + +// return state +// } catch (error) { +// return state +// } +// }, +// '72': (state: RootState) => { +// try { +// addMiniApp(state, 'monica') + +// // remove duplicate lmstudio providers +// const emptyLmStudioProviderIndex = state.llm.providers.findLastIndex( +// (provider) => provider.id === 'lmstudio' && provider.models.length === 0 +// ) + +// if (emptyLmStudioProviderIndex !== -1) { +// state.llm.providers.splice(emptyLmStudioProviderIndex, 1) +// } + +// return state +// } catch (error) { +// return state +// } +// }, +// '73': (state: RootState) => { +// try { +// if (state.websearch) { +// state.websearch.searchWithTime = true +// state.websearch.maxResults = 5 +// state.websearch.excludeDomains = [] +// } + +// addProvider(state, 'lmstudio') +// addProvider(state, 'o3') +// state.llm.providers = moveProvider(state.llm.providers, 'o3', 2) + +// state.assistants.assistants.forEach((assistant) => { +// const leadingEmoji = getLeadingEmoji(assistant.name) +// if (leadingEmoji) { +// assistant.emoji = leadingEmoji +// assistant.name = assistant.name.replace(leadingEmoji, '').trim() +// } +// }) + +// state.agents.agents.forEach((agent) => { +// const leadingEmoji = getLeadingEmoji(agent.name) +// if (leadingEmoji) { +// agent.emoji = leadingEmoji +// agent.name = agent.name.replace(leadingEmoji, '').trim() +// } +// }) + +// const defaultAssistantEmoji = getLeadingEmoji(state.assistants.defaultAssistant.name) + +// if (defaultAssistantEmoji) { +// state.assistants.defaultAssistant.emoji = defaultAssistantEmoji +// state.assistants.defaultAssistant.name = state.assistants.defaultAssistant.name +// .replace(defaultAssistantEmoji, '') +// .trim() +// } + +// return state +// } catch (error) { +// return state +// } +// }, +// '74': (state: RootState) => { +// try { +// addProvider(state, 'xirang') +// return state +// } catch (error) { +// return state +// } +// }, +// '75': (state: RootState) => { +// try { +// addMiniApp(state, 'you') +// addMiniApp(state, 'cici') +// addMiniApp(state, 'zhihu') +// return state +// } catch (error) { +// return state +// } +// }, +// '76': (state: RootState) => { +// try { +// addProvider(state, 'tencent-cloud-ti') +// return state +// } catch (error) { +// return state +// } +// }, +// '77': (state: RootState) => { +// try { +// addWebSearchProvider(state, 'searxng') +// addWebSearchProvider(state, 'exa') +// if (state.websearch) { +// state.websearch.providers.forEach((p) => { +// // @ts-ignore eslint-disable-next-line +// delete p.enabled +// }) +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '78': (state: RootState) => { +// try { +// state.llm.providers = moveProvider(state.llm.providers, 'ppio', 9) +// state.llm.providers = moveProvider(state.llm.providers, 'infini', 10) +// removeMiniAppIconsFromState(state) +// return state +// } catch (error) { +// return state +// } +// }, +// '79': (state: RootState) => { +// try { +// addProvider(state, 'gpustack') +// return state +// } catch (error) { +// return state +// } +// }, +// '80': (state: RootState) => { +// try { +// addProvider(state, 'alayanew') +// state.llm.providers = moveProvider(state.llm.providers, 'alayanew', 10) +// return state +// } catch (error) { +// return state +// } +// }, +// '81': (state: RootState) => { +// try { +// addProvider(state, 'copilot') +// return state +// } catch (error) { +// return state +// } +// }, +// '82': (state: RootState) => { +// try { +// const runtimeState = state.runtime as any +// if (runtimeState?.webdavSync) { +// state.backup = state.backup || {} +// state.backup = { +// ...state.backup, +// webdavSync: { +// lastSyncTime: runtimeState.webdavSync.lastSyncTime || null, +// syncing: runtimeState.webdavSync.syncing || false, +// lastSyncError: runtimeState.webdavSync.lastSyncError || null +// } +// } +// delete runtimeState.webdavSync +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '83': (state: RootState) => { +// try { +// state.settings.messageNavigation = 'buttons' +// state.settings.launchOnBoot = false +// state.settings.launchToTray = false +// state.settings.trayOnClose = true +// return state +// } catch (error) { +// logger.error('migrate 83 error', error as Error) +// return state +// } +// }, +// '84': (state: RootState) => { +// try { +// addProvider(state, 'voyageai') +// return state +// } catch (error) { +// logger.error('migrate 84 error', error as Error) +// return state +// } +// }, +// '85': (state: RootState) => { +// try { +// // @ts-ignore eslint-disable-next-line +// state.settings.autoCheckUpdate = !state.settings.manualUpdateCheck +// // @ts-ignore eslint-disable-next-line +// delete state.settings.manualUpdateCheck +// state.settings.gridPopoverTrigger = 'click' +// return state +// } catch (error) { +// return state +// } +// }, +// '86': (state: RootState) => { +// try { +// if (state?.mcp?.servers) { +// state.mcp.servers = state.mcp.servers.map((server) => ({ +// ...server, +// id: nanoid() +// })) +// } +// } catch (error) { +// return state +// } +// return state +// }, +// '87': (state: RootState) => { +// try { +// state.settings.maxKeepAliveMinapps = 3 +// state.settings.showOpenedMinappsInSidebar = true +// return state +// } catch (error) { +// return state +// } +// }, +// '88': (state: RootState) => { +// try { +// if (state?.mcp?.servers) { +// const hasAutoInstall = state.mcp.servers.some((server) => server.name === '@cherry/mcp-auto-install') +// if (!hasAutoInstall) { +// const defaultServer = mcpSlice.getInitialState().servers[0] +// state.mcp.servers = [{ ...defaultServer, id: nanoid() }, ...state.mcp.servers] +// } +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '89': (state: RootState) => { +// try { +// removeMiniAppFromState(state, 'aistudio') +// return state +// } catch (error) { +// return state +// } +// }, +// '90': (state: RootState) => { +// try { +// state.settings.enableDataCollection = true +// return state +// } catch (error) { +// return state +// } +// }, +// '91': (state: RootState) => { +// try { +// // @ts-ignore eslint-disable-next-line +// state.settings.codeCacheable = false +// // @ts-ignore eslint-disable-next-line +// state.settings.codeCacheMaxSize = 1000 +// // @ts-ignore eslint-disable-next-line +// state.settings.codeCacheTTL = 15 +// // @ts-ignore eslint-disable-next-line +// state.settings.codeCacheThreshold = 2 +// addProvider(state, 'qiniu') +// return state +// } catch (error) { +// return state +// } +// }, +// '92': (state: RootState) => { +// try { +// addMiniApp(state, 'dangbei') +// state.llm.providers = moveProvider(state.llm.providers, 'qiniu', 12) +// return state +// } catch (error) { +// return state +// } +// }, +// '93': (state: RootState) => { +// try { +// if (!state?.settings?.exportMenuOptions) { +// state.settings.exportMenuOptions = settingsInitialState.exportMenuOptions +// return state +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '94': (state: RootState) => { +// try { +// state.settings.enableQuickPanelTriggers = false +// return state +// } catch (error) { +// return state +// } +// }, +// '95': (state: RootState) => { +// try { +// addWebSearchProvider(state, 'local-google') +// addWebSearchProvider(state, 'local-bing') +// addWebSearchProvider(state, 'local-baidu') + +// if (state.websearch) { +// if (isEmpty(state.websearch.subscribeSources)) { +// state.websearch.subscribeSources = [] +// } +// } + +// const qiniuProvider = state.llm.providers.find((provider) => provider.id === 'qiniu') +// if (qiniuProvider && isEmpty(qiniuProvider.models)) { +// qiniuProvider.models = SYSTEM_MODELS.qiniu +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '96': (state: RootState) => { +// try { +// // @ts-ignore eslint-disable-next-line +// state.settings.assistantIconType = state.settings?.showAssistantIcon ? 'model' : 'emoji' +// // @ts-ignore eslint-disable-next-line +// delete state.settings.showAssistantIcon +// return state +// } catch (error) { +// return state +// } +// }, +// '97': (state: RootState) => { +// try { +// addMiniApp(state, 'zai') +// state.settings.webdavMaxBackups = 0 +// if (state.websearch && state.websearch.providers) { +// state.websearch.providers.forEach((provider) => { +// provider.basicAuthUsername = '' +// provider.basicAuthPassword = '' +// }) +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '98': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// if (provider.type === 'openai' && provider.id !== 'openai') { +// // @ts-ignore eslint-disable-next-line +// provider.type = 'openai-compatible' +// } +// }) +// return state +// } catch (error) { +// return state +// } +// }, +// '99': (state: RootState) => { +// try { +// state.settings.showPrompt = true + +// addWebSearchProvider(state, 'bocha') + +// updateWebSearchProvider(state, { +// id: 'exa', +// apiHost: 'https://api.exa.ai' +// }) + +// updateWebSearchProvider(state, { +// id: 'tavily', +// apiHost: 'https://api.tavily.com' +// }) + +// // Remove basic auth fields from exa and tavily +// if (state.websearch?.providers) { +// state.websearch.providers = state.websearch.providers.map((provider) => { +// if (provider.id === 'exa' || provider.id === 'tavily') { +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const { basicAuthUsername, basicAuthPassword, ...rest } = provider +// return rest +// } +// return provider +// }) +// } +// return state +// } catch (error) { +// return state +// } +// }, +// '100': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// // @ts-ignore eslint-disable-next-line +// if (['openai-compatible', 'openai'].includes(provider.type)) { +// provider.type = 'openai' +// } +// if (provider.id === 'openai') { +// provider.type = 'openai-response' +// } +// }) +// state.assistants.assistants.forEach((assistant) => { +// assistant.knowledgeRecognition = 'off' +// }) +// return state +// } catch (error) { +// logger.error('migrate 100 error', error as Error) +// return state +// } +// }, +// '101': (state: RootState) => { +// try { +// state.assistants.assistants.forEach((assistant) => { +// if (assistant.settings) { +// // @ts-ignore eslint-disable-next-line +// if (assistant.settings.enableToolUse) { +// // @ts-ignore eslint-disable-next-line +// assistant.settings.toolUseMode = assistant.settings.enableToolUse ? 'function' : 'prompt' +// // @ts-ignore eslint-disable-next-line +// delete assistant.settings.enableToolUse +// } +// } +// }) +// if (state.shortcuts) { +// state.shortcuts.shortcuts.push({ +// key: 'exit_fullscreen', +// shortcut: ['Escape'], +// editable: false, +// enabled: true, +// system: true +// }) +// } +// return state +// } catch (error) { +// logger.error('migrate 101 error', error as Error) +// return state +// } +// }, +// '102': (state: RootState) => { +// try { +// state.settings.openAI = { +// summaryText: 'off', +// serviceTier: 'auto', +// verbosity: 'medium' +// } + +// state.settings.codeExecution = { +// enabled: false, +// timeoutMinutes: 1 +// } +// state.settings.codeEditor = { +// enabled: false, +// themeLight: 'auto', +// themeDark: 'auto', +// highlightActiveLine: false, +// foldGutter: false, +// autocompletion: true, +// keymap: false +// } +// // @ts-ignore eslint-disable-next-line +// state.settings.codePreview = { +// themeLight: 'auto', +// themeDark: 'auto' +// } + +// // @ts-ignore eslint-disable-next-line +// if (state.settings.codeStyle) { +// // @ts-ignore eslint-disable-next-line +// state.settings.codePreview.themeLight = state.settings.codeStyle +// // @ts-ignore eslint-disable-next-line +// state.settings.codePreview.themeDark = state.settings.codeStyle +// } + +// // @ts-ignore eslint-disable-next-line +// delete state.settings.codeStyle +// // @ts-ignore eslint-disable-next-line +// delete state.settings.codeCacheable +// // @ts-ignore eslint-disable-next-line +// delete state.settings.codeCacheMaxSize +// // @ts-ignore eslint-disable-next-line +// delete state.settings.codeCacheTTL +// // @ts-ignore eslint-disable-next-line +// delete state.settings.codeCacheThreshold +// return state +// } catch (error) { +// logger.error('migrate 102 error', error as Error) +// return state +// } +// }, +// '103': (state: RootState) => { +// try { +// if (state.shortcuts) { +// if (!state.shortcuts.shortcuts.find((shortcut) => shortcut.key === 'search_message_in_chat')) { +// state.shortcuts.shortcuts.push({ +// key: 'search_message_in_chat', +// shortcut: [isMac ? 'Command' : 'Ctrl', 'F'], +// editable: true, +// enabled: true, +// system: false +// }) +// } +// const searchMessageShortcut = state.shortcuts.shortcuts.find((shortcut) => shortcut.key === 'search_message') +// const targetShortcut = [isMac ? 'Command' : 'Ctrl', 'F'] +// if ( +// searchMessageShortcut && +// Array.isArray(searchMessageShortcut.shortcut) && +// searchMessageShortcut.shortcut.length === targetShortcut.length && +// searchMessageShortcut.shortcut.every((v, i) => v === targetShortcut[i]) +// ) { +// searchMessageShortcut.shortcut = [isMac ? 'Command' : 'Ctrl', 'Shift', 'F'] +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 103 error', error as Error) +// return state +// } +// }, +// '104': (state: RootState) => { +// try { +// addProvider(state, 'burncloud') +// state.llm.providers = moveProvider(state.llm.providers, 'burncloud', 10) +// return state +// } catch (error) { +// logger.error('migrate 104 error', error as Error) +// return state +// } +// }, +// '105': (state: RootState) => { +// try { +// state.settings.notification = settingsInitialState.notification +// addMiniApp(state, 'google') +// if (!state.settings.openAI) { +// state.settings.openAI = { +// summaryText: 'off', +// serviceTier: 'auto', +// verbosity: 'medium' +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 105 error', error as Error) +// return state +// } +// }, +// '106': (state: RootState) => { +// try { +// addProvider(state, 'tokenflux') +// state.llm.providers = moveProvider(state.llm.providers, 'tokenflux', 15) +// return state +// } catch (error) { +// logger.error('migrate 106 error', error as Error) +// return state +// } +// }, +// '107': (state: RootState) => { +// try { +// if (state.paintings && !state.paintings.dmxapi_paintings) { +// state.paintings.dmxapi_paintings = [] +// } +// return state +// } catch (error) { +// logger.error('migrate 107 error', error as Error) +// return state +// } +// }, +// '108': (state: RootState) => { +// try { +// state.inputTools.toolOrder = DEFAULT_TOOL_ORDER +// state.inputTools.isCollapsed = false +// return state +// } catch (error) { +// logger.error('migrate 108 error', error as Error) +// return state +// } +// }, +// '109': (state: RootState) => { +// try { +// state.settings.userTheme = settingsInitialState.userTheme +// return state +// } catch (error) { +// logger.error('migrate 109 error', error as Error) +// return state +// } +// }, +// '110': (state: RootState) => { +// try { +// if (state.paintings && !state.paintings.tokenflux_paintings) { +// state.paintings.tokenflux_paintings = [] +// } +// state.settings.testPlan = false +// return state +// } catch (error) { +// logger.error('migrate 110 error', error as Error) +// return state +// } +// }, +// '111': (state: RootState) => { +// try { +// addSelectionAction(state, 'quote') +// if ( +// state.llm.translateModel.provider === 'silicon' && +// state.llm.translateModel.id === 'meta-llama/Llama-3.3-70B-Instruct' +// ) { +// state.llm.translateModel = SYSTEM_MODELS.defaultModel[2] +// } + +// // add selection_assistant_toggle and selection_assistant_select_text shortcuts after mini_window +// addShortcuts(state, ['selection_assistant_toggle', 'selection_assistant_select_text'], 'mini_window') + +// return state +// } catch (error) { +// logger.error('migrate 111 error', error as Error) +// return state +// } +// }, +// '112': (state: RootState) => { +// try { +// addProvider(state, 'cephalon') +// addProvider(state, '302ai') +// addProvider(state, 'lanyun') +// state.llm.providers = moveProvider(state.llm.providers, 'cephalon', 13) +// state.llm.providers = moveProvider(state.llm.providers, '302ai', 14) +// state.llm.providers = moveProvider(state.llm.providers, 'lanyun', 15) +// return state +// } catch (error) { +// logger.error('migrate 112 error', error as Error) +// return state +// } +// }, +// '113': (state: RootState) => { +// try { +// addProvider(state, 'vertexai') +// if (!state.llm.settings.vertexai) { +// state.llm.settings.vertexai = llmInitialState.settings.vertexai +// } +// updateProvider(state, 'gemini', { +// isVertex: false +// }) +// updateProvider(state, 'vertexai', { +// isVertex: true +// }) +// return state +// } catch (error) { +// logger.error('migrate 113 error', error as Error) +// return state +// } +// }, +// '114': (state: RootState) => { +// try { +// if (state.settings && state.settings.exportMenuOptions) { +// if (typeof state.settings.exportMenuOptions.plain_text === 'undefined') { +// state.settings.exportMenuOptions.plain_text = true +// } +// } +// if (state.settings) { +// state.settings.enableSpellCheck = false +// state.settings.spellCheckLanguages = [] +// } +// return state +// } catch (error) { +// logger.error('migrate 114 error', error as Error) +// return state +// } +// }, +// '115': (state: RootState) => { +// try { +// state.assistants.assistants.forEach((assistant) => { +// if (!assistant.settings) { +// assistant.settings = { +// temperature: DEFAULT_TEMPERATURE, +// contextCount: DEFAULT_CONTEXTCOUNT, +// topP: 1, +// toolUseMode: 'prompt', +// customParameters: [], +// streamOutput: true, +// enableMaxTokens: false +// } +// } +// }) +// return state +// } catch (error) { +// logger.error('migrate 115 error', error as Error) +// return state +// } +// }, +// '116': (state: RootState) => { +// try { +// if (state.websearch) { +// // migrate contentLimit to cutoffLimit +// // @ts-ignore eslint-disable-next-line +// if (state.websearch.contentLimit) { +// state.websearch.compressionConfig = { +// method: 'cutoff', +// cutoffUnit: 'char', +// // @ts-ignore eslint-disable-next-line +// cutoffLimit: state.websearch.contentLimit +// } +// } else { +// state.websearch.compressionConfig = { method: 'none', cutoffUnit: 'char' } +// } + +// // @ts-ignore eslint-disable-next-line +// delete state.websearch.contentLimit +// } +// if (state.settings) { +// state.settings.testChannel = UpgradeChannel.LATEST +// } + +// return state +// } catch (error) { +// logger.error('migrate 116 error', error as Error) +// return state +// } +// }, +// '117': (state: RootState) => { +// try { +// const ppioProvider = state.llm.providers.find((provider) => provider.id === 'ppio') +// const modelsToRemove = [ +// 'qwen/qwen-2.5-72b-instruct', +// 'qwen/qwen2.5-32b-instruct', +// 'meta-llama/llama-3.1-70b-instruct', +// 'meta-llama/llama-3.1-8b-instruct', +// '01-ai/yi-1.5-34b-chat', +// '01-ai/yi-1.5-9b-chat', +// 'thudm/glm-z1-32b-0414', +// 'thudm/glm-z1-9b-0414' +// ] +// if (ppioProvider) { +// updateProvider(state, 'ppio', { +// models: [ +// ...ppioProvider.models.filter((model) => !modelsToRemove.includes(model.id)), +// ...SYSTEM_MODELS.ppio.filter( +// (systemModel) => !ppioProvider.models.some((existingModel) => existingModel.id === systemModel.id) +// ) +// ], +// apiHost: 'https://api.ppinfra.com/v3/openai/' +// }) +// } +// state.assistants.assistants.forEach((assistant) => { +// if (assistant.settings && assistant.settings.streamOutput === undefined) { +// assistant.settings = { +// ...assistant.settings, +// streamOutput: true +// } +// } +// }) +// return state +// } catch (error) { +// logger.error('migrate 117 error', error as Error) +// return state +// } +// }, +// '118': (state: RootState) => { +// try { +// addProvider(state, 'ph8') +// state.llm.providers = moveProvider(state.llm.providers, 'ph8', 14) + +// if (!state.settings.userId) { +// state.settings.userId = uuid() +// } + +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'mistral') { +// provider.type = 'mistral' +// } +// }) + +// return state +// } catch (error) { +// logger.error('migrate 118 error', error as Error) +// return state +// } +// }, +// '119': (state: RootState) => { +// try { +// addProvider(state, 'new-api') +// state.llm.providers = moveProvider(state.llm.providers, 'new-api', 16) +// state.settings.disableHardwareAcceleration = false +// // migrate to enable memory feature on sidebar +// if (state.settings && state.settings.sidebarIcons) { +// // Check if 'memory' is not already in visible icons +// if (!state.settings.sidebarIcons.visible.includes('memory' as any)) { +// state.settings.sidebarIcons.visible = [...state.settings.sidebarIcons.visible, 'memory' as any] +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 119 error', error as Error) +// return state +// } +// }, +// '120': (state: RootState) => { +// try { +// // migrate to remove memory feature from sidebar (moved to settings) +// if (state.settings && state.settings.sidebarIcons) { +// // Remove 'memory' from visible icons if present +// state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.filter( +// (icon) => icon !== ('memory' as any) +// ) +// // Remove 'memory' from disabled icons if present +// state.settings.sidebarIcons.disabled = state.settings.sidebarIcons.disabled.filter( +// (icon) => icon !== ('memory' as any) +// ) +// } + +// if (!state.settings.s3) { +// state.settings.s3 = settingsInitialState.s3 +// } + +// const langMap: Record = { +// english: 'en-us', +// chinese: 'zh-cn', +// 'chinese-traditional': 'zh-tw', +// japanese: 'ja-jp', +// russian: 'ru-ru' +// } + +// const origin = state.settings.targetLanguage +// const newLang = langMap[origin] +// if (newLang) state.settings.targetLanguage = newLang +// else state.settings.targetLanguage = 'en-us' + +// state.llm.providers.forEach((provider) => { +// if (provider.id === 'azure-openai') { +// provider.type = 'azure-openai' +// } +// }) + +// state.settings.localBackupMaxBackups = 0 +// state.settings.localBackupSkipBackupFile = false +// state.settings.localBackupDir = '' +// state.settings.localBackupAutoSync = false +// state.settings.localBackupSyncInterval = 0 +// return state +// } catch (error) { +// logger.error('migrate 120 error', error as Error) +// return state +// } +// }, +// '121': (state: RootState) => { +// try { +// const { toolOrder } = state.inputTools +// const urlContextKey = 'url_context' +// if (!toolOrder.visible.includes(urlContextKey)) { +// const webSearchIndex = toolOrder.visible.indexOf('web_search') +// const knowledgeBaseIndex = toolOrder.visible.indexOf('knowledge_base') +// if (webSearchIndex !== -1) { +// toolOrder.visible.splice(webSearchIndex, 0, urlContextKey) +// } else if (knowledgeBaseIndex !== -1) { +// toolOrder.visible.splice(knowledgeBaseIndex, 0, urlContextKey) +// } else { +// toolOrder.visible.push(urlContextKey) +// } +// } + +// for (const assistant of state.assistants.assistants) { +// if (assistant.settings?.toolUseMode === 'prompt' && isFunctionCallingModel(assistant.model)) { +// assistant.settings.toolUseMode = 'function' +// } +// } + +// if (state.settings && typeof state.settings.webdavDisableStream === 'undefined') { +// state.settings.webdavDisableStream = false +// } + +// return state +// } catch (error) { +// logger.error('migrate 121 error', error as Error) +// return state +// } +// }, +// '122': (state: RootState) => { +// try { +// state.settings.navbarPosition = 'left' +// return state +// } catch (error) { +// logger.error('migrate 122 error', error as Error) +// return state +// } +// }, +// '123': (state: RootState) => { +// try { +// state.llm.providers.forEach((provider) => { +// provider.models.forEach((model) => { +// if (model.type && Array.isArray(model.type)) { +// model.capabilities = model.type.map((t) => ({ +// type: t, +// isUserSelected: true +// })) +// delete model.type +// } +// }) +// }) + +// const lanyunProvider = state.llm.providers.find((provider) => provider.id === 'lanyun') +// if (lanyunProvider && lanyunProvider.models.length === 0) { +// updateProvider(state, 'lanyun', { models: SYSTEM_MODELS.lanyun }) +// } + +// return state +// } catch (error) { +// logger.error('migrate 123 error', error as Error) +// return state +// } +// }, // 1.5.4 +// '124': (state: RootState) => { +// try { +// state.assistants.assistants.forEach((assistant) => { +// if (assistant.settings && !assistant.settings.toolUseMode) { +// assistant.settings.toolUseMode = 'prompt' +// } +// }) + +// const updateModelTextDelta = (model?: Model) => { +// if (model) { +// model.supported_text_delta = true +// if (isNotSupportedTextDelta(model)) { +// model.supported_text_delta = false +// } +// } +// } + +// state.llm.providers.forEach((provider) => { +// provider.models.forEach((model) => { +// updateModelTextDelta(model) +// }) +// }) +// state.assistants.assistants.forEach((assistant) => { +// updateModelTextDelta(assistant.defaultModel) +// updateModelTextDelta(assistant.model) +// }) + +// updateModelTextDelta(state.llm.defaultModel) +// updateModelTextDelta(state.llm.topicNamingModel) +// updateModelTextDelta(state.llm.translateModel) + +// if (state.assistants.defaultAssistant.model) { +// updateModelTextDelta(state.assistants.defaultAssistant.model) +// updateModelTextDelta(state.assistants.defaultAssistant.defaultModel) +// } + +// addProvider(state, 'aws-bedrock') + +// // 初始化 awsBedrock 设置 +// if (!state.llm.settings.awsBedrock) { +// state.llm.settings.awsBedrock = llmInitialState.settings.awsBedrock +// } + +// return state +// } catch (error) { +// logger.error('migrate 124 error', error as Error) +// return state +// } +// }, +// '125': (state: RootState) => { +// try { +// // Initialize API server configuration if not present +// if (!state.settings.apiServer) { +// state.settings.apiServer = { +// enabled: false, +// host: 'localhost', +// port: 23333, +// apiKey: `cs-sk-${uuid()}` +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 125 error', error as Error) +// return state +// } +// }, +// '126': (state: RootState) => { +// try { +// state.knowledge.bases.forEach((base) => { +// // @ts-ignore eslint-disable-next-line +// if (base.preprocessOrOcrProvider) { +// // @ts-ignore eslint-disable-next-line +// base.preprocessProvider = base.preprocessOrOcrProvider +// // @ts-ignore eslint-disable-next-line +// delete base.preprocessOrOcrProvider +// // @ts-ignore eslint-disable-next-line +// if (base.preprocessProvider.type === 'ocr') { +// // @ts-ignore eslint-disable-next-line +// delete base.preprocessProvider +// } +// } +// }) +// return state +// } catch (error) { +// logger.error('migrate 126 error', error as Error) +// return state +// } +// }, +// '127': (state: RootState) => { +// try { +// addProvider(state, 'poe') + +// // 迁移api选项设置 +// state.llm.providers.forEach((provider) => { +// // 新字段默认支持 +// const changes = { +// isNotSupportArrayContent: false, +// isNotSupportDeveloperRole: false, +// isNotSupportStreamOptions: false +// } +// if (!isSupportArrayContentProvider(provider) || provider.isNotSupportArrayContent) { +// // 原本开启了兼容模式的provider不受影响 +// changes.isNotSupportArrayContent = true +// } +// if (!isSupportDeveloperRoleProvider(provider)) { +// changes.isNotSupportDeveloperRole = true +// } +// if (!isSupportStreamOptionsProvider(provider)) { +// changes.isNotSupportStreamOptions = true +// } +// updateProvider(state, provider.id, changes) +// }) + +// // 迁移以前删除掉的内置提供商 +// for (const provider of state.llm.providers) { +// if (provider.isSystem && !isSystemProvider(provider)) { +// updateProvider(state, provider.id, { isSystem: false }) +// } +// } + +// if (!state.settings.proxyBypassRules) { +// state.settings.proxyBypassRules = defaultByPassRules +// } +// return state +// } catch (error) { +// logger.error('migrate 127 error', error as Error) +// return state +// } +// }, +// '128': (state: RootState) => { +// try { +// // 迁移 service tier 设置 +// const openai = state.llm.providers.find((provider) => provider.id === SystemProviderIds.openai) +// const serviceTier = state.settings.openAI.serviceTier +// if (openai) { +// openai.serviceTier = serviceTier +// } + +// // @ts-ignore eslint-disable-next-line +// if (state.settings.codePreview) { +// // @ts-ignore eslint-disable-next-line +// state.settings.codeViewer = state.settings.codePreview +// } else { +// state.settings.codeViewer = { +// themeLight: 'auto', +// themeDark: 'auto' +// } +// } + +// return state +// } catch (error) { +// logger.error('migrate 128 error', error as Error) +// return state +// } +// }, +// '129': (state: RootState) => { +// try { +// // 聚合 api options +// state.llm.providers.forEach((p) => { +// if (isSystemProvider(p)) { +// updateProvider(state, p.id, { apiOptions: undefined }) +// } else { +// const changes: ProviderApiOptions = { +// isNotSupportArrayContent: p.isNotSupportArrayContent, +// isNotSupportServiceTier: p.isNotSupportServiceTier, +// isNotSupportDeveloperRole: p.isNotSupportDeveloperRole, +// isNotSupportStreamOptions: p.isNotSupportStreamOptions +// } +// updateProvider(state, p.id, { apiOptions: changes }) +// } +// }) +// return state +// } catch (error) { +// logger.error('migrate 129 error', error as Error) +// return state +// } +// }, +// '130': (state: RootState) => { +// try { +// if (state.settings && state.settings.openAI && !state.settings.openAI.verbosity) { +// state.settings.openAI.verbosity = 'medium' +// } +// // 为 nutstore 添加备份数量限制的默认值 +// if (state.nutstore && state.nutstore.nutstoreMaxBackups === undefined) { +// state.nutstore.nutstoreMaxBackups = 0 +// } +// return state +// } catch (error) { +// logger.error('migrate 130 error', error as Error) +// return state +// } +// }, +// '131': (state: RootState) => { +// try { +// state.settings.mathEnableSingleDollar = true +// return state +// } catch (error) { +// logger.error('migrate 131 error', error as Error) +// return state +// } +// }, +// '132': (state: RootState) => { +// try { +// state.llm.providers.forEach((p) => { +// // 如果原本是undefined则不做改动,静默从默认支持改为默认不支持 +// if (p.apiOptions?.isNotSupportDeveloperRole) { +// p.apiOptions.isSupportDeveloperRole = !p.apiOptions.isNotSupportDeveloperRole +// } +// if (p.apiOptions?.isNotSupportServiceTier) { +// p.apiOptions.isSupportServiceTier = !p.apiOptions.isNotSupportServiceTier +// } +// }) +// return state +// } catch (error) { +// logger.error('migrate 132 error', error as Error) +// return state +// } +// }, +// '133': (state: RootState) => { +// try { +// state.settings.sidebarIcons.visible.push('code_tools') +// if (state.codeTools) { +// state.codeTools.environmentVariables = { +// 'qwen-code': '', +// 'claude-code': '', +// 'gemini-cli': '' +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 133 error', error as Error) +// return state +// } +// }, +// '134': (state: RootState) => { +// try { +// state.llm.quickModel = state.llm.topicNamingModel + +// return state +// } catch (error) { +// logger.error('migrate 134 error', error as Error) +// return state +// } +// }, +// '135': (state: RootState) => { +// try { +// if (!state.assistants.defaultAssistant.settings) { +// state.assistants.defaultAssistant.settings = DEFAULT_ASSISTANT_SETTINGS +// } else if (!state.assistants.defaultAssistant.settings.toolUseMode) { +// state.assistants.defaultAssistant.settings.toolUseMode = 'prompt' +// } +// return state +// } catch (error) { +// logger.error('migrate 135 error', error as Error) +// return state +// } +// }, +// '136': (state: RootState) => { +// try { +// state.settings.sidebarIcons.visible = [...new Set(state.settings.sidebarIcons.visible)].filter((icon) => +// DefaultPreferences.default['ui.sidebar.icons.visible'].includes(icon) +// ) +// state.settings.sidebarIcons.disabled = [...new Set(state.settings.sidebarIcons.disabled)].filter((icon) => +// DefaultPreferences.default['ui.sidebar.icons.visible'].includes(icon) +// ) +// return state +// } catch (error) { +// logger.error('migrate 136 error', error as Error) +// return state +// } +// }, +// '137': (state: RootState) => { +// try { +// state.ocr = { +// providers: BUILTIN_OCR_PROVIDERS, +// imageProviderId: DEFAULT_OCR_PROVIDER.image.id +// } +// state.translate.translateInput = '' +// return state +// } catch (error) { +// logger.error('migrate 137 error', error as Error) +// return state +// } +// }, +// '138': (state: RootState) => { +// try { +// addOcrProvider(state, BUILTIN_OCR_PROVIDERS_MAP.system) +// return state +// } catch (error) { +// logger.error('migrate 138 error', error as Error) +// return state +// } +// }, +// '139': (state: RootState) => { +// try { +// addProvider(state, 'cherryin') +// state.llm.providers = moveProvider(state.llm.providers, 'cherryin', 1) + +// const zhipuProvider = state.llm.providers.find((p) => p.id === 'zhipu') + +// if (zhipuProvider) { +// // Update zhipu model list +// if (!zhipuProvider.enabled) { +// zhipuProvider.models = SYSTEM_MODELS.zhipu +// } + +// // Update zhipu model list +// if (zhipuProvider.models.length === 0) { +// zhipuProvider.models = SYSTEM_MODELS.zhipu +// } + +// // Add GLM-4.5-Flash model if not exists +// const hasGlm45FlashModel = zhipuProvider?.models.find((m) => m.id === 'glm-4.5-flash') + +// if (!hasGlm45FlashModel) { +// zhipuProvider?.models.push(glm45FlashModel) +// } + +// // Update default painting provider to zhipu +// state.settings.defaultPaintingProvider = 'zhipu' + +// // Add zhipu web search provider +// addWebSearchProvider(state, 'zhipu') + +// // Update zhipu web search provider api key +// if (zhipuProvider.apiKey) { +// state?.websearch?.providers.forEach((provider) => { +// if (provider.id === 'zhipu') { +// provider.apiKey = zhipuProvider.apiKey +// } +// }) +// } +// } + +// return state +// } catch (error) { +// logger.error('migrate 139 error', error as Error) +// return state +// } +// }, +// '140': (state: RootState) => { +// try { +// state.paintings = { +// // @ts-ignore paintings +// siliconflow_paintings: state?.paintings?.paintings || [], +// // @ts-ignore DMXAPIPaintings +// dmxapi_paintings: state?.paintings?.DMXAPIPaintings || [], +// // @ts-ignore tokenFluxPaintings +// tokenflux_paintings: state?.paintings?.tokenFluxPaintings || [], +// zhipu_paintings: [], +// // @ts-ignore generate +// aihubmix_image_generate: state?.paintings?.generate || [], +// // @ts-ignore remix +// aihubmix_image_remix: state?.paintings?.remix || [], +// // @ts-ignore edit +// aihubmix_image_edit: state?.paintings?.edit || [], +// // @ts-ignore upscale +// aihubmix_image_upscale: state?.paintings?.upscale || [], +// openai_image_generate: state?.paintings?.openai_image_generate || [], +// openai_image_edit: state?.paintings?.openai_image_edit || [] +// } + +// return state +// } catch (error) { +// logger.error('migrate 140 error', error as Error) +// return state +// } +// }, +// '141': (state: RootState) => { +// try { +// if (state.settings && state.settings.sidebarIcons) { +// // Check if 'notes' is not already in visible icons +// if (!state.settings.sidebarIcons.visible.includes('notes')) { +// state.settings.sidebarIcons.visible = [...state.settings.sidebarIcons.visible, 'notes'] +// } +// } +// return state +// } catch (error) { +// logger.error('migrate 141 error', error as Error) +// return state +// } +// }, +// '142': (state: RootState) => { +// try { +// // Initialize notes settings if not present +// if (!state.note) { +// state.note = notesInitialState +// } +// return state +// } catch (error) { +// logger.error('migrate 142 error', error as Error) +// return state +// } +// }, +// '143': (state: RootState) => { +// try { +// addMiniApp(state, 'longcat') +// return state +// } catch (error) { +// return state +// } +// }, +// '144': (state: RootState) => { +// try { +// if (state.settings) { +// state.settings.confirmDeleteMessage = settingsInitialState.confirmDeleteMessage +// state.settings.confirmRegenerateMessage = settingsInitialState.confirmRegenerateMessage +// } +// return state +// } catch (error) { +// logger.error('migrate 144 error', error as Error) +// return state +// } +// } +// } + +// // 注意:添加新迁移时,记得同时更新 persistReducer +// // file://./index.ts + +// const migrate = createMigrate(migrateConfig as any) + +// export default migrate diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 41b2367d81..06e175ee3b 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -1,27 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { isMac } from '@renderer/config/constant' -import { - ApiServerConfig, - CodeStyleVarious, - MathEngine, - OpenAIServiceTier, - OpenAISummaryText, - PaintingProvider, - S3Config, - TranslateLanguageCode -} from '@renderer/types' +import { ApiServerConfig, OpenAIServiceTier, OpenAISummaryText, PaintingProvider } from '@renderer/types' import { uuid } from '@renderer/utils' -import { TRANSLATE_PROMPT } from '@shared/config/prompts' -import { DefaultPreferences } from '@shared/data/preferences' -import type { - AssistantIconType, - AssistantTabSortType, - LanguageVarious, - SendMessageShortcut, - SidebarIcon -} from '@shared/data/preferenceTypes' -import { UpgradeChannel } from '@shared/data/preferenceTypes' -import { ThemeMode } from '@shared/data/preferenceTypes' import { OpenAIVerbosity } from '@types' import { RemoteSyncState } from './backup' @@ -40,159 +19,159 @@ export type UserTheme = { } export interface SettingsState { - showAssistants: boolean - showTopics: boolean - assistantsTabSortType: AssistantTabSortType - sendMessageShortcut: SendMessageShortcut - language: LanguageVarious - targetLanguage: TranslateLanguageCode - proxyMode: 'system' | 'custom' | 'none' - proxyUrl?: string - proxyBypassRules?: string - userName: string - userId: string - showPrompt: boolean - showMessageDivider: boolean - messageFont: 'system' | 'serif' - showInputEstimatedTokens: boolean - launchOnBoot: boolean - launchToTray: boolean - trayOnClose: boolean - tray: boolean - theme: ThemeMode - userTheme: { - colorPrimary: string - } - windowStyle: 'transparent' | 'opaque' - fontSize: number - topicPosition: 'left' | 'right' - showTopicTime: boolean - pinTopicsToTop: boolean - assistantIconType: AssistantIconType - pasteLongTextAsFile: boolean - pasteLongTextThreshold: number - clickAssistantToShowTopic: boolean - autoCheckUpdate: boolean - testPlan: boolean - testChannel: UpgradeChannel - renderInputMessageAsMarkdown: boolean - // 代码执行 - codeExecution: { - enabled: boolean - timeoutMinutes: number - } - codeEditor: { - enabled: boolean - themeLight: string - themeDark: string - highlightActiveLine: boolean - foldGutter: boolean - autocompletion: boolean - keymap: boolean - } - /** @deprecated use codeViewer instead */ - codePreview: { - themeLight: CodeStyleVarious - themeDark: CodeStyleVarious - } - codeViewer: { - themeLight: CodeStyleVarious - themeDark: CodeStyleVarious - } - codeShowLineNumbers: boolean - codeCollapsible: boolean - codeWrappable: boolean - codeImageTools: boolean - mathEngine: MathEngine - mathEnableSingleDollar: boolean - messageStyle: 'plain' | 'bubble' - foldDisplayMode: 'expanded' | 'compact' - gridColumns: number - gridPopoverTrigger: 'hover' | 'click' - messageNavigation: 'none' | 'buttons' | 'anchor' - // 数据目录设置 - skipBackupFile: boolean - // webdav 配置 host, user, pass, path - webdavHost: string - webdavUser: string - webdavPass: string - webdavPath: string - webdavAutoSync: boolean - webdavSyncInterval: number - webdavMaxBackups: number - webdavSkipBackupFile: boolean - webdavDisableStream: boolean - translateModelPrompt: string - autoTranslateWithSpace: boolean - showTranslateConfirm: boolean - enableTopicNaming: boolean - customCss: string - topicNamingPrompt: string - // 消息操作确认设置 - confirmDeleteMessage: boolean - confirmRegenerateMessage: boolean - // Sidebar icons - sidebarIcons: { - visible: SidebarIcon[] - disabled: SidebarIcon[] - } - narrowMode: boolean - // QuickAssistant - enableQuickAssistant: boolean - clickTrayToShowQuickAssistant: boolean - multiModelMessageStyle: MultiModelMessageStyle - readClipboardAtStartup: boolean - notionDatabaseID: string | null - notionApiKey: string | null - notionPageNameKey: string | null - markdownExportPath: string | null - forceDollarMathInMarkdown: boolean - useTopicNamingForMessageTitle: boolean - showModelNameInMarkdown: boolean - showModelProviderInMarkdown: boolean - thoughtAutoCollapse: boolean - notionExportReasoning: boolean - excludeCitationsInExport: boolean - standardizeCitationsInExport: boolean - yuqueToken: string | null - yuqueUrl: string | null - yuqueRepoId: string | null - joplinToken: string | null - joplinUrl: string | null - joplinExportReasoning: boolean - defaultObsidianVault: string | null + // showAssistants: boolean + // showTopics: boolean + // assistantsTabSortType: AssistantTabSortType + // sendMessageShortcut: SendMessageShortcut + // language: LanguageVarious + // targetLanguage: TranslateLanguageCode + // proxyMode: 'system' | 'custom' | 'none' + // proxyUrl?: string + // proxyBypassRules?: string + // userName: string + // userId: string + // showPrompt: boolean + // showMessageDivider: boolean + // messageFont: 'system' | 'serif' + // showInputEstimatedTokens: boolean + // launchOnBoot: boolean + // launchToTray: boolean + // trayOnClose: boolean + // tray: boolean + // theme: ThemeMode + // userTheme: { + // colorPrimary: string + // } + // windowStyle: 'transparent' | 'opaque' + // fontSize: number + // topicPosition: 'left' | 'right' + // showTopicTime: boolean + // pinTopicsToTop: boolean + // assistantIconType: AssistantIconType + // pasteLongTextAsFile: boolean + // pasteLongTextThreshold: number + // clickAssistantToShowTopic: boolean + // autoCheckUpdate: boolean + // testPlan: boolean + // testChannel: UpgradeChannel + // renderInputMessageAsMarkdown: boolean + // // 代码执行 + // codeExecution: { + // enabled: boolean + // timeoutMinutes: number + // } + // codeEditor: { + // enabled: boolean + // themeLight: string + // themeDark: string + // highlightActiveLine: boolean + // foldGutter: boolean + // autocompletion: boolean + // keymap: boolean + // } + // /** @deprecated use codeViewer instead */ + // codePreview: { + // themeLight: CodeStyleVarious + // themeDark: CodeStyleVarious + // } + // codeViewer: { + // themeLight: CodeStyleVarious + // themeDark: CodeStyleVarious + // } + // codeShowLineNumbers: boolean + // codeCollapsible: boolean + // codeWrappable: boolean + // codeImageTools: boolean + // mathEngine: MathEngine + // mathEnableSingleDollar: boolean + // messageStyle: 'plain' | 'bubble' + // foldDisplayMode: 'expanded' | 'compact' + // gridColumns: number + // gridPopoverTrigger: 'hover' | 'click' + // messageNavigation: 'none' | 'buttons' | 'anchor' + // // 数据目录设置 + // skipBackupFile: boolean + // // webdav 配置 host, user, pass, path + // webdavHost: string + // webdavUser: string + // webdavPass: string + // webdavPath: string + // webdavAutoSync: boolean + // webdavSyncInterval: number + // webdavMaxBackups: number + // webdavSkipBackupFile: boolean + // webdavDisableStream: boolean + // translateModelPrompt: string + // autoTranslateWithSpace: boolean + // showTranslateConfirm: boolean + // enableTopicNaming: boolean + // customCss: string + // topicNamingPrompt: string + // // 消息操作确认设置 + // confirmDeleteMessage: boolean + // confirmRegenerateMessage: boolean + // // Sidebar icons + // sidebarIcons: { + // visible: SidebarIcon[] + // disabled: SidebarIcon[] + // } + // narrowMode: boolean + // // QuickAssistant + // enableQuickAssistant: boolean + // clickTrayToShowQuickAssistant: boolean + // multiModelMessageStyle: MultiModelMessageStyle + // readClipboardAtStartup: boolean + // notionDatabaseID: string | null + // notionApiKey: string | null + // notionPageNameKey: string | null + // markdownExportPath: string | null + // forceDollarMathInMarkdown: boolean + // useTopicNamingForMessageTitle: boolean + // showModelNameInMarkdown: boolean + // showModelProviderInMarkdown: boolean + // thoughtAutoCollapse: boolean + // notionExportReasoning: boolean + // excludeCitationsInExport: boolean + // standardizeCitationsInExport: boolean + // yuqueToken: string | null + // yuqueUrl: string | null + // yuqueRepoId: string | null + // joplinToken: string | null + // joplinUrl: string | null + // joplinExportReasoning: boolean + // defaultObsidianVault: string | null defaultAgent: string | null - // 思源笔记配置 - siyuanApiUrl: string | null - siyuanToken: string | null - siyuanBoxId: string | null - siyuanRootPath: string | null + // // 思源笔记配置 + // siyuanApiUrl: string | null + // siyuanToken: string | null + // siyuanBoxId: string | null + // siyuanRootPath: string | null // 订阅的助手地址 agentssubscribeUrl: string | null // MinApps maxKeepAliveMinapps: number showOpenedMinappsInSidebar: boolean minappsOpenLinkExternal: boolean - // 隐私设置 - enableDataCollection: boolean - enableSpellCheck: boolean - spellCheckLanguages: string[] - enableQuickPanelTriggers: boolean - // 硬件加速设置 - disableHardwareAcceleration: boolean - exportMenuOptions: { - image: boolean - markdown: boolean - markdown_reason: boolean - notion: boolean - yuque: boolean - joplin: boolean - obsidian: boolean - siyuan: boolean - docx: boolean - plain_text: boolean - notes: boolean - } + // // 隐私设置 + // enableDataCollection: boolean + // enableSpellCheck: boolean + // spellCheckLanguages: string[] + // enableQuickPanelTriggers: boolean + // // 硬件加速设置 + // disableHardwareAcceleration: boolean + // exportMenuOptions: { + // image: boolean + // markdown: boolean + // markdown_reason: boolean + // notion: boolean + // yuque: boolean + // joplin: boolean + // obsidian: boolean + // siyuan: boolean + // docx: boolean + // plain_text: boolean + // notes: boolean + // } // OpenAI openAI: { summaryText: OpenAISummaryText @@ -200,24 +179,24 @@ export interface SettingsState { serviceTier: OpenAIServiceTier verbosity: OpenAIVerbosity } - // Notification - notification: { - assistant: boolean - backup: boolean - knowledge: boolean - } - // Local backup settings - localBackupDir: string - localBackupAutoSync: boolean - localBackupSyncInterval: number - localBackupMaxBackups: number - localBackupSkipBackupFile: boolean + // // Notification + // notification: { + // assistant: boolean + // backup: boolean + // knowledge: boolean + // } + // // Local backup settings + // localBackupDir: string + // localBackupAutoSync: boolean + // localBackupSyncInterval: number + // localBackupMaxBackups: number + // localBackupSkipBackupFile: boolean defaultPaintingProvider: PaintingProvider - s3: S3Config + // s3: S3Config // Developer mode - enableDeveloperMode: boolean - // UI - navbarPosition: 'left' | 'top' + // enableDeveloperMode: boolean + // // UI + // navbarPosition: 'left' | 'top' // API Server apiServer: ApiServerConfig showMessageOutline?: boolean @@ -225,189 +204,189 @@ export interface SettingsState { showWorkspace: boolean } -export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' +// export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' export const initialState: SettingsState = { - showAssistants: true, - showTopics: true, - assistantsTabSortType: 'list', - sendMessageShortcut: 'Enter', - language: navigator.language as LanguageVarious, - targetLanguage: 'en-us', - proxyMode: 'system', - proxyUrl: undefined, - proxyBypassRules: undefined, - userName: '', - userId: uuid(), - showPrompt: true, - showMessageDivider: true, - messageFont: 'system', - showInputEstimatedTokens: false, - launchOnBoot: false, - launchToTray: false, - trayOnClose: true, - tray: true, - theme: ThemeMode.system, - userTheme: { - colorPrimary: '#00b96b' - }, - windowStyle: isMac ? 'transparent' : 'opaque', - fontSize: 14, - topicPosition: 'left', - showTopicTime: false, - pinTopicsToTop: false, - assistantIconType: 'emoji', - pasteLongTextAsFile: false, - pasteLongTextThreshold: 1500, - clickAssistantToShowTopic: true, - autoCheckUpdate: true, - testPlan: false, - testChannel: UpgradeChannel.LATEST, - renderInputMessageAsMarkdown: false, - codeExecution: { - enabled: false, - timeoutMinutes: 1 - }, - codeEditor: { - enabled: false, - themeLight: 'auto', - themeDark: 'auto', - highlightActiveLine: false, - foldGutter: false, - autocompletion: true, - keymap: false - }, - /** @deprecated use codeViewer instead */ - codePreview: { - themeLight: 'auto', - themeDark: 'auto' - }, - codeViewer: { - themeLight: 'auto', - themeDark: 'auto' - }, - codeShowLineNumbers: false, - codeCollapsible: false, - codeWrappable: false, - codeImageTools: false, - mathEngine: 'KaTeX', - mathEnableSingleDollar: true, - messageStyle: 'plain', - foldDisplayMode: 'expanded', - gridColumns: 2, - gridPopoverTrigger: 'click', - messageNavigation: 'none', - skipBackupFile: false, - webdavHost: '', - webdavUser: '', - webdavPass: '', - webdavPath: '/cherry-studio', - webdavAutoSync: false, - webdavSyncInterval: 0, - webdavMaxBackups: 0, - webdavSkipBackupFile: false, - webdavDisableStream: false, - translateModelPrompt: TRANSLATE_PROMPT, - autoTranslateWithSpace: false, - showTranslateConfirm: true, - enableTopicNaming: true, - customCss: '', - topicNamingPrompt: '', - sidebarIcons: { - visible: DefaultPreferences.default['ui.sidebar.icons.visible'], - disabled: [] - }, - narrowMode: false, - enableQuickAssistant: false, - clickTrayToShowQuickAssistant: false, - readClipboardAtStartup: true, - multiModelMessageStyle: 'horizontal', - notionDatabaseID: '', - notionApiKey: '', - notionPageNameKey: 'Name', - markdownExportPath: null, - forceDollarMathInMarkdown: false, - useTopicNamingForMessageTitle: false, - showModelNameInMarkdown: false, - showModelProviderInMarkdown: false, - thoughtAutoCollapse: true, - notionExportReasoning: false, - excludeCitationsInExport: false, - standardizeCitationsInExport: false, - yuqueToken: '', - yuqueUrl: '', - yuqueRepoId: '', - joplinToken: '', - joplinUrl: '', - joplinExportReasoning: false, - defaultObsidianVault: null, + // showAssistants: true, + // showTopics: true, + // assistantsTabSortType: 'list', + // sendMessageShortcut: 'Enter', + // language: navigator.language as LanguageVarious, + // targetLanguage: 'en-us', + // proxyMode: 'system', + // proxyUrl: undefined, + // proxyBypassRules: undefined, + // userName: '', + // userId: uuid(), + // showPrompt: true, + // showMessageDivider: true, + // messageFont: 'system', + // showInputEstimatedTokens: false, + // launchOnBoot: false, + // launchToTray: false, + // trayOnClose: true, + // tray: true, + // theme: ThemeMode.system, + // userTheme: { + // colorPrimary: '#00b96b' + // }, + // windowStyle: isMac ? 'transparent' : 'opaque', + // fontSize: 14, + // topicPosition: 'left', + // showTopicTime: false, + // pinTopicsToTop: false, + // assistantIconType: 'emoji', + // pasteLongTextAsFile: false, + // pasteLongTextThreshold: 1500, + // clickAssistantToShowTopic: true, + // autoCheckUpdate: true, + // testPlan: false, + // testChannel: UpgradeChannel.LATEST, + // renderInputMessageAsMarkdown: false, + // codeExecution: { + // enabled: false, + // timeoutMinutes: 1 + // }, + // codeEditor: { + // enabled: false, + // themeLight: 'auto', + // themeDark: 'auto', + // highlightActiveLine: false, + // foldGutter: false, + // autocompletion: true, + // keymap: false + // }, + // /** @deprecated use codeViewer instead */ + // codePreview: { + // themeLight: 'auto', + // themeDark: 'auto' + // }, + // codeViewer: { + // themeLight: 'auto', + // themeDark: 'auto' + // }, + // codeShowLineNumbers: false, + // codeCollapsible: false, + // codeWrappable: false, + // codeImageTools: false, + // mathEngine: 'KaTeX', + // mathEnableSingleDollar: true, + // messageStyle: 'plain', + // foldDisplayMode: 'expanded', + // gridColumns: 2, + // gridPopoverTrigger: 'click', + // messageNavigation: 'none', + // skipBackupFile: false, + // webdavHost: '', + // webdavUser: '', + // webdavPass: '', + // webdavPath: '/cherry-studio', + // webdavAutoSync: false, + // webdavSyncInterval: 0, + // webdavMaxBackups: 0, + // webdavSkipBackupFile: false, + // webdavDisableStream: false, + // translateModelPrompt: TRANSLATE_PROMPT, + // autoTranslateWithSpace: false, + // showTranslateConfirm: true, + // enableTopicNaming: true, + // customCss: '', + // topicNamingPrompt: '', + // sidebarIcons: { + // visible: DefaultPreferences.default['ui.sidebar.icons.visible'], + // disabled: [] + // }, + // narrowMode: false, + // enableQuickAssistant: false, + // clickTrayToShowQuickAssistant: false, + // readClipboardAtStartup: true, + // multiModelMessageStyle: 'horizontal', + // notionDatabaseID: '', + // notionApiKey: '', + // notionPageNameKey: 'Name', + // markdownExportPath: null, + // forceDollarMathInMarkdown: false, + // useTopicNamingForMessageTitle: false, + // showModelNameInMarkdown: false, + // showModelProviderInMarkdown: false, + // thoughtAutoCollapse: true, + // notionExportReasoning: false, + // excludeCitationsInExport: false, + // standardizeCitationsInExport: false, + // yuqueToken: '', + // yuqueUrl: '', + // yuqueRepoId: '', + // joplinToken: '', + // joplinUrl: '', + // joplinExportReasoning: false, + // defaultObsidianVault: null, defaultAgent: null, - siyuanApiUrl: null, - siyuanToken: null, - siyuanBoxId: null, - siyuanRootPath: null, + // siyuanApiUrl: null, + // siyuanToken: null, + // siyuanBoxId: null, + // siyuanRootPath: null, agentssubscribeUrl: '', // MinApps maxKeepAliveMinapps: 3, showOpenedMinappsInSidebar: true, minappsOpenLinkExternal: false, - enableDataCollection: false, - enableSpellCheck: false, - spellCheckLanguages: [], - enableQuickPanelTriggers: false, - // 消息操作确认设置 - confirmDeleteMessage: true, - confirmRegenerateMessage: true, - // 硬件加速设置 - disableHardwareAcceleration: false, - exportMenuOptions: { - image: true, - markdown: true, - markdown_reason: true, - notion: true, - yuque: true, - joplin: true, - obsidian: true, - siyuan: true, - docx: true, - plain_text: true, - notes: true - }, + // enableDataCollection: false, + // enableSpellCheck: false, + // spellCheckLanguages: [], + // enableQuickPanelTriggers: false, + // // 消息操作确认设置 + // confirmDeleteMessage: true, + // confirmRegenerateMessage: true, + // // 硬件加速设置 + // disableHardwareAcceleration: false, + // exportMenuOptions: { + // image: true, + // markdown: true, + // markdown_reason: true, + // notion: true, + // yuque: true, + // joplin: true, + // obsidian: true, + // siyuan: true, + // docx: true, + // plain_text: true, + // notes: true + // }, // OpenAI openAI: { summaryText: 'off', serviceTier: 'auto', verbosity: 'medium' }, - notification: { - assistant: false, - backup: false, - knowledge: false - }, - // Local backup settings - localBackupDir: '', - localBackupAutoSync: false, - localBackupSyncInterval: 0, - localBackupMaxBackups: 0, - localBackupSkipBackupFile: false, + // notification: { + // assistant: false, + // backup: false, + // knowledge: false + // }, + // // Local backup settings + // localBackupDir: '', + // localBackupAutoSync: false, + // localBackupSyncInterval: 0, + // localBackupMaxBackups: 0, + // localBackupSkipBackupFile: false, defaultPaintingProvider: 'zhipu', - s3: { - endpoint: '', - region: '', - bucket: '', - accessKeyId: '', - secretAccessKey: '', - root: '', - autoSync: false, - syncInterval: 0, - maxBackups: 0, - skipBackupFile: false - }, + // s3: { + // endpoint: '', + // region: '', + // bucket: '', + // accessKeyId: '', + // secretAccessKey: '', + // root: '', + // autoSync: false, + // syncInterval: 0, + // maxBackups: 0, + // skipBackupFile: false + // }, - // Developer mode - enableDeveloperMode: false, - // UI - navbarPosition: 'top', + // // Developer mode + // enableDeveloperMode: false, + // // UI + // navbarPosition: 'top', // API Server apiServer: { enabled: false, @@ -415,7 +394,7 @@ export const initialState: SettingsState = { port: 23333, apiKey: `cs-sk-${uuid()}` }, - showMessageOutline: undefined, + // showMessageOutline: undefined, // Notes Related showWorkspace: true } @@ -424,438 +403,438 @@ const settingsSlice = createSlice({ name: 'settings', initialState, reducers: { - setShowAssistants: (state, action: PayloadAction) => { - state.showAssistants = action.payload - }, - toggleShowAssistants: (state) => { - state.showAssistants = !state.showAssistants - }, - setShowTopics: (state, action: PayloadAction) => { - state.showTopics = action.payload - }, - toggleShowTopics: (state) => { - state.showTopics = !state.showTopics - }, - setAssistantsTabSortType: (state, action: PayloadAction) => { - state.assistantsTabSortType = action.payload - }, - setSendMessageShortcut: (state, action: PayloadAction) => { - state.sendMessageShortcut = action.payload - }, - setLanguage: (state, action: PayloadAction) => { - state.language = action.payload - }, - setTargetLanguage: (state, action: PayloadAction) => { - state.targetLanguage = action.payload - }, - setProxyMode: (state, action: PayloadAction<'system' | 'custom' | 'none'>) => { - state.proxyMode = action.payload - }, - setProxyUrl: (state, action: PayloadAction) => { - state.proxyUrl = action.payload - }, - setProxyBypassRules: (state, action: PayloadAction) => { - state.proxyBypassRules = action.payload - }, - setUserName: (state, action: PayloadAction) => { - state.userName = action.payload - }, - setShowPrompt: (state, action: PayloadAction) => { - state.showPrompt = action.payload - }, - setShowMessageDivider: (state, action: PayloadAction) => { - state.showMessageDivider = action.payload - }, - setMessageFont: (state, action: PayloadAction<'system' | 'serif'>) => { - state.messageFont = action.payload - }, - setShowInputEstimatedTokens: (state, action: PayloadAction) => { - state.showInputEstimatedTokens = action.payload - }, - setLaunchOnBoot: (state, action: PayloadAction) => { - state.launchOnBoot = action.payload - }, - setLaunchToTray: (state, action: PayloadAction) => { - state.launchToTray = action.payload - }, - setTray: (state, action: PayloadAction) => { - state.tray = action.payload - }, - setTrayOnClose: (state, action: PayloadAction) => { - state.trayOnClose = action.payload - }, - setTheme: (state, action: PayloadAction) => { - state.theme = action.payload - }, - setCustomCss: (state, action: PayloadAction) => { - state.customCss = action.payload - }, - setUserTheme: (state, action: PayloadAction) => { - state.userTheme = action.payload - }, - setFontSize: (state, action: PayloadAction) => { - state.fontSize = action.payload - }, - setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => { - state.windowStyle = action.payload - }, - setTopicPosition: (state, action: PayloadAction<'left' | 'right'>) => { - state.topicPosition = action.payload - }, - setShowTopicTime: (state, action: PayloadAction) => { - state.showTopicTime = action.payload - }, - setPinTopicsToTop: (state, action: PayloadAction) => { - state.pinTopicsToTop = action.payload - }, - setAssistantIconType: (state, action: PayloadAction) => { - state.assistantIconType = action.payload - }, - setPasteLongTextAsFile: (state, action: PayloadAction) => { - state.pasteLongTextAsFile = action.payload - }, - setAutoCheckUpdate: (state, action: PayloadAction) => { - state.autoCheckUpdate = action.payload - }, - setTestPlan: (state, action: PayloadAction) => { - state.testPlan = action.payload - }, - setTestChannel: (state, action: PayloadAction) => { - state.testChannel = action.payload - }, - setRenderInputMessageAsMarkdown: (state, action: PayloadAction) => { - state.renderInputMessageAsMarkdown = action.payload - }, - setClickAssistantToShowTopic: (state, action: PayloadAction) => { - state.clickAssistantToShowTopic = action.payload - }, - setSkipBackupFile: (state, action: PayloadAction) => { - state.skipBackupFile = action.payload - }, - setWebdavHost: (state, action: PayloadAction) => { - state.webdavHost = action.payload - }, - setWebdavUser: (state, action: PayloadAction) => { - state.webdavUser = action.payload - }, - setWebdavPass: (state, action: PayloadAction) => { - state.webdavPass = action.payload - }, - setWebdavPath: (state, action: PayloadAction) => { - state.webdavPath = action.payload - }, - setWebdavAutoSync: (state, action: PayloadAction) => { - state.webdavAutoSync = action.payload - }, - setWebdavSyncInterval: (state, action: PayloadAction) => { - state.webdavSyncInterval = action.payload - }, - setWebdavMaxBackups: (state, action: PayloadAction) => { - state.webdavMaxBackups = action.payload - }, - setWebdavSkipBackupFile: (state, action: PayloadAction) => { - state.webdavSkipBackupFile = action.payload - }, - setWebdavDisableStream: (state, action: PayloadAction) => { - state.webdavDisableStream = action.payload - }, - setCodeExecution: (state, action: PayloadAction<{ enabled?: boolean; timeoutMinutes?: number }>) => { - if (action.payload.enabled !== undefined) { - state.codeExecution.enabled = action.payload.enabled - } - if (action.payload.timeoutMinutes !== undefined) { - state.codeExecution.timeoutMinutes = action.payload.timeoutMinutes - } - }, - setCodeEditor: ( - state, - action: PayloadAction<{ - enabled?: boolean - themeLight?: string - themeDark?: string - highlightActiveLine?: boolean - foldGutter?: boolean - autocompletion?: boolean - keymap?: boolean - }> - ) => { - if (action.payload.enabled !== undefined) { - state.codeEditor.enabled = action.payload.enabled - } - if (action.payload.themeLight !== undefined) { - state.codeEditor.themeLight = action.payload.themeLight - } - if (action.payload.themeDark !== undefined) { - state.codeEditor.themeDark = action.payload.themeDark - } - if (action.payload.highlightActiveLine !== undefined) { - state.codeEditor.highlightActiveLine = action.payload.highlightActiveLine - } - if (action.payload.foldGutter !== undefined) { - state.codeEditor.foldGutter = action.payload.foldGutter - } - if (action.payload.autocompletion !== undefined) { - state.codeEditor.autocompletion = action.payload.autocompletion - } - if (action.payload.keymap !== undefined) { - state.codeEditor.keymap = action.payload.keymap - } - }, - setCodeViewer: (state, action: PayloadAction<{ themeLight?: string; themeDark?: string }>) => { - if (action.payload.themeLight !== undefined) { - state.codeViewer.themeLight = action.payload.themeLight - } - if (action.payload.themeDark !== undefined) { - state.codeViewer.themeDark = action.payload.themeDark - } - }, - setCodeShowLineNumbers: (state, action: PayloadAction) => { - state.codeShowLineNumbers = action.payload - }, - setCodeCollapsible: (state, action: PayloadAction) => { - state.codeCollapsible = action.payload - }, - setCodeWrappable: (state, action: PayloadAction) => { - state.codeWrappable = action.payload - }, - setCodeImageTools: (state, action: PayloadAction) => { - state.codeImageTools = action.payload - }, - setMathEngine: (state, action: PayloadAction) => { - state.mathEngine = action.payload - }, - setMathEnableSingleDollar: (state, action: PayloadAction) => { - state.mathEnableSingleDollar = action.payload - }, - setFoldDisplayMode: (state, action: PayloadAction<'expanded' | 'compact'>) => { - state.foldDisplayMode = action.payload - }, - setGridColumns: (state, action: PayloadAction) => { - state.gridColumns = action.payload - }, - setGridPopoverTrigger: (state, action: PayloadAction<'hover' | 'click'>) => { - state.gridPopoverTrigger = action.payload - }, - setMessageStyle: (state, action: PayloadAction<'plain' | 'bubble'>) => { - state.messageStyle = action.payload - }, - setTranslateModelPrompt: (state, action: PayloadAction) => { - state.translateModelPrompt = action.payload - }, - setAutoTranslateWithSpace: (state, action: PayloadAction) => { - state.autoTranslateWithSpace = action.payload - }, - setShowTranslateConfirm: (state, action: PayloadAction) => { - state.showTranslateConfirm = action.payload - }, - setEnableTopicNaming: (state, action: PayloadAction) => { - state.enableTopicNaming = action.payload - }, - setPasteLongTextThreshold: (state, action: PayloadAction) => { - state.pasteLongTextThreshold = action.payload - }, - setTopicNamingPrompt: (state, action: PayloadAction) => { - state.topicNamingPrompt = action.payload - }, - setSidebarIcons: (state, action: PayloadAction<{ visible?: SidebarIcon[]; disabled?: SidebarIcon[] }>) => { - if (action.payload.visible) { - state.sidebarIcons.visible = action.payload.visible - } - if (action.payload.disabled) { - state.sidebarIcons.disabled = action.payload.disabled - } - }, - setNarrowMode: (state, action: PayloadAction) => { - state.narrowMode = action.payload - }, - setClickTrayToShowQuickAssistant: (state, action: PayloadAction) => { - state.clickTrayToShowQuickAssistant = action.payload - }, - setEnableQuickAssistant: (state, action: PayloadAction) => { - state.enableQuickAssistant = action.payload - }, - setReadClipboardAtStartup: (state, action: PayloadAction) => { - state.readClipboardAtStartup = action.payload - }, - setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold' | 'grid'>) => { - state.multiModelMessageStyle = action.payload - }, - setNotionDatabaseID: (state, action: PayloadAction) => { - state.notionDatabaseID = action.payload - }, - setNotionApiKey: (state, action: PayloadAction) => { - state.notionApiKey = action.payload - }, - setNotionPageNameKey: (state, action: PayloadAction) => { - state.notionPageNameKey = action.payload - }, - setmarkdownExportPath: (state, action: PayloadAction) => { - state.markdownExportPath = action.payload - }, - setForceDollarMathInMarkdown: (state, action: PayloadAction) => { - state.forceDollarMathInMarkdown = action.payload - }, - setUseTopicNamingForMessageTitle: (state, action: PayloadAction) => { - state.useTopicNamingForMessageTitle = action.payload - }, - setShowModelNameInMarkdown: (state, action: PayloadAction) => { - state.showModelNameInMarkdown = action.payload - }, - setShowModelProviderInMarkdown: (state, action: PayloadAction) => { - state.showModelProviderInMarkdown = action.payload - }, - setThoughtAutoCollapse: (state, action: PayloadAction) => { - state.thoughtAutoCollapse = action.payload - }, - setNotionExportReasoning: (state, action: PayloadAction) => { - state.notionExportReasoning = action.payload - }, - setExcludeCitationsInExport: (state, action: PayloadAction) => { - state.excludeCitationsInExport = action.payload - }, - setStandardizeCitationsInExport: (state, action: PayloadAction) => { - state.standardizeCitationsInExport = action.payload - }, - setYuqueToken: (state, action: PayloadAction) => { - state.yuqueToken = action.payload - }, - setYuqueRepoId: (state, action: PayloadAction) => { - state.yuqueRepoId = action.payload - }, - setYuqueUrl: (state, action: PayloadAction) => { - state.yuqueUrl = action.payload - }, - setJoplinToken: (state, action: PayloadAction) => { - state.joplinToken = action.payload - }, - setJoplinUrl: (state, action: PayloadAction) => { - state.joplinUrl = action.payload - }, - setJoplinExportReasoning: (state, action: PayloadAction) => { - state.joplinExportReasoning = action.payload - }, - setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => { - state.messageNavigation = action.payload - }, - setDefaultObsidianVault: (state, action: PayloadAction) => { - state.defaultObsidianVault = action.payload - }, + // setShowAssistants: (state, action: PayloadAction) => { + // state.showAssistants = action.payload + // }, + // toggleShowAssistants: (state) => { + // state.showAssistants = !state.showAssistants + // }, + // setShowTopics: (state, action: PayloadAction) => { + // state.showTopics = action.payload + // }, + // toggleShowTopics: (state) => { + // state.showTopics = !state.showTopics + // }, + // setAssistantsTabSortType: (state, action: PayloadAction) => { + // state.assistantsTabSortType = action.payload + // }, + // setSendMessageShortcut: (state, action: PayloadAction) => { + // state.sendMessageShortcut = action.payload + // }, + // setLanguage: (state, action: PayloadAction) => { + // state.language = action.payload + // }, + // setTargetLanguage: (state, action: PayloadAction) => { + // state.targetLanguage = action.payload + // }, + // setProxyMode: (state, action: PayloadAction<'system' | 'custom' | 'none'>) => { + // state.proxyMode = action.payload + // }, + // setProxyUrl: (state, action: PayloadAction) => { + // state.proxyUrl = action.payload + // }, + // setProxyBypassRules: (state, action: PayloadAction) => { + // state.proxyBypassRules = action.payload + // }, + // setUserName: (state, action: PayloadAction) => { + // state.userName = action.payload + // }, + // setShowPrompt: (state, action: PayloadAction) => { + // state.showPrompt = action.payload + // }, + // setShowMessageDivider: (state, action: PayloadAction) => { + // state.showMessageDivider = action.payload + // }, + // setMessageFont: (state, action: PayloadAction<'system' | 'serif'>) => { + // state.messageFont = action.payload + // }, + // setShowInputEstimatedTokens: (state, action: PayloadAction) => { + // state.showInputEstimatedTokens = action.payload + // }, + // setLaunchOnBoot: (state, action: PayloadAction) => { + // state.launchOnBoot = action.payload + // }, + // setLaunchToTray: (state, action: PayloadAction) => { + // state.launchToTray = action.payload + // }, + // setTray: (state, action: PayloadAction) => { + // state.tray = action.payload + // }, + // setTrayOnClose: (state, action: PayloadAction) => { + // state.trayOnClose = action.payload + // }, + // setTheme: (state, action: PayloadAction) => { + // state.theme = action.payload + // }, + // setCustomCss: (state, action: PayloadAction) => { + // state.customCss = action.payload + // }, + // setUserTheme: (state, action: PayloadAction) => { + // state.userTheme = action.payload + // }, + // setFontSize: (state, action: PayloadAction) => { + // state.fontSize = action.payload + // }, + // setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => { + // state.windowStyle = action.payload + // }, + // setTopicPosition: (state, action: PayloadAction<'left' | 'right'>) => { + // state.topicPosition = action.payload + // }, + // setShowTopicTime: (state, action: PayloadAction) => { + // state.showTopicTime = action.payload + // }, + // setPinTopicsToTop: (state, action: PayloadAction) => { + // state.pinTopicsToTop = action.payload + // }, + // setAssistantIconType: (state, action: PayloadAction) => { + // state.assistantIconType = action.payload + // }, + // setPasteLongTextAsFile: (state, action: PayloadAction) => { + // state.pasteLongTextAsFile = action.payload + // }, + // setAutoCheckUpdate: (state, action: PayloadAction) => { + // state.autoCheckUpdate = action.payload + // }, + // setTestPlan: (state, action: PayloadAction) => { + // state.testPlan = action.payload + // }, + // setTestChannel: (state, action: PayloadAction) => { + // state.testChannel = action.payload + // }, + // setRenderInputMessageAsMarkdown: (state, action: PayloadAction) => { + // state.renderInputMessageAsMarkdown = action.payload + // }, + // setClickAssistantToShowTopic: (state, action: PayloadAction) => { + // state.clickAssistantToShowTopic = action.payload + // }, + // setSkipBackupFile: (state, action: PayloadAction) => { + // state.skipBackupFile = action.payload + // }, + // setWebdavHost: (state, action: PayloadAction) => { + // state.webdavHost = action.payload + // }, + // setWebdavUser: (state, action: PayloadAction) => { + // state.webdavUser = action.payload + // }, + // setWebdavPass: (state, action: PayloadAction) => { + // state.webdavPass = action.payload + // }, + // setWebdavPath: (state, action: PayloadAction) => { + // state.webdavPath = action.payload + // }, + // setWebdavAutoSync: (state, action: PayloadAction) => { + // state.webdavAutoSync = action.payload + // }, + // setWebdavSyncInterval: (state, action: PayloadAction) => { + // state.webdavSyncInterval = action.payload + // }, + // setWebdavMaxBackups: (state, action: PayloadAction) => { + // state.webdavMaxBackups = action.payload + // }, + // setWebdavSkipBackupFile: (state, action: PayloadAction) => { + // state.webdavSkipBackupFile = action.payload + // }, + // setWebdavDisableStream: (state, action: PayloadAction) => { + // state.webdavDisableStream = action.payload + // }, + // setCodeExecution: (state, action: PayloadAction<{ enabled?: boolean; timeoutMinutes?: number }>) => { + // if (action.payload.enabled !== undefined) { + // state.codeExecution.enabled = action.payload.enabled + // } + // if (action.payload.timeoutMinutes !== undefined) { + // state.codeExecution.timeoutMinutes = action.payload.timeoutMinutes + // } + // }, + // setCodeEditor: ( + // state, + // action: PayloadAction<{ + // enabled?: boolean + // themeLight?: string + // themeDark?: string + // highlightActiveLine?: boolean + // foldGutter?: boolean + // autocompletion?: boolean + // keymap?: boolean + // }> + // ) => { + // if (action.payload.enabled !== undefined) { + // state.codeEditor.enabled = action.payload.enabled + // } + // if (action.payload.themeLight !== undefined) { + // state.codeEditor.themeLight = action.payload.themeLight + // } + // if (action.payload.themeDark !== undefined) { + // state.codeEditor.themeDark = action.payload.themeDark + // } + // if (action.payload.highlightActiveLine !== undefined) { + // state.codeEditor.highlightActiveLine = action.payload.highlightActiveLine + // } + // if (action.payload.foldGutter !== undefined) { + // state.codeEditor.foldGutter = action.payload.foldGutter + // } + // if (action.payload.autocompletion !== undefined) { + // state.codeEditor.autocompletion = action.payload.autocompletion + // } + // if (action.payload.keymap !== undefined) { + // state.codeEditor.keymap = action.payload.keymap + // } + // }, + // setCodeViewer: (state, action: PayloadAction<{ themeLight?: string; themeDark?: string }>) => { + // if (action.payload.themeLight !== undefined) { + // state.codeViewer.themeLight = action.payload.themeLight + // } + // if (action.payload.themeDark !== undefined) { + // state.codeViewer.themeDark = action.payload.themeDark + // } + // }, + // setCodeShowLineNumbers: (state, action: PayloadAction) => { + // state.codeShowLineNumbers = action.payload + // }, + // setCodeCollapsible: (state, action: PayloadAction) => { + // state.codeCollapsible = action.payload + // }, + // setCodeWrappable: (state, action: PayloadAction) => { + // state.codeWrappable = action.payload + // }, + // setCodeImageTools: (state, action: PayloadAction) => { + // state.codeImageTools = action.payload + // }, + // setMathEngine: (state, action: PayloadAction) => { + // state.mathEngine = action.payload + // }, + // setMathEnableSingleDollar: (state, action: PayloadAction) => { + // state.mathEnableSingleDollar = action.payload + // }, + // setFoldDisplayMode: (state, action: PayloadAction<'expanded' | 'compact'>) => { + // state.foldDisplayMode = action.payload + // }, + // setGridColumns: (state, action: PayloadAction) => { + // state.gridColumns = action.payload + // }, + // setGridPopoverTrigger: (state, action: PayloadAction<'hover' | 'click'>) => { + // state.gridPopoverTrigger = action.payload + // }, + // setMessageStyle: (state, action: PayloadAction<'plain' | 'bubble'>) => { + // state.messageStyle = action.payload + // }, + // setTranslateModelPrompt: (state, action: PayloadAction) => { + // state.translateModelPrompt = action.payload + // }, + // setAutoTranslateWithSpace: (state, action: PayloadAction) => { + // state.autoTranslateWithSpace = action.payload + // }, + // setShowTranslateConfirm: (state, action: PayloadAction) => { + // state.showTranslateConfirm = action.payload + // }, + // setEnableTopicNaming: (state, action: PayloadAction) => { + // state.enableTopicNaming = action.payload + // }, + // setPasteLongTextThreshold: (state, action: PayloadAction) => { + // state.pasteLongTextThreshold = action.payload + // }, + // setTopicNamingPrompt: (state, action: PayloadAction) => { + // state.topicNamingPrompt = action.payload + // }, + // setSidebarIcons: (state, action: PayloadAction<{ visible?: SidebarIcon[]; disabled?: SidebarIcon[] }>) => { + // if (action.payload.visible) { + // state.sidebarIcons.visible = action.payload.visible + // } + // if (action.payload.disabled) { + // state.sidebarIcons.disabled = action.payload.disabled + // } + // }, + // setNarrowMode: (state, action: PayloadAction) => { + // state.narrowMode = action.payload + // }, + // setClickTrayToShowQuickAssistant: (state, action: PayloadAction) => { + // state.clickTrayToShowQuickAssistant = action.payload + // }, + // setEnableQuickAssistant: (state, action: PayloadAction) => { + // state.enableQuickAssistant = action.payload + // }, + // setReadClipboardAtStartup: (state, action: PayloadAction) => { + // state.readClipboardAtStartup = action.payload + // }, + // setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold' | 'grid'>) => { + // state.multiModelMessageStyle = action.payload + // }, + // setNotionDatabaseID: (state, action: PayloadAction) => { + // state.notionDatabaseID = action.payload + // }, + // setNotionApiKey: (state, action: PayloadAction) => { + // state.notionApiKey = action.payload + // }, + // setNotionPageNameKey: (state, action: PayloadAction) => { + // state.notionPageNameKey = action.payload + // }, + // setmarkdownExportPath: (state, action: PayloadAction) => { + // state.markdownExportPath = action.payload + // }, + // setForceDollarMathInMarkdown: (state, action: PayloadAction) => { + // state.forceDollarMathInMarkdown = action.payload + // }, + // setUseTopicNamingForMessageTitle: (state, action: PayloadAction) => { + // state.useTopicNamingForMessageTitle = action.payload + // }, + // setShowModelNameInMarkdown: (state, action: PayloadAction) => { + // state.showModelNameInMarkdown = action.payload + // }, + // setShowModelProviderInMarkdown: (state, action: PayloadAction) => { + // state.showModelProviderInMarkdown = action.payload + // }, + // setThoughtAutoCollapse: (state, action: PayloadAction) => { + // state.thoughtAutoCollapse = action.payload + // }, + // setNotionExportReasoning: (state, action: PayloadAction) => { + // state.notionExportReasoning = action.payload + // }, + // setExcludeCitationsInExport: (state, action: PayloadAction) => { + // state.excludeCitationsInExport = action.payload + // }, + // setStandardizeCitationsInExport: (state, action: PayloadAction) => { + // state.standardizeCitationsInExport = action.payload + // }, + // setYuqueToken: (state, action: PayloadAction) => { + // state.yuqueToken = action.payload + // }, + // setYuqueRepoId: (state, action: PayloadAction) => { + // state.yuqueRepoId = action.payload + // }, + // setYuqueUrl: (state, action: PayloadAction) => { + // state.yuqueUrl = action.payload + // }, + // setJoplinToken: (state, action: PayloadAction) => { + // state.joplinToken = action.payload + // }, + // setJoplinUrl: (state, action: PayloadAction) => { + // state.joplinUrl = action.payload + // }, + // setJoplinExportReasoning: (state, action: PayloadAction) => { + // state.joplinExportReasoning = action.payload + // }, + // setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => { + // state.messageNavigation = action.payload + // }, + // setDefaultObsidianVault: (state, action: PayloadAction) => { + // state.defaultObsidianVault = action.payload + // }, setDefaultAgent: (state, action: PayloadAction) => { state.defaultAgent = action.payload }, - setSiyuanApiUrl: (state, action: PayloadAction) => { - state.siyuanApiUrl = action.payload - }, - setSiyuanToken: (state, action: PayloadAction) => { - state.siyuanToken = action.payload - }, - setSiyuanBoxId: (state, action: PayloadAction) => { - state.siyuanBoxId = action.payload - }, - setSiyuanRootPath: (state, action: PayloadAction) => { - state.siyuanRootPath = action.payload - }, + // setSiyuanApiUrl: (state, action: PayloadAction) => { + // state.siyuanApiUrl = action.payload + // }, + // setSiyuanToken: (state, action: PayloadAction) => { + // state.siyuanToken = action.payload + // }, + // setSiyuanBoxId: (state, action: PayloadAction) => { + // state.siyuanBoxId = action.payload + // }, + // setSiyuanRootPath: (state, action: PayloadAction) => { + // state.siyuanRootPath = action.payload + // }, setAgentssubscribeUrl: (state, action: PayloadAction) => { state.agentssubscribeUrl = action.payload }, - setMaxKeepAliveMinapps: (state, action: PayloadAction) => { - state.maxKeepAliveMinapps = action.payload - }, - setShowOpenedMinappsInSidebar: (state, action: PayloadAction) => { - state.showOpenedMinappsInSidebar = action.payload - }, - setMinappsOpenLinkExternal: (state, action: PayloadAction) => { - state.minappsOpenLinkExternal = action.payload - }, - setEnableDataCollection: (state, action: PayloadAction) => { - state.enableDataCollection = action.payload - }, - setEnableSpellCheck: (state, action: PayloadAction) => { - state.enableSpellCheck = action.payload - }, - setSpellCheckLanguages: (state, action: PayloadAction) => { - state.spellCheckLanguages = action.payload - }, - setExportMenuOptions: (state, action: PayloadAction) => { - state.exportMenuOptions = action.payload - }, - setEnableQuickPanelTriggers: (state, action: PayloadAction) => { - state.enableQuickPanelTriggers = action.payload - }, - setConfirmDeleteMessage: (state, action: PayloadAction) => { - state.confirmDeleteMessage = action.payload - }, - setConfirmRegenerateMessage: (state, action: PayloadAction) => { - state.confirmRegenerateMessage = action.payload - }, - // setDisableHardwareAcceleration: (state, action: PayloadAction) => { - // state.disableHardwareAcceleration = action.payload + // setMaxKeepAliveMinapps: (state, action: PayloadAction) => { + // state.maxKeepAliveMinapps = action.payload // }, + // setShowOpenedMinappsInSidebar: (state, action: PayloadAction) => { + // state.showOpenedMinappsInSidebar = action.payload + // }, + // setMinappsOpenLinkExternal: (state, action: PayloadAction) => { + // state.minappsOpenLinkExternal = action.payload + // }, + // setEnableDataCollection: (state, action: PayloadAction) => { + // state.enableDataCollection = action.payload + // }, + // setEnableSpellCheck: (state, action: PayloadAction) => { + // state.enableSpellCheck = action.payload + // }, + // setSpellCheckLanguages: (state, action: PayloadAction) => { + // state.spellCheckLanguages = action.payload + // }, + // setExportMenuOptions: (state, action: PayloadAction) => { + // state.exportMenuOptions = action.payload + // }, + // setEnableQuickPanelTriggers: (state, action: PayloadAction) => { + // state.enableQuickPanelTriggers = action.payload + // }, + // setConfirmDeleteMessage: (state, action: PayloadAction) => { + // state.confirmDeleteMessage = action.payload + // }, + // setConfirmRegenerateMessage: (state, action: PayloadAction) => { + // state.confirmRegenerateMessage = action.payload + // }, + // // setDisableHardwareAcceleration: (state, action: PayloadAction) => { + // // state.disableHardwareAcceleration = action.payload + // // }, setOpenAISummaryText: (state, action: PayloadAction) => { state.openAI.summaryText = action.payload }, setOpenAIVerbosity: (state, action: PayloadAction) => { state.openAI.verbosity = action.payload }, - setNotificationSettings: (state, action: PayloadAction) => { - state.notification = action.payload - }, - // Local backup settings - setLocalBackupDir: (state, action: PayloadAction) => { - state.localBackupDir = action.payload - }, - setLocalBackupAutoSync: (state, action: PayloadAction) => { - state.localBackupAutoSync = action.payload - }, - setLocalBackupSyncInterval: (state, action: PayloadAction) => { - state.localBackupSyncInterval = action.payload - }, - setLocalBackupMaxBackups: (state, action: PayloadAction) => { - state.localBackupMaxBackups = action.payload - }, - setLocalBackupSkipBackupFile: (state, action: PayloadAction) => { - state.localBackupSkipBackupFile = action.payload - }, + // setNotificationSettings: (state, action: PayloadAction) => { + // state.notification = action.payload + // }, + // // Local backup settings + // setLocalBackupDir: (state, action: PayloadAction) => { + // state.localBackupDir = action.payload + // }, + // setLocalBackupAutoSync: (state, action: PayloadAction) => { + // state.localBackupAutoSync = action.payload + // }, + // setLocalBackupSyncInterval: (state, action: PayloadAction) => { + // state.localBackupSyncInterval = action.payload + // }, + // setLocalBackupMaxBackups: (state, action: PayloadAction) => { + // state.localBackupMaxBackups = action.payload + // }, + // setLocalBackupSkipBackupFile: (state, action: PayloadAction) => { + // state.localBackupSkipBackupFile = action.payload + // }, setDefaultPaintingProvider: (state, action: PayloadAction) => { state.defaultPaintingProvider = action.payload }, - setS3: (state, action: PayloadAction) => { - state.s3 = action.payload - }, - setS3Partial: (state, action: PayloadAction>) => { - state.s3 = { ...state.s3, ...action.payload } - }, - setEnableDeveloperMode: (state, action: PayloadAction) => { - state.enableDeveloperMode = action.payload - }, - setNavbarPosition: (state, action: PayloadAction<'left' | 'top'>) => { - state.navbarPosition = action.payload - }, - // API Server actions - setApiServerEnabled: (state, action: PayloadAction) => { - state.apiServer = { - ...state.apiServer, - enabled: action.payload - } - }, - setApiServerPort: (state, action: PayloadAction) => { - state.apiServer = { - ...state.apiServer, - port: action.payload - } - }, - setApiServerApiKey: (state, action: PayloadAction) => { - state.apiServer = { - ...state.apiServer, - apiKey: action.payload - } - }, - setShowMessageOutline: (state, action: PayloadAction) => { - state.showMessageOutline = action.payload - }, - setShowWorkspace: (state, action: PayloadAction) => { - state.showWorkspace = action.payload - }, + // setS3: (state, action: PayloadAction) => { + // state.s3 = action.payload + // }, + // setS3Partial: (state, action: PayloadAction>) => { + // state.s3 = { ...state.s3, ...action.payload } + // }, + // setEnableDeveloperMode: (state, action: PayloadAction) => { + // state.enableDeveloperMode = action.payload + // }, + // setNavbarPosition: (state, action: PayloadAction<'left' | 'top'>) => { + // state.navbarPosition = action.payload + // }, + // // API Server actions + // setApiServerEnabled: (state, action: PayloadAction) => { + // state.apiServer = { + // ...state.apiServer, + // enabled: action.payload + // } + // }, + // setApiServerPort: (state, action: PayloadAction) => { + // state.apiServer = { + // ...state.apiServer, + // port: action.payload + // } + // }, + // setApiServerApiKey: (state, action: PayloadAction) => { + // state.apiServer = { + // ...state.apiServer, + // apiKey: action.payload + // } + // }, + // setShowMessageOutline: (state, action: PayloadAction) => { + // state.showMessageOutline = action.payload + // }, + // setShowWorkspace: (state, action: PayloadAction) => { + // state.showWorkspace = action.payload + // }, toggleShowWorkspace: (state) => { state.showWorkspace = !state.showWorkspace } diff --git a/src/renderer/src/utils/__tests__/export.test.ts b/src/renderer/src/utils/__tests__/export.test.ts index da7cc66815..44ef6ff420 100644 --- a/src/renderer/src/utils/__tests__/export.test.ts +++ b/src/renderer/src/utils/__tests__/export.test.ts @@ -257,21 +257,21 @@ describe('export', () => { mockedMessages = [userMsg, assistantMsg] }) - it('should handle empty content in message blocks', () => { + it('should handle empty content in message blocks', async () => { const msgWithEmptyContent = createMessage({ role: 'user', id: 'empty_block' }, [ { type: MessageBlockType.MAIN_TEXT, content: '' } ]) - const markdown = messageToMarkdown(msgWithEmptyContent) + const markdown = await messageToMarkdown(msgWithEmptyContent) expect(markdown).toContain('## 🧑‍💻 User') // Should handle empty content gracefully expect(markdown).toBeDefined() expect(markdown.split('\n\n').filter((s) => s.trim()).length).toBeGreaterThanOrEqual(1) }) - it('should format user message using main text block', () => { + it('should format user message using main text block', async () => { const msg = mockedMessages.find((m) => m.id === 'u1') expect(msg).toBeDefined() - const markdown = messageToMarkdown(msg!) + const markdown = await messageToMarkdown(msg!) expect(markdown).toContain('## 🧑‍💻 User') expect(markdown).toContain('hello user') @@ -281,10 +281,10 @@ describe('export', () => { expect(sections.length).toBeGreaterThanOrEqual(2) // title section and content section }) - it('should format assistant message using main text block', () => { + it('should format assistant message using main text block', async () => { const msg = mockedMessages.find((m) => m.id === 'a1') expect(msg).toBeDefined() - const markdown = messageToMarkdown(msg!) + const markdown = await messageToMarkdown(msg!) expect(markdown).toContain('## 🤖 Assistant') expect(markdown).toContain('hi assistant') @@ -294,21 +294,21 @@ describe('export', () => { expect(sections.length).toBeGreaterThanOrEqual(2) // title section and content section }) - it('should handle message with no main text block gracefully', () => { + it('should handle message with no main text block gracefully', async () => { const msg = createMessage({ role: 'user', id: 'u2' }, []) mockedMessages.push(msg) - const markdown = messageToMarkdown(msg) + const markdown = await messageToMarkdown(msg) expect(markdown).toContain('## 🧑‍💻 User') // Check that it doesn't fail when no content exists expect(markdown).toBeDefined() }) - it('should include citation content when citation blocks exist', () => { + it('should include citation content when citation blocks exist', async () => { const msgWithCitation = createMessage({ role: 'assistant', id: 'a_cite' }, [ { type: MessageBlockType.MAIN_TEXT, content: 'Main content' }, { type: MessageBlockType.CITATION } ]) - const markdown = messageToMarkdown(msgWithCitation) + const markdown = await messageToMarkdown(msgWithCitation) expect(markdown).toContain('## 🤖 Assistant') expect(markdown).toContain('Main content') expect(markdown).toContain('[1] [https://example1.com](Example Citation 1)') @@ -337,10 +337,10 @@ describe('export', () => { mockedMessages = [msgWithReasoning, msgWithThinkTag, msgWithoutReasoning, msgWithReasoningAndCitation] }) - it('should include reasoning content from thinking block in details section', () => { + it('should include reasoning content from thinking block in details section', async () => { const msg = mockedMessages.find((m) => m.id === 'a2') expect(msg).toBeDefined() - const markdown = messageToMarkdownWithReasoning(msg!) + const markdown = await messageToMarkdownWithReasoning(msg!) expect(markdown).toContain('## 🤖 Assistant') expect(markdown).toContain('Main Answer') expect(markdown).toContain(' { mockedMessages = [userMsg, assistantMsg, singleUserMsg] }) - it('should join multiple messages with markdown separator', () => { + it('should join multiple messages with markdown separator', async () => { const msgs = mockedMessages.filter((m) => ['u3', 'a5'].includes(m.id)) - const markdown = messagesToMarkdown(msgs) + const markdown = await messagesToMarkdown(msgs) expect(markdown).toContain('User query A') expect(markdown).toContain('Assistant response B') @@ -414,13 +414,13 @@ describe('export', () => { expect(markdown.split('\n---\n').length).toBe(2) }) - it('should handle an empty array of messages', () => { + it('should handle an empty array of messages', async () => { expect(messagesToMarkdown([])).toBe('') }) - it('should handle a single message without separator', () => { + it('should handle a single message without separator', async () => { const msgs = mockedMessages.filter((m) => m.id === 'u4') - const markdown = messagesToMarkdown(msgs) + const markdown = await messagesToMarkdown(msgs) expect(markdown).toContain('Single user query') expect(markdown.split('\n\n---\n\n').length).toBe(1) }) @@ -458,7 +458,7 @@ describe('export', () => { const { TopicManager } = await import('@renderer/hooks/useTopic') ;(TopicManager.getTopicMessages as any).mockResolvedValue([userMsg, assistantMsg]) // Specific mock for this test to check formatting - ;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*]/g, '')) + ;(markdownToPlainText as any).mockImplementation(async (str: string) => str.replace(/[#*]/g, '')) const plainText = await topicToPlainText(testTopic) @@ -471,13 +471,13 @@ describe('export', () => { }) describe('messageToPlainText', () => { - it('should convert a single message content to plain text without role prefix', () => { + it('should convert a single message content to plain text without role prefix', async () => { const testMessage = createMessage({ role: 'user', id: 'single_msg_plain' }, [ { type: MessageBlockType.MAIN_TEXT, content: '### Single Message Content' } ]) - ;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*_]/g, '')) + ;(markdownToPlainText as any).mockImplementation(async (str: string) => str.replace(/[#*_]/g, '')) - const result = messageToPlainText(testMessage) + const result = await messageToPlainText(testMessage) expect(result).toBe('Single Message Content') expect(markdownToPlainText).toHaveBeenCalledWith('### Single Message Content') }) diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index e048a3e7c7..c70e9176f7 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1,3 +1,4 @@ +import { preferenceService } from '@data/PreferenceService' import { loggerService } from '@logger' import { Client } from '@notionhq/client' import i18n from '@renderer/i18n' @@ -159,9 +160,11 @@ export function getTitleFromString(str: string, length: number = 80): string { return title } -const getRoleText = (role: string, modelName?: string, providerId?: string): string => { - const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings - +const getRoleText = async (role: string, modelName?: string, providerId?: string): Promise => { + const { showModelNameInMarkdown, showModelProviderInMarkdown } = await preferenceService.getMultiple({ + showModelNameInMarkdown: 'data.export.markdown.show_model_name', + showModelProviderInMarkdown: 'data.export.markdown.show_model_provider' + }) if (role === 'user') { return '🧑‍💻 User' } else if (role === 'system') { @@ -263,13 +266,13 @@ const formatCitationsAsFootnotes = (citations: string): string => { return footnotes.join('\n\n') } -const createBaseMarkdown = ( +const createBaseMarkdown = async ( message: Message, includeReasoning: boolean = false, excludeCitations: boolean = false, normalizeCitations: boolean = true -): { titleSection: string; reasoningSection: string; contentSection: string; citation: string } => { - const { forceDollarMathInMarkdown } = store.getState().settings +): Promise<{ titleSection: string; reasoningSection: string; contentSection: string; citation: string }> => { + const forceDollarMathInMarkdown = await preferenceService.get('data.export.markdown.force_dollar_math') const roleText = getRoleText(message.role, message.model?.name, message.model?.provider) const titleSection = `## ${roleText}` let reasoningSection = '' @@ -313,10 +316,13 @@ const createBaseMarkdown = ( return { titleSection, reasoningSection, contentSection: processedContent, citation } } -export const messageToMarkdown = (message: Message, excludeCitations?: boolean): string => { - const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings +export const messageToMarkdown = async (message: Message, excludeCitations?: boolean): Promise => { + const { excludeCitationsInExport, standardizeCitationsInExport } = await preferenceService.getMultiple({ + excludeCitationsInExport: 'data.export.markdown.exclude_citations', + standardizeCitationsInExport: 'data.export.markdown.standardize_citations' + }) const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport - const { titleSection, contentSection, citation } = createBaseMarkdown( + const { titleSection, contentSection, citation } = await createBaseMarkdown( message, false, shouldExcludeCitations, @@ -325,10 +331,13 @@ export const messageToMarkdown = (message: Message, excludeCitations?: boolean): return [titleSection, '', contentSection, citation].join('\n') } -export const messageToMarkdownWithReasoning = (message: Message, excludeCitations?: boolean): string => { - const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings +export const messageToMarkdownWithReasoning = async (message: Message, excludeCitations?: boolean): Promise => { + const { excludeCitationsInExport, standardizeCitationsInExport } = await preferenceService.getMultiple({ + excludeCitationsInExport: 'data.export.markdown.exclude_citations', + standardizeCitationsInExport: 'data.export.markdown.standardize_citations' + }) const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport - const { titleSection, reasoningSection, contentSection, citation } = createBaseMarkdown( + const { titleSection, reasoningSection, contentSection, citation } = await createBaseMarkdown( message, true, shouldExcludeCitations, @@ -337,18 +346,14 @@ export const messageToMarkdownWithReasoning = (message: Message, excludeCitation return [titleSection, '', reasoningSection, contentSection, citation].join('\n') } -export const messagesToMarkdown = ( +export const messagesToMarkdown = async ( messages: Message[], exportReasoning?: boolean, excludeCitations?: boolean -): string => { - return messages - .map((message) => - exportReasoning - ? messageToMarkdownWithReasoning(message, excludeCitations) - : messageToMarkdown(message, excludeCitations) - ) - .join('\n---\n') +): Promise => { + const converter = exportReasoning ? messageToMarkdownWithReasoning : messageToMarkdown + const markdowns = await Promise.all(messages.map((message) => converter(message, excludeCitations))) + return markdowns.join('\n---\n') } const formatMessageAsPlainText = (message: Message): string => { @@ -377,7 +382,7 @@ export const topicToMarkdown = async ( const messages = await fetchTopicMessages(topic.id) if (messages && messages.length > 0) { - return topicName + '\n\n' + messagesToMarkdown(messages, exportReasoning, excludeCitations) + return topicName + '\n\n' + (await messagesToMarkdown(messages, exportReasoning, excludeCitations)) } return topicName @@ -407,7 +412,7 @@ export const exportTopicAsMarkdown = async ( setExportingState(true) - const { markdownExportPath } = store.getState().settings + const markdownExportPath = await preferenceService.get('data.export.markdown.path') if (!markdownExportPath) { try { const fileName = removeSpecialCharactersForFileName(topic.name) + '.md' @@ -453,14 +458,14 @@ export const exportMessageAsMarkdown = async ( setExportingState(true) - const { markdownExportPath } = store.getState().settings + const markdownExportPath = await preferenceService.get('data.export.markdown.path') if (!markdownExportPath) { try { const title = await getMessageTitle(message) const fileName = removeSpecialCharactersForFileName(title) + '.md' const markdown = exportReasoning - ? messageToMarkdownWithReasoning(message, excludeCitations) - : messageToMarkdown(message, excludeCitations) + ? await messageToMarkdownWithReasoning(message, excludeCitations) + : await messageToMarkdown(message, excludeCitations) const result = await window.api.file.save(fileName, markdown) if (result) { window.message.success({ @@ -480,8 +485,8 @@ export const exportMessageAsMarkdown = async ( const title = await getMessageTitle(message) const fileName = removeSpecialCharactersForFileName(title) + ` ${timestamp}.md` const markdown = exportReasoning - ? messageToMarkdownWithReasoning(message, excludeCitations) - : messageToMarkdown(message, excludeCitations) + ? await messageToMarkdownWithReasoning(message, excludeCitations) + : await messageToMarkdown(message, excludeCitations) await window.api.file.write(markdownExportPath + '/' + fileName, markdown) window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) } catch (error: any) { @@ -579,7 +584,11 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise => { - const { notionExportReasoning } = store.getState().settings + const notionExportReasoning = await preferenceService.get('data.integration.notion.export_reasoning') const notionBlocks = await convertMarkdownToNotionBlocks(content) @@ -665,7 +674,10 @@ export const exportMessageToNotion = async (title: string, content: string, mess } export const exportTopicToNotion = async (topic: Topic): Promise => { - const { notionExportReasoning, excludeCitationsInExport } = store.getState().settings + const { notionExportReasoning, excludeCitationsInExport } = await preferenceService.getMultiple({ + notionExportReasoning: 'data.integration.notion.export_reasoning', + excludeCitationsInExport: 'data.export.markdown.exclude_citations' + }) const topicMessages = await fetchTopicMessages(topic.id) @@ -677,7 +689,7 @@ export const exportTopicToNotion = async (topic: Topic): Promise => { for (const message of topicMessages) { // 将单个消息转换为markdown - const messageMarkdown = messageToMarkdown(message, excludeCitationsInExport) + const messageMarkdown = await messageToMarkdown(message, excludeCitationsInExport) const messageBlocks = await convertMarkdownToNotionBlocks(messageMarkdown) if (notionExportReasoning) { @@ -699,7 +711,10 @@ export const exportTopicToNotion = async (topic: Topic): Promise => { } export const exportMarkdownToYuque = async (title: string, content: string): Promise => { - const { yuqueToken, yuqueRepoId } = store.getState().settings + const { yuqueToken, yuqueRepoId } = await preferenceService.getMultiple({ + yuqueToken: 'data.integration.yuque.token', + yuqueRepoId: 'data.integration.yuque.repo_id' + }) if (getExportState()) { window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'yuque-exporting' }) @@ -897,7 +912,13 @@ export const exportMarkdownToJoplin = async ( title: string, contentOrMessages: string | Message | Message[] ): Promise => { - const { joplinUrl, joplinToken, joplinExportReasoning, excludeCitationsInExport } = store.getState().settings + const { joplinUrl, joplinToken, joplinExportReasoning, excludeCitationsInExport } = + await preferenceService.getMultiple({ + joplinUrl: 'data.integration.joplin.url', + joplinToken: 'data.integration.joplin.token', + joplinExportReasoning: 'data.integration.joplin.export_reasoning', + excludeCitationsInExport: 'data.export.markdown.exclude_citations' + }) if (getExportState()) { window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'joplin-exporting' }) @@ -915,12 +936,12 @@ export const exportMarkdownToJoplin = async ( if (typeof contentOrMessages === 'string') { content = contentOrMessages } else if (Array.isArray(contentOrMessages)) { - content = messagesToMarkdown(contentOrMessages, joplinExportReasoning, excludeCitationsInExport) + content = await messagesToMarkdown(contentOrMessages, joplinExportReasoning, excludeCitationsInExport) } else { // 单条Message content = joplinExportReasoning - ? messageToMarkdownWithReasoning(contentOrMessages, excludeCitationsInExport) - : messageToMarkdown(contentOrMessages, excludeCitationsInExport) + ? await messageToMarkdownWithReasoning(contentOrMessages, excludeCitationsInExport) + : await messageToMarkdown(contentOrMessages, excludeCitationsInExport) } try { @@ -963,7 +984,12 @@ export const exportMarkdownToJoplin = async ( * @param content 笔记内容 */ export const exportMarkdownToSiyuan = async (title: string, content: string): Promise => { - const { siyuanApiUrl, siyuanToken, siyuanBoxId, siyuanRootPath } = store.getState().settings + const { siyuanApiUrl, siyuanToken, siyuanBoxId, siyuanRootPath } = await preferenceService.getMultiple({ + siyuanApiUrl: 'data.integration.siyuan.api_url', + siyuanToken: 'data.integration.siyuan.token', + siyuanBoxId: 'data.integration.siyuan.box_id', + siyuanRootPath: 'data.integration.siyuan.root_path' + }) if (getExportState()) { window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'siyuan-exporting' }) diff --git a/src/renderer/src/utils/prompt.ts b/src/renderer/src/utils/prompt.ts index 85766953ba..5cb972a714 100644 --- a/src/renderer/src/utils/prompt.ts +++ b/src/renderer/src/utils/prompt.ts @@ -1,6 +1,8 @@ import { loggerService } from '@logger' +import { preferenceService } from '@renderer/data/PreferenceService' import store from '@renderer/store' import { MCPTool } from '@renderer/types' +import { defaultLanguage } from '@shared/config/constant' const logger = loggerService.withContext('Utils:Prompt') @@ -205,7 +207,7 @@ export const replacePromptVariables = async (userSystemPrompt: string, modelName if (userSystemPrompt.includes('{{username}}')) { try { - const userName = store.getState().settings.userName || 'Unknown Username' + const userName = (await preferenceService.get('app.user.name')) || 'Unknown Username' userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, userName) } catch (error) { logger.error('Failed to get username:', error as Error) @@ -225,8 +227,8 @@ export const replacePromptVariables = async (userSystemPrompt: string, modelName if (userSystemPrompt.includes('{{language}}')) { try { - const language = store.getState().settings.language - userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language) + const language = await preferenceService.get('app.language') + userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language || navigator.language || defaultLanguage) } catch (error) { logger.error('Failed to get language:', error as Error) userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, 'Unknown System Language') diff --git a/src/renderer/src/windows/dataRefactorTest/components/PreferenceHookTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/PreferenceHookTests.tsx index 2e148ba331..fbbccc3313 100644 --- a/src/renderer/src/windows/dataRefactorTest/components/PreferenceHookTests.tsx +++ b/src/renderer/src/windows/dataRefactorTest/components/PreferenceHookTests.tsx @@ -70,7 +70,7 @@ const PreferenceHookTests: React.FC = () => { const testBatchOperations = async () => { try { const keys: PreferenceKeyType[] = ['ui.theme_mode', 'app.language'] - const result = await preferenceService.getMultiple(keys) + const result = await preferenceService.getMultipleRaw(keys) message.success(`批量获取成功: ${Object.keys(result).length} 项`) logger.debug('Batch get result:', { result }) diff --git a/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx b/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx index 8567228c54..acc809ded7 100644 --- a/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx +++ b/src/renderer/src/windows/selection/action/components/ActionTranslate.tsx @@ -87,13 +87,15 @@ const ActionTranslate: FC = ({ action, scrollToBottom }) => { if (initialized.current || !action.selectedText) return initialized.current = true - // Initialize assistant - const currentAssistant = getDefaultTranslateAssistant(targetLanguage, action.selectedText) + runAsyncFunction(async () => { + // Initialize assistant + const currentAssistant = await getDefaultTranslateAssistant(targetLanguage, action.selectedText!) - assistantRef.current = currentAssistant + assistantRef.current = currentAssistant - // Initialize topic - topicRef.current = getDefaultTopic(currentAssistant.id) + // Initialize topic + topicRef.current = getDefaultTopic(currentAssistant.id) + }) }, [action, targetLanguage, translateModelPrompt]) const fetchResult = useCallback(async () => { @@ -141,7 +143,7 @@ const ActionTranslate: FC = ({ action, scrollToBottom }) => { } } - assistantRef.current = getDefaultTranslateAssistant(translateLang, action.selectedText) + assistantRef.current = await getDefaultTranslateAssistant(translateLang, action.selectedText) processMessages(assistantRef.current, topicRef.current, action.selectedText, setAskId, onStream, onFinish, onError) }, [action, targetLanguage, alterLanguage, scrollToBottom])