mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 18:10:26 +08:00
refactor: enhance preference management and streamline component integrations
- Updated various components to utilize the usePreference hook, replacing previous useSettings references for improved consistency and maintainability. - Introduced new preference types for ChatMessageStyle, ChatMessageNavigationMode, and MultiModelMessageStyle to enhance type safety. - Refactored preference handling in multiple files, ensuring a more streamlined approach to managing user preferences. - Cleaned up unused code and comments related to previous settings for better clarity and maintainability. - Updated auto-generated preference mappings to reflect recent changes in preference structure.
This commit is contained in:
parent
68cd87e069
commit
566dd14fed
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<PopupContainerProps> = ({
|
||||
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<PopupContainerProps> = ({
|
||||
}
|
||||
}
|
||||
fetchVaults()
|
||||
}, [defaultObsidianVault])
|
||||
}, [defaultObsidianVault, setDefaultObsidianVault])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedVault) {
|
||||
@ -232,9 +231,9 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
||||
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 = ''
|
||||
}
|
||||
|
||||
@ -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<Props> = ({ resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const [progressData, setProgressData] = useState<ProgressData>()
|
||||
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) => {
|
||||
|
||||
@ -210,9 +210,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple preferences at once
|
||||
* Get multiple preferences at once, return is Partial<PreferenceDefaultScopeType>
|
||||
*/
|
||||
public async getMultiple(keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> {
|
||||
public async getMultipleRaw(keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> {
|
||||
// Check which keys are already cached
|
||||
const cachedResults: Partial<PreferenceDefaultScopeType> = {}
|
||||
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<T extends Record<string, PreferenceKeyType>>(
|
||||
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)
|
||||
|
||||
@ -315,7 +315,7 @@ export function useMultiplePreferences<T extends Record<string, PreferenceKeyTyp
|
||||
})
|
||||
|
||||
if (uncachedKeys.length > 0) {
|
||||
preferenceService.getMultiple(uncachedKeys).catch((error) => {
|
||||
preferenceService.getMultipleRaw(uncachedKeys).catch((error) => {
|
||||
logger.error('Failed to load initial preferences:', error as Error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
// }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<TranslateLanguage[]>(builtinLanguages)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<HTMLDivElement> {
|
||||
topic?: Topic
|
||||
}
|
||||
@ -27,7 +26,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
const TopicMessages: FC<Props> = ({ 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 | undefined>(_topic)
|
||||
|
||||
@ -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> = (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> = (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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<Props> = ({ estimateTokenCount, inputTokenCount, contextCount }) => {
|
||||
const { t } = useTranslation()
|
||||
const { showInputEstimatedTokens } = useSettings()
|
||||
const [showInputEstimatedTokens] = usePreference('chat.input.show_estimated_tokens')
|
||||
|
||||
if (!showInputEstimatedTokens) {
|
||||
return null
|
||||
|
||||
@ -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<Props> = ({ 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)
|
||||
|
||||
@ -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<Props> = ({ 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))
|
||||
|
||||
|
||||
@ -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<Props> = ({ 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])
|
||||
|
||||
@ -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<ChatFlowHistoryProps> = ({ conversationId }) => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { userName } = useSettings()
|
||||
const [userName] = usePreference('app.user.name')
|
||||
const { settedTheme } = useTheme()
|
||||
|
||||
const topicId = conversationId
|
||||
|
||||
@ -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<ChatNavigationProps> = ({ containerId }) => {
|
||||
const [manuallyClosedUntil, setManuallyClosedUntil] = useState<number | null>(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
|
||||
|
||||
@ -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<Props> = ({
|
||||
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<HTMLDivElement>(null)
|
||||
const { editingMessageId, stopEditing } = useMessageEditing()
|
||||
|
||||
@ -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<MessageLineProps> = ({ 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<HTMLDivElement>(null)
|
||||
|
||||
@ -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<Props> = ({ 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<TextAreaRef>(null)
|
||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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> = (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> = (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> = (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> = (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> = (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> = (props) => {
|
||||
key: 'siyuan',
|
||||
onClick: async () => {
|
||||
const title = await getMessageTitle(message)
|
||||
const markdown = messageToMarkdown(message)
|
||||
const markdown = await messageToMarkdown(message)
|
||||
exportMarkdownToSiyuan(title, markdown)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Props> = ({ block }) => {
|
||||
const [copiedMap, setCopiedMap] = useState<Record<string, boolean>>({})
|
||||
const [countdown, setCountdown] = useState<number>(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<number>(0)
|
||||
|
||||
@ -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<MessagesProps> = ({ 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)
|
||||
|
||||
@ -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<HTMLDivElement> {
|
||||
}
|
||||
|
||||
const NarrowLayout: FC<Props> = ({ children, ...props }) => {
|
||||
const { narrowMode } = useSettings()
|
||||
const [narrowMode] = usePreference('chat.narrow_mode')
|
||||
|
||||
return (
|
||||
<Container className={`narrow-mode ${narrowMode ? 'active' : ''}`} {...props}>
|
||||
|
||||
@ -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<Props> = ({ 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<Topic | null>(null)
|
||||
const targetTopic = useDeferredValue(_targetTopic)
|
||||
|
||||
@ -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<Props> = ({
|
||||
}) => {
|
||||
const { addAssistant } = useAssistants()
|
||||
const [tab, setTab] = useState<Tab>(position === 'left' ? _tab || 'assistants' : 'topic')
|
||||
const { topicPosition } = useSettings()
|
||||
const [topicPosition] = usePreference('topic.position')
|
||||
const { defaultAssistant } = useDefaultAssistant()
|
||||
const { toggleShowTopics } = useShowTopics()
|
||||
const { isLeftNavbar } = useNavbarPosition()
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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<NodeJS.Timeout>(null)
|
||||
const aihubmixProvider = providers.find((p) => p.id === 'aihubmix')!
|
||||
|
||||
|
||||
@ -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<NodeJS.Timeout>(null)
|
||||
const newApiProvider = providers.find((p) => p.id === 'new-api')!
|
||||
|
||||
|
||||
@ -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<NodeJS.Timeout>(null)
|
||||
|
||||
@ -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<NodeJS.Timeout>(null)
|
||||
const tokenfluxProvider = providers.find((p) => p.id === 'tokenflux')!
|
||||
const textareaRef = useRef<any>(null)
|
||||
|
||||
@ -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<WebSearchProviderResponse> {
|
||||
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')
|
||||
|
||||
@ -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<void> {
|
||||
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`)
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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<TranslateAssistant> {
|
||||
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 = {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<void> {
|
||||
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)
|
||||
|
||||
@ -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 })
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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('<details')
|
||||
@ -404,9 +404,9 @@ describe('export', () => {
|
||||
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')
|
||||
})
|
||||
|
||||
@ -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<string> => {
|
||||
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<string> => {
|
||||
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<string> => {
|
||||
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<string> => {
|
||||
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<boo
|
||||
return false
|
||||
}
|
||||
|
||||
const { notionDatabaseID, notionApiKey } = store.getState().settings
|
||||
const { notionDatabaseID, notionApiKey, notionPageNameKey } = await preferenceService.getMultiple({
|
||||
notionDatabaseID: 'data.integration.notion.database_id',
|
||||
notionPageNameKey: 'data.integration.notion.page_name_key',
|
||||
notionApiKey: 'data.integration.notion.api_key'
|
||||
})
|
||||
if (!notionApiKey || !notionDatabaseID) {
|
||||
window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' })
|
||||
return false
|
||||
@ -609,7 +618,7 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<boo
|
||||
const response = await notion.pages.create({
|
||||
parent: { database_id: notionDatabaseID },
|
||||
properties: {
|
||||
[store.getState().settings.notionPageNameKey || 'Name']: {
|
||||
[notionPageNameKey || 'Name']: {
|
||||
title: [{ text: { content: title } }]
|
||||
}
|
||||
}
|
||||
@ -645,7 +654,7 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<boo
|
||||
}
|
||||
|
||||
export const exportMessageToNotion = async (title: string, content: string, message?: Message): Promise<boolean> => {
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
|
||||
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<boolean> => {
|
||||
}
|
||||
|
||||
export const exportMarkdownToYuque = async (title: string, content: string): Promise<any | null> => {
|
||||
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<any | null> => {
|
||||
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<void> => {
|
||||
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' })
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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 })
|
||||
|
||||
@ -87,13 +87,15 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
if (initialized.current || !action.selectedText) return
|
||||
initialized.current = true
|
||||
|
||||
runAsyncFunction(async () => {
|
||||
// Initialize assistant
|
||||
const currentAssistant = getDefaultTranslateAssistant(targetLanguage, action.selectedText)
|
||||
const currentAssistant = await getDefaultTranslateAssistant(targetLanguage, action.selectedText!)
|
||||
|
||||
assistantRef.current = currentAssistant
|
||||
|
||||
// Initialize topic
|
||||
topicRef.current = getDefaultTopic(currentAssistant.id)
|
||||
})
|
||||
}, [action, targetLanguage, translateModelPrompt])
|
||||
|
||||
const fetchResult = useCallback(async () => {
|
||||
@ -141,7 +143,7 @@ const ActionTranslate: FC<Props> = ({ 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])
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user