mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 10:00:08 +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', // 公测版本
|
RC = 'rc', // 公测版本
|
||||||
BETA = 'beta' // 预览版本
|
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 {
|
import type {
|
||||||
AssistantIconType,
|
AssistantIconType,
|
||||||
AssistantTabSortType,
|
AssistantTabSortType,
|
||||||
|
ChatMessageNavigationMode,
|
||||||
|
ChatMessageStyle,
|
||||||
|
LanguageVarious,
|
||||||
MultiModelFoldDisplayMode,
|
MultiModelFoldDisplayMode,
|
||||||
|
MultiModelGridPopoverTrigger,
|
||||||
|
MultiModelMessageStyle,
|
||||||
ProxyMode,
|
ProxyMode,
|
||||||
SelectionActionItem,
|
SelectionActionItem,
|
||||||
SelectionFilterMode,
|
SelectionFilterMode,
|
||||||
@ -22,7 +27,7 @@ import type {
|
|||||||
SidebarIcon,
|
SidebarIcon,
|
||||||
WindowStyle
|
WindowStyle
|
||||||
} from '@shared/data/preferenceTypes'
|
} 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", {
|
/* eslint @typescript-eslint/member-ordering: ["error", {
|
||||||
"interfaces": { "order": "alphabetically" },
|
"interfaces": { "order": "alphabetically" },
|
||||||
@ -148,11 +153,11 @@ export interface PreferencesType {
|
|||||||
// redux/settings/gridColumns
|
// redux/settings/gridColumns
|
||||||
'chat.message.multi_model.grid_columns': number
|
'chat.message.multi_model.grid_columns': number
|
||||||
// redux/settings/gridPopoverTrigger
|
// redux/settings/gridPopoverTrigger
|
||||||
'chat.message.multi_model.grid_popover_trigger': string
|
'chat.message.multi_model.grid_popover_trigger': MultiModelGridPopoverTrigger
|
||||||
// redux/settings/multiModelMessageStyle
|
// redux/settings/multiModelMessageStyle
|
||||||
'chat.message.multi_model.style': string
|
'chat.message.multi_model.style': MultiModelMessageStyle
|
||||||
// redux/settings/messageNavigation
|
// redux/settings/messageNavigation
|
||||||
'chat.message.navigation_mode': string
|
'chat.message.navigation_mode': ChatMessageNavigationMode
|
||||||
// redux/settings/renderInputMessageAsMarkdown
|
// redux/settings/renderInputMessageAsMarkdown
|
||||||
'chat.message.render_as_markdown': boolean
|
'chat.message.render_as_markdown': boolean
|
||||||
// redux/settings/showMessageDivider
|
// redux/settings/showMessageDivider
|
||||||
@ -162,7 +167,7 @@ export interface PreferencesType {
|
|||||||
// redux/settings/showPrompt
|
// redux/settings/showPrompt
|
||||||
'chat.message.show_prompt': boolean
|
'chat.message.show_prompt': boolean
|
||||||
// redux/settings/messageStyle
|
// redux/settings/messageStyle
|
||||||
'chat.message.style': string
|
'chat.message.style': ChatMessageStyle
|
||||||
// redux/settings/thoughtAutoCollapse
|
// redux/settings/thoughtAutoCollapse
|
||||||
'chat.message.thought.auto_collapse': boolean
|
'chat.message.thought.auto_collapse': boolean
|
||||||
// redux/settings/narrowMode
|
// redux/settings/narrowMode
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
|
||||||
import type { Topic } from '@renderer/types'
|
import type { Topic } from '@renderer/types'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
import {
|
import {
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
} from '@renderer/utils/export'
|
} from '@renderer/utils/export'
|
||||||
import { Alert, Empty, Form, Input, Modal, Select, Spin, Switch, TreeSelect } from 'antd'
|
import { Alert, Empty, Form, Input, Modal, Select, Spin, Switch, TreeSelect } from 'antd'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
const logger = loggerService.withContext('ObsidianExportDialog')
|
const logger = loggerService.withContext('ObsidianExportDialog')
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
@ -142,7 +141,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
|||||||
messages,
|
messages,
|
||||||
topic
|
topic
|
||||||
}) => {
|
}) => {
|
||||||
const defaultObsidianVault = store.getState().settings.defaultObsidianVault
|
const [defaultObsidianVault, setDefaultObsidianVault] = usePreference('data.integration.obsidian.default_vault')
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
title,
|
title,
|
||||||
tags: obsidianTags || '',
|
tags: obsidianTags || '',
|
||||||
@ -202,7 +201,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchVaults()
|
fetchVaults()
|
||||||
}, [defaultObsidianVault])
|
}, [defaultObsidianVault, setDefaultObsidianVault])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedVault) {
|
if (selectedVault) {
|
||||||
@ -232,9 +231,9 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
|||||||
if (topic) {
|
if (topic) {
|
||||||
markdown = await topicToMarkdown(topic, exportReasoning)
|
markdown = await topicToMarkdown(topic, exportReasoning)
|
||||||
} else if (messages && messages.length > 0) {
|
} else if (messages && messages.length > 0) {
|
||||||
markdown = messagesToMarkdown(messages, exportReasoning)
|
markdown = await messagesToMarkdown(messages, exportReasoning)
|
||||||
} else if (message) {
|
} else if (message) {
|
||||||
markdown = exportReasoning ? messageToMarkdownWithReasoning(message) : messageToMarkdown(message)
|
markdown = exportReasoning ? await messageToMarkdownWithReasoning(message) : await messageToMarkdown(message)
|
||||||
} else {
|
} else {
|
||||||
markdown = ''
|
markdown = ''
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { getBackupProgressLabel } from '@renderer/i18n/label'
|
import { getBackupProgressLabel } from '@renderer/i18n/label'
|
||||||
import { backup } from '@renderer/services/BackupService'
|
import { backup } from '@renderer/services/BackupService'
|
||||||
import store from '@renderer/store'
|
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { Modal, Progress } from 'antd'
|
import { Modal, Progress } from 'antd'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -27,7 +27,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [progressData, setProgressData] = useState<ProgressData>()
|
const [progressData, setProgressData] = useState<ProgressData>()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const skipBackupFile = store.getState().settings.skipBackupFile
|
const [skipBackupFile] = usePreference('data.backup.general.skip_backup_file')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const removeListener = window.electron.ipcRenderer.on(IpcChannel.BackupProgress, (_, data: ProgressData) => {
|
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
|
// Check which keys are already cached
|
||||||
const cachedResults: Partial<PreferenceDefaultScopeType> = {}
|
const cachedResults: Partial<PreferenceDefaultScopeType> = {}
|
||||||
const uncachedKeys: PreferenceKeyType[] = []
|
const uncachedKeys: PreferenceKeyType[] = []
|
||||||
@ -258,6 +258,23 @@ export class PreferenceService {
|
|||||||
|
|
||||||
return cachedResults
|
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
|
* Set multiple preferences at once with configurable update strategy
|
||||||
*/
|
*/
|
||||||
@ -454,7 +471,7 @@ export class PreferenceService {
|
|||||||
|
|
||||||
if (uncachedKeys.length > 0) {
|
if (uncachedKeys.length > 0) {
|
||||||
try {
|
try {
|
||||||
const values = await this.getMultiple(uncachedKeys)
|
const values = await this.getMultipleRaw(uncachedKeys)
|
||||||
logger.debug(`Preloaded ${Object.keys(values).length} preferences`)
|
logger.debug(`Preloaded ${Object.keys(values).length} preferences`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to preload preferences:', error as 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) {
|
if (uncachedKeys.length > 0) {
|
||||||
preferenceService.getMultiple(uncachedKeys).catch((error) => {
|
preferenceService.getMultipleRaw(uncachedKeys).catch((error) => {
|
||||||
logger.error('Failed to load initial preferences:', error as Error)
|
logger.error('Failed to load initial preferences:', error as Error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,25 +21,19 @@ import { useEffect } from 'react'
|
|||||||
import { useDefaultModel } from './useAssistant'
|
import { useDefaultModel } from './useAssistant'
|
||||||
import useFullScreenNotice from './useFullScreenNotice'
|
import useFullScreenNotice from './useFullScreenNotice'
|
||||||
import { useRuntime } from './useRuntime'
|
import { useRuntime } from './useRuntime'
|
||||||
import { useSettings } from './useSettings'
|
|
||||||
import useUpdateHandler from './useUpdateHandler'
|
import useUpdateHandler from './useUpdateHandler'
|
||||||
const logger = loggerService.withContext('useAppInit')
|
const logger = loggerService.withContext('useAppInit')
|
||||||
|
|
||||||
export function useAppInit() {
|
export function useAppInit() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const {
|
|
||||||
proxyUrl,
|
|
||||||
proxyBypassRules,
|
|
||||||
// language,
|
|
||||||
// windowStyle,
|
|
||||||
autoCheckUpdate,
|
|
||||||
proxyMode,
|
|
||||||
// customCss,
|
|
||||||
enableDataCollection
|
|
||||||
} = useSettings()
|
|
||||||
const [language] = usePreference('app.language')
|
const [language] = usePreference('app.language')
|
||||||
const [windowStyle] = usePreference('ui.window_style')
|
const [windowStyle] = usePreference('ui.window_style')
|
||||||
const [customCss] = usePreference('ui.custom_css')
|
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 { minappShow } = useRuntime()
|
||||||
const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel()
|
const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel()
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import store, { useAppSelector } from '@renderer/store'
|
//TODO data refactor
|
||||||
import {
|
// this file will be removed
|
||||||
SettingsState
|
|
||||||
// setWindowStyle
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
} from '@renderer/store/settings'
|
import { useAppSelector } from '@renderer/store'
|
||||||
|
import store from '@renderer/store'
|
||||||
|
import { SettingsState } from '@renderer/store/settings'
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
const settings = useAppSelector((state) => state.settings)
|
const settings = useAppSelector((state) => state.settings)
|
||||||
@ -87,7 +89,7 @@ export function useSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useMessageStyle() {
|
export function useMessageStyle() {
|
||||||
const { messageStyle } = useSettings()
|
const [messageStyle] = usePreference('chat.message.style')
|
||||||
const isBubbleStyle = messageStyle === 'bubble'
|
const isBubbleStyle = messageStyle === 'bubble'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -112,6 +114,6 @@ export const getStoreSetting = (key: keyof SettingsState) => {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export const getEnableDeveloperMode = () => {
|
// export const getEnableDeveloperMode = () => {
|
||||||
return store.getState().settings.enableDeveloperMode
|
// return store.getState().settings.enableDeveloperMode
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
||||||
@ -13,7 +14,6 @@ import { find, isEmpty } from 'lodash'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useAssistant } from './useAssistant'
|
import { useAssistant } from './useAssistant'
|
||||||
import { getStoreSetting } from './useSettings'
|
|
||||||
|
|
||||||
let _activeTopic: Topic
|
let _activeTopic: Topic
|
||||||
let _setActiveTopic: (topic: Topic) => void
|
let _setActiveTopic: (topic: Topic) => void
|
||||||
@ -109,7 +109,7 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) =>
|
|||||||
topicRenamingLocks.add(topicId)
|
topicRenamingLocks.add(topicId)
|
||||||
|
|
||||||
const topic = await getTopicById(topicId)
|
const topic = await getTopicById(topicId)
|
||||||
const enableTopicNaming = getStoreSetting('enableTopicNaming')
|
const enableTopicNaming = await preferenceService.get('topic.naming.enabled')
|
||||||
|
|
||||||
if (isEmpty(topic.messages)) {
|
if (isEmpty(topic.messages)) {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { builtinLanguages, UNKNOWN } from '@renderer/config/translate'
|
import { builtinLanguages, UNKNOWN } from '@renderer/config/translate'
|
||||||
import { useAppSelector } from '@renderer/store'
|
|
||||||
import { TranslateLanguage } from '@renderer/types'
|
import { TranslateLanguage } from '@renderer/types'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { getTranslateOptions } from '@renderer/utils/translate'
|
import { getTranslateOptions } from '@renderer/utils/translate'
|
||||||
@ -16,7 +16,7 @@ const logger = loggerService.withContext('useTranslate')
|
|||||||
* - getLanguageByLangcode: 通过语言代码获取语言对象
|
* - getLanguageByLangcode: 通过语言代码获取语言对象
|
||||||
*/
|
*/
|
||||||
export default function useTranslate() {
|
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 [translateLanguages, setTranslateLanguages] = useState<TranslateLanguage[]>(builtinLanguages)
|
||||||
const [isLoaded, setIsLoaded] = useState(false)
|
const [isLoaded, setIsLoaded] = useState(false)
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import KeyvStorage from '@kangfenmao/keyv-storage'
|
import KeyvStorage from '@kangfenmao/keyv-storage'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
|
||||||
@ -5,8 +6,6 @@ import { startAutoSync } from './services/BackupService'
|
|||||||
import { startNutstoreAutoSync } from './services/NutstoreService'
|
import { startNutstoreAutoSync } from './services/NutstoreService'
|
||||||
import storeSyncService from './services/StoreSyncService'
|
import storeSyncService from './services/StoreSyncService'
|
||||||
import { webTraceService } from './services/WebTraceService'
|
import { webTraceService } from './services/WebTraceService'
|
||||||
import store from './store'
|
|
||||||
|
|
||||||
loggerService.initWindowSource('mainWindow')
|
loggerService.initWindowSource('mainWindow')
|
||||||
|
|
||||||
function initKeyv() {
|
function initKeyv() {
|
||||||
@ -15,13 +14,18 @@ function initKeyv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initAutoSync() {
|
function initAutoSync() {
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
const { webdavAutoSync, localBackupAutoSync, s3 } = store.getState().settings
|
const autoSyncStates = await preferenceService.getMultiple({
|
||||||
const { nutstoreAutoSync } = store.getState().nutstore
|
webdav: 'data.backup.webdav.auto_sync',
|
||||||
if (webdavAutoSync || (s3 && s3.autoSync) || localBackupAutoSync) {
|
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()
|
startAutoSync()
|
||||||
}
|
}
|
||||||
if (nutstoreAutoSync) {
|
if (autoSyncStates.nutstore) {
|
||||||
startNutstoreAutoSync()
|
startNutstoreAutoSync()
|
||||||
}
|
}
|
||||||
}, 8000)
|
}, 8000)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MessageOutlined } from '@ant-design/icons'
|
import { MessageOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||||
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
|
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
|
||||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { getTopicById } from '@renderer/hooks/useTopic'
|
import { getTopicById } from '@renderer/hooks/useTopic'
|
||||||
import { getAssistantById } from '@renderer/services/AssistantService'
|
import { getAssistantById } from '@renderer/services/AssistantService'
|
||||||
@ -19,7 +19,6 @@ import { FC, useEffect, useState } from 'react'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { default as MessageItem } from '../../home/Messages/Message'
|
import { default as MessageItem } from '../../home/Messages/Message'
|
||||||
|
|
||||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
topic?: Topic
|
topic?: Topic
|
||||||
}
|
}
|
||||||
@ -27,7 +26,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
|
const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
|
||||||
const navigate = NavigationService.navigate!
|
const navigate = NavigationService.navigate!
|
||||||
const { handleScroll, containerRef } = useScrollPosition('TopicMessages')
|
const { handleScroll, containerRef } = useScrollPosition('TopicMessages')
|
||||||
const { messageStyle } = useSettings()
|
const [messageStyle] = usePreference('chat.message.style')
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
|
|
||||||
const [topic, setTopic] = useState<Topic | undefined>(_topic)
|
const [topic, setTopic] = useState<Topic | undefined>(_topic)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSearch'
|
import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSearch'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
@ -6,7 +7,6 @@ import { QuickPanelProvider } from '@renderer/components/QuickPanel'
|
|||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
@ -36,7 +36,9 @@ interface Props {
|
|||||||
|
|
||||||
const Chat: FC<Props> = (props) => {
|
const Chat: FC<Props> = (props) => {
|
||||||
const { assistant } = useAssistant(props.assistant.id)
|
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 { showTopics } = useShowTopics()
|
||||||
const { isMultiSelectMode } = useChatContext(props.activeTopic)
|
const { isMultiSelectMode } = useChatContext(props.activeTopic)
|
||||||
const { isTopNavbar } = useNavbarPosition()
|
const { isTopNavbar } = useNavbarPosition()
|
||||||
@ -178,7 +180,8 @@ const Chat: FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useChatMaxWidth = () => {
|
export const useChatMaxWidth = () => {
|
||||||
const { showTopics, topicPosition } = useSettings()
|
const [showTopics] = usePreference('topic.tab.show')
|
||||||
|
const [topicPosition] = usePreference('topic.position')
|
||||||
const { isLeftNavbar } = useNavbarPosition()
|
const { isLeftNavbar } = useNavbarPosition()
|
||||||
const { showAssistants } = useShowAssistants()
|
const { showAssistants } = useShowAssistants()
|
||||||
const showRightTopics = showTopics && topicPosition === 'right'
|
const showRightTopics = showTopics && topicPosition === 'right'
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import NavigationService from '@renderer/services/NavigationService'
|
import NavigationService from '@renderer/services/NavigationService'
|
||||||
@ -30,7 +30,9 @@ const HomePage: FC = () => {
|
|||||||
|
|
||||||
const [activeAssistant, _setActiveAssistant] = useState(state?.assistant || _activeAssistant || assistants[0])
|
const [activeAssistant, _setActiveAssistant] = useState(state?.assistant || _activeAssistant || assistants[0])
|
||||||
const { activeTopic, setActiveTopic: _setActiveTopic } = useActiveTopic(activeAssistant?.id, state?.topic)
|
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()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
_activeAssistant = activeAssistant
|
_activeAssistant = activeAssistant
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { HStack, VStack } from '@renderer/components/Layout'
|
import { HStack, VStack } from '@renderer/components/Layout'
|
||||||
import MaxContextCount from '@renderer/components/MaxContextCount'
|
import MaxContextCount from '@renderer/components/MaxContextCount'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { Divider, Popover } from 'antd'
|
import { Divider, Popover } from 'antd'
|
||||||
import { ArrowUp, MenuIcon } from 'lucide-react'
|
import { ArrowUp, MenuIcon } from 'lucide-react'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
@ -16,7 +16,7 @@ type Props = {
|
|||||||
|
|
||||||
const TokenCount: FC<Props> = ({ estimateTokenCount, inputTokenCount, contextCount }) => {
|
const TokenCount: FC<Props> = ({ estimateTokenCount, inputTokenCount, contextCount }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { showInputEstimatedTokens } = useSettings()
|
const [showInputEstimatedTokens] = usePreference('chat.input.show_estimated_tokens')
|
||||||
|
|
||||||
if (!showInputEstimatedTokens) {
|
if (!showInputEstimatedTokens) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import 'katex/dist/contrib/copy-tex'
|
|||||||
import 'katex/dist/contrib/mhchem'
|
import 'katex/dist/contrib/mhchem'
|
||||||
import 'remark-github-blockquote-alert/alert.css'
|
import 'remark-github-blockquote-alert/alert.css'
|
||||||
|
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import ImageViewer from '@renderer/components/ImageViewer'
|
import ImageViewer from '@renderer/components/ImageViewer'
|
||||||
import MarkdownShadowDOMRenderer from '@renderer/components/MarkdownShadowDOMRenderer'
|
import MarkdownShadowDOMRenderer from '@renderer/components/MarkdownShadowDOMRenderer'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useSmoothStream } from '@renderer/hooks/useSmoothStream'
|
import { useSmoothStream } from '@renderer/hooks/useSmoothStream'
|
||||||
import type { MainTextMessageBlock, ThinkingMessageBlock, TranslationMessageBlock } from '@renderer/types/newMessage'
|
import type { MainTextMessageBlock, ThinkingMessageBlock, TranslationMessageBlock } from '@renderer/types/newMessage'
|
||||||
import { removeSvgEmptyLines } from '@renderer/utils/formats'
|
import { removeSvgEmptyLines } from '@renderer/utils/formats'
|
||||||
@ -46,7 +46,8 @@ interface Props {
|
|||||||
|
|
||||||
const Markdown: FC<Props> = ({ block, postProcess }) => {
|
const Markdown: FC<Props> = ({ block, postProcess }) => {
|
||||||
const { t } = useTranslation()
|
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 isTrulyDone = 'status' in block && block.status === 'success'
|
||||||
const [displayedContent, setDisplayedContent] = useState(postProcess ? postProcess(block.content) : block.content)
|
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 { getModelUniqId } from '@renderer/services/ModelService'
|
||||||
import type { RootState } from '@renderer/store'
|
import type { RootState } from '@renderer/store'
|
||||||
import { selectFormattedCitationsByBlockId } from '@renderer/store/messageBlock'
|
import { selectFormattedCitationsByBlockId } from '@renderer/store/messageBlock'
|
||||||
@ -21,7 +21,7 @@ interface Props {
|
|||||||
|
|
||||||
const MainTextBlock: React.FC<Props> = ({ block, citationBlockId, role, mentions = [] }) => {
|
const MainTextBlock: React.FC<Props> = ({ block, citationBlockId, role, mentions = [] }) => {
|
||||||
// Use the passed citationBlockId directly in the selector
|
// 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))
|
const rawCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId))
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { CheckOutlined } from '@ant-design/icons'
|
import { CheckOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import ThinkingEffect from '@renderer/components/ThinkingEffect'
|
import ThinkingEffect from '@renderer/components/ThinkingEffect'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
|
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
|
||||||
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
|
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||||
import { Collapse, message as antdMessage, Tooltip } from 'antd'
|
import { Collapse, message as antdMessage, Tooltip } from 'antd'
|
||||||
@ -19,7 +19,9 @@ interface Props {
|
|||||||
const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
||||||
const [copied, setCopied] = useTemporaryValue(false, 2000)
|
const [copied, setCopied] = useTemporaryValue(false, 2000)
|
||||||
const { t } = useTranslation()
|
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 [activeKey, setActiveKey] = useState<'thought' | ''>(thoughtAutoCollapse ? '' : 'thought')
|
||||||
|
|
||||||
const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status])
|
const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status])
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import '@xyflow/react/dist/style.css'
|
import '@xyflow/react/dist/style.css'
|
||||||
|
|
||||||
import { RobotOutlined, UserOutlined } from '@ant-design/icons'
|
import { RobotOutlined, UserOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
||||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
import { getModelLogo } from '@renderer/config/models'
|
import { getModelLogo } from '@renderer/config/models'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { RootState } from '@renderer/store'
|
import { RootState } from '@renderer/store'
|
||||||
@ -205,7 +205,7 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
|
|||||||
const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
|
const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
|
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const { userName } = useSettings()
|
const [userName] = usePreference('app.user.name')
|
||||||
const { settedTheme } = useTheme()
|
const { settedTheme } = useTheme()
|
||||||
|
|
||||||
const topicId = conversationId
|
const topicId = conversationId
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
VerticalAlignBottomOutlined,
|
VerticalAlignBottomOutlined,
|
||||||
VerticalAlignTopOutlined
|
VerticalAlignTopOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { RootState } from '@renderer/store'
|
import { RootState } from '@renderer/store'
|
||||||
// import { selectCurrentTopicId } from '@renderer/store/newMessage'
|
// import { selectCurrentTopicId } from '@renderer/store/newMessage'
|
||||||
import { Button, Drawer, Tooltip } from 'antd'
|
import { Button, Drawer, Tooltip } from 'antd'
|
||||||
@ -44,7 +44,8 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
const [manuallyClosedUntil, setManuallyClosedUntil] = useState<number | null>(null)
|
const [manuallyClosedUntil, setManuallyClosedUntil] = useState<number | null>(null)
|
||||||
const currentTopicId = useSelector((state: RootState) => state.messages.currentTopicId)
|
const currentTopicId = useSelector((state: RootState) => state.messages.currentTopicId)
|
||||||
const lastMoveTime = useRef(0)
|
const lastMoveTime = useRef(0)
|
||||||
const { topicPosition, showTopics } = useSettings()
|
const [topicPosition] = usePreference('topic.position')
|
||||||
|
const [showTopics] = usePreference('topic.tab.show')
|
||||||
const showRightTopics = topicPosition === 'right' && showTopics
|
const showRightTopics = topicPosition === 'right' && showTopics
|
||||||
|
|
||||||
// Reset hide timer and make buttons visible
|
// Reset hide timer and make buttons visible
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
||||||
@ -5,7 +6,6 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
|
|||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||||
import { useModel } from '@renderer/hooks/useModel'
|
import { useModel } from '@renderer/hooks/useModel'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getMessageModelId } from '@renderer/services/MessagesService'
|
import { getMessageModelId } from '@renderer/services/MessagesService'
|
||||||
@ -67,7 +67,12 @@ const MessageItem: FC<Props> = ({
|
|||||||
const { assistant, setModel } = useAssistant(message.assistantId)
|
const { assistant, setModel } = useAssistant(message.assistantId)
|
||||||
const { isMultiSelectMode } = useChatContext(topic)
|
const { isMultiSelectMode } = useChatContext(topic)
|
||||||
const model = useModel(getMessageModelId(message), message.model?.provider) || message.model
|
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 { editMessageBlocks, resendUserMessageWithEdit, editMessage } = useMessageOperations(topic)
|
||||||
const messageContainerRef = useRef<HTMLDivElement>(null)
|
const messageContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const { editingMessageId, stopEditing } = useMessageEditing()
|
const { editingMessageId, stopEditing } = useMessageEditing()
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
||||||
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
|
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
|
||||||
import { getModelLogo } from '@renderer/config/models'
|
import { getModelLogo } from '@renderer/config/models'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { getMessageModelId } from '@renderer/services/MessagesService'
|
import { getMessageModelId } from '@renderer/services/MessagesService'
|
||||||
import { getModelName } from '@renderer/services/ModelService'
|
import { getModelName } from '@renderer/services/ModelService'
|
||||||
@ -33,7 +33,7 @@ const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
|
|||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { userName } = useSettings()
|
const [userName] = usePreference('app.user.name')
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
|
|
||||||
const messagesListRef = useRef<HTMLDivElement>(null)
|
const messagesListRef = useRef<HTMLDivElement>(null)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
|
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import PasteService from '@renderer/services/PasteService'
|
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 [isFileDragging, setIsFileDragging] = useState(false)
|
||||||
const { assistant } = useAssistant(message.assistantId)
|
const { assistant } = useAssistant(message.assistantId)
|
||||||
const model = assistant.model || assistant.defaultModel
|
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 { t } = useTranslation()
|
||||||
const textareaRef = useRef<TextAreaRef>(null)
|
const textareaRef = useRef<TextAreaRef>(null)
|
||||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
|
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
|
||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
|
||||||
import type { Topic } from '@renderer/types'
|
import type { Topic } from '@renderer/types'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
import { classNames } from '@renderer/utils'
|
import { classNames } from '@renderer/utils'
|
||||||
|
import type { MultiModelMessageStyle } from '@shared/data/preferenceTypes'
|
||||||
import { Popover } from 'antd'
|
import { Popover } from 'antd'
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -30,7 +30,9 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => {
|
|||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const { editMessage } = useMessageOperations(topic)
|
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 { isMultiSelectMode } = useChatContext(topic)
|
||||||
const maxWidth = useChatMaxWidth()
|
const maxWidth = useChatMaxWidth()
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
|
|||||||
@ -9,11 +9,11 @@ import {
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||||
import { MultiModelMessageStyle } from '@renderer/store/settings'
|
|
||||||
import type { Topic } from '@renderer/types'
|
import type { Topic } from '@renderer/types'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
import { AssistantMessageStatus } from '@renderer/types/newMessage'
|
import { AssistantMessageStatus } from '@renderer/types/newMessage'
|
||||||
import { getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
|
import { MultiModelMessageStyle } from '@shared/data/preferenceTypes'
|
||||||
import { Button, Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
import { FC, memo } from 'react'
|
import { FC, memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// import { InfoCircleOutlined } from '@ant-design/icons'
|
// import { InfoCircleOutlined } from '@ant-design/icons'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { useMultiplePreferences } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons'
|
import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons'
|
||||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
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 { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
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 { messageBlocksSelectors, removeOneBlock } from '@renderer/store/messageBlock'
|
||||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||||
import { TraceIcon } from '@renderer/trace/pages/Component'
|
import { TraceIcon } from '@renderer/trace/pages/Component'
|
||||||
@ -107,7 +108,19 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
|
|
||||||
const isUserMessage = message.role === 'user'
|
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 dispatch = useAppDispatch()
|
||||||
|
|
||||||
// const processedMessage = useMemo(() => {
|
// const processedMessage = useMemo(() => {
|
||||||
@ -263,7 +276,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
key: 'clipboard',
|
key: 'clipboard',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = await messageToMarkdown(message)
|
||||||
exportMessageToNotes(title, markdown, notesPath)
|
exportMessageToNotes(title, markdown, notesPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,7 +328,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
label: t('chat.topics.export.word'),
|
label: t('chat.topics.export.word'),
|
||||||
key: 'word',
|
key: 'word',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = await messageToMarkdown(message)
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
window.api.export.toWord(markdown, title)
|
window.api.export.toWord(markdown, title)
|
||||||
}
|
}
|
||||||
@ -325,7 +338,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
key: 'notion',
|
key: 'notion',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = await messageToMarkdown(message)
|
||||||
exportMessageToNotion(title, markdown, message)
|
exportMessageToNotion(title, markdown, message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -334,7 +347,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
key: 'yuque',
|
key: 'yuque',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = await messageToMarkdown(message)
|
||||||
exportMarkdownToYuque(title, markdown)
|
exportMarkdownToYuque(title, markdown)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -359,7 +372,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
key: 'siyuan',
|
key: 'siyuan',
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const markdown = messageToMarkdown(message)
|
const markdown = await messageToMarkdown(message)
|
||||||
exportMarkdownToSiyuan(title, markdown)
|
exportMarkdownToSiyuan(title, markdown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { CopyIcon, LoadingIcon } from '@renderer/components/Icons'
|
import { CopyIcon, LoadingIcon } from '@renderer/components/Icons'
|
||||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import type { ToolMessageBlock } from '@renderer/types/newMessage'
|
import type { ToolMessageBlock } from '@renderer/types/newMessage'
|
||||||
import { isToolAutoApproved } from '@renderer/utils/mcp-tools'
|
import { isToolAutoApproved } from '@renderer/utils/mcp-tools'
|
||||||
@ -49,7 +49,8 @@ const MessageTools: FC<Props> = ({ block }) => {
|
|||||||
const [copiedMap, setCopiedMap] = useState<Record<string, boolean>>({})
|
const [copiedMap, setCopiedMap] = useState<Record<string, boolean>>({})
|
||||||
const [countdown, setCountdown] = useState<number>(COUNTDOWN_TIME)
|
const [countdown, setCountdown] = useState<number>(COUNTDOWN_TIME)
|
||||||
const { t } = useTranslation()
|
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 { mcpServers, updateMCPServer } = useMCPServers()
|
||||||
const [expandedResponse, setExpandedResponse] = useState<{ content: string; title: string } | null>(null)
|
const [expandedResponse, setExpandedResponse] = useState<{ content: string; title: string } | null>(null)
|
||||||
const [progress, setProgress] = useState<number>(0)
|
const [progress, setProgress] = useState<number>(0)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import ContextMenu from '@renderer/components/ContextMenu'
|
import ContextMenu from '@renderer/components/ContextMenu'
|
||||||
import { LoadingIcon } from '@renderer/components/Icons'
|
import { LoadingIcon } from '@renderer/components/Icons'
|
||||||
@ -7,7 +8,6 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
|
|||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
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 [isProcessingContext, setIsProcessingContext] = useState(false)
|
||||||
|
|
||||||
const { updateTopic, addTopic } = useAssistant(assistant.id)
|
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 { t } = useTranslation()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const messages = useTopicMessages(topic.id)
|
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 { FC, HTMLAttributes } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NarrowLayout: FC<Props> = ({ children, ...props }) => {
|
const NarrowLayout: FC<Props> = ({ children, ...props }) => {
|
||||||
const { narrowMode } = useSettings()
|
const [narrowMode] = usePreference('chat.narrow_mode')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={`narrow-mode ${narrowMode ? 'active' : ''}`} {...props}>
|
<Container className={`narrow-mode ${narrowMode ? 'active' : ''}`} {...props}>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { useMultiplePreferences } from '@data/hooks/usePreference'
|
||||||
import { DraggableVirtualList } from '@renderer/components/DraggableList'
|
import { DraggableVirtualList } from '@renderer/components/DraggableList'
|
||||||
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
||||||
@ -191,7 +192,18 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
|
|||||||
[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, setTargetTopic] = useState<Topic | null>(null)
|
||||||
const targetTopic = useDeferredValue(_targetTopic)
|
const targetTopic = useDeferredValue(_targetTopic)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
|
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
|
||||||
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
import { useNavbarPosition } from '@renderer/hooks/useNavbar'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
@ -39,7 +39,7 @@ const HomeTabs: FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { addAssistant } = useAssistants()
|
const { addAssistant } = useAssistants()
|
||||||
const [tab, setTab] = useState<Tab>(position === 'left' ? _tab || 'assistants' : 'topic')
|
const [tab, setTab] = useState<Tab>(position === 'left' ? _tab || 'assistants' : 'topic')
|
||||||
const { topicPosition } = useSettings()
|
const [topicPosition] = usePreference('topic.position')
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
const { toggleShowTopics } = useShowTopics()
|
const { toggleShowTopics } = useShowTopics()
|
||||||
const { isLeftNavbar } = useNavbarPosition()
|
const { isLeftNavbar } = useNavbarPosition()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { SyncOutlined } from '@ant-design/icons'
|
import { SyncOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -8,7 +8,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
const UpdateAppButton: FC = () => {
|
const UpdateAppButton: FC = () => {
|
||||||
const { update } = useRuntime()
|
const { update } = useRuntime()
|
||||||
const { autoCheckUpdate } = useSettings()
|
const [autoCheckUpdate] = usePreference('app.dist.auto_update.enabled')
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!update) {
|
if (!update) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
|
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
|
||||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
|
||||||
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
||||||
import { KnowledgeBase, PreprocessProviderId } from '@renderer/types'
|
import { KnowledgeBase, PreprocessProviderId } from '@renderer/types'
|
||||||
import { Tag } from 'antd'
|
import { Tag } from 'antd'
|
||||||
@ -28,7 +28,7 @@ const QuotaTag: FC<{ base: KnowledgeBase; providerId: PreprocessProviderId; quot
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (quota === undefined) {
|
if (quota === undefined) {
|
||||||
const userId = getStoreSetting('userId')
|
const userId = await preferenceService.get('app.user.id')
|
||||||
const baseParams = getKnowledgeBaseParams(base)
|
const baseParams = getKnowledgeBaseParams(base)
|
||||||
try {
|
try {
|
||||||
const response = await window.api.knowledgeBase.checkQuota({
|
const response = await window.api.knowledgeBase.checkQuota({
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
|
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import AiProvider from '@renderer/aiCore'
|
import AiProvider from '@renderer/aiCore'
|
||||||
import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg'
|
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 { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
@ -94,7 +94,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { autoTranslateWithSpace } = useSettings()
|
const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space')
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
const aihubmixProvider = providers.find((p) => p.id === 'aihubmix')!
|
const aihubmixProvider = providers.find((p) => p.id === 'aihubmix')!
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons'
|
import { PlusOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import AiProvider from '@renderer/aiCore'
|
import AiProvider from '@renderer/aiCore'
|
||||||
import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg'
|
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 { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import {
|
import {
|
||||||
getPaintingsBackgroundOptionsLabel,
|
getPaintingsBackgroundOptionsLabel,
|
||||||
getPaintingsImageSizeOptionsLabel,
|
getPaintingsImageSizeOptionsLabel,
|
||||||
@ -85,7 +85,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { autoTranslateWithSpace } = useSettings()
|
const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space')
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
const newApiProvider = providers.find((p) => p.id === 'new-api')!
|
const newApiProvider = providers.find((p) => p.id === 'new-api')!
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
|
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import AiProvider from '@renderer/aiCore'
|
import AiProvider from '@renderer/aiCore'
|
||||||
import ImageSize1_1 from '@renderer/assets/images/paintings/image-size-1-1.svg'
|
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 { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
@ -303,7 +303,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
setCurrentImageIndex(0)
|
setCurrentImageIndex(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { autoTranslateWithSpace } = useSettings()
|
const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space')
|
||||||
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons'
|
import { PlusOutlined } from '@ant-design/icons'
|
||||||
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
@ -9,7 +10,6 @@ import { LanguagesEnum } from '@renderer/config/translate'
|
|||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { translateText } from '@renderer/services/TranslateService'
|
import { translateText } from '@renderer/services/TranslateService'
|
||||||
@ -74,7 +74,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
const { generating } = useRuntime()
|
const { generating } = useRuntime()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { autoTranslateWithSpace } = useSettings()
|
const [autoTranslateWithSpace] = usePreference('chat.input.translate.auto_translate_with_space')
|
||||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||||
const tokenfluxProvider = providers.find((p) => p.id === 'tokenflux')!
|
const tokenfluxProvider = providers.find((p) => p.id === 'tokenflux')!
|
||||||
const textareaRef = useRef<any>(null)
|
const textareaRef = useRef<any>(null)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import store from '@renderer/store'
|
|
||||||
import { WebSearchState } from '@renderer/store/websearch'
|
import { WebSearchState } from '@renderer/store/websearch'
|
||||||
import { WebSearchProvider, WebSearchProviderResponse, WebSearchProviderResult } from '@renderer/types'
|
import { WebSearchProvider, WebSearchProviderResponse, WebSearchProviderResult } from '@renderer/types'
|
||||||
import { createAbortPromise } from '@renderer/utils/abortController'
|
import { createAbortPromise } from '@renderer/utils/abortController'
|
||||||
@ -30,7 +30,7 @@ export default class LocalSearchProvider extends BaseWebSearchProvider {
|
|||||||
httpOptions?: RequestInit
|
httpOptions?: RequestInit
|
||||||
): Promise<WebSearchProviderResponse> {
|
): Promise<WebSearchProviderResponse> {
|
||||||
const uid = nanoid()
|
const uid = nanoid()
|
||||||
const language = store.getState().settings.language
|
const language = await preferenceService.get('app.language')
|
||||||
try {
|
try {
|
||||||
if (!query.trim()) {
|
if (!query.trim()) {
|
||||||
throw new Error('Search query cannot be empty')
|
throw new Error('Search query cannot be empty')
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
|
||||||
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
||||||
import { NotificationService } from '@renderer/services/NotificationService'
|
import { NotificationService } from '@renderer/services/NotificationService'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
@ -96,7 +96,7 @@ class KnowledgeQueue {
|
|||||||
|
|
||||||
private async processItem(baseId: string, item: KnowledgeItem): Promise<void> {
|
private async processItem(baseId: string, item: KnowledgeItem): Promise<void> {
|
||||||
const notificationService = NotificationService.getInstance()
|
const notificationService = NotificationService.getInstance()
|
||||||
const userId = getStoreSetting('userId')
|
const userId = await preferenceService.get('app.user.id')
|
||||||
try {
|
try {
|
||||||
if (item.retryCount && item.retryCount >= this.MAX_RETRIES) {
|
if (item.retryCount && item.retryCount >= this.MAX_RETRIES) {
|
||||||
logger.info(`Item ${item.id} has reached max retries, skipping`)
|
logger.info(`Item ${item.id} has reached max retries, skipping`)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { CompletionsParams } from '@renderer/aiCore/middleware/schemas'
|
import { CompletionsParams } from '@renderer/aiCore/middleware/schemas'
|
||||||
import { SYSTEM_PROMPT_THRESHOLD } from '@renderer/config/constant'
|
import { SYSTEM_PROMPT_THRESHOLD } from '@renderer/config/constant'
|
||||||
@ -12,7 +13,6 @@ import {
|
|||||||
isWebSearchModel
|
isWebSearchModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
import { getModel } from '@renderer/hooks/useModel'
|
import { getModel } from '@renderer/hooks/useModel'
|
||||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { currentSpan, withSpanResult } from '@renderer/services/SpanManagerService'
|
import { currentSpan, withSpanResult } from '@renderer/services/SpanManagerService'
|
||||||
import store from '@renderer/store'
|
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 }) {
|
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()
|
const model = getQuickModel() || assistant.model || getDefaultModel()
|
||||||
|
|
||||||
if (prompt && containsSupportedVariables(prompt)) {
|
if (prompt && containsSupportedVariables(prompt)) {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import {
|
import {
|
||||||
DEFAULT_CONTEXTCOUNT,
|
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 model = getTranslateModel()
|
||||||
const assistant: Assistant = getDefaultAssistant()
|
const assistant: Assistant = getDefaultAssistant()
|
||||||
|
|
||||||
@ -77,10 +81,8 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage,
|
|||||||
prompt = ''
|
prompt = ''
|
||||||
} else {
|
} else {
|
||||||
content = 'follow system instruction'
|
content = 'follow system instruction'
|
||||||
prompt = store
|
const translateModelPrompt = await preferenceService.get('feature.translate.model_prompt')
|
||||||
.getState()
|
prompt = translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value).replaceAll('{{text}}', text)
|
||||||
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
|
|
||||||
.replaceAll('{{text}}', text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateAssistant = {
|
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 { loggerService } from '@logger'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades'
|
import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades'
|
||||||
@ -164,7 +168,16 @@ export async function backupToWebdav({
|
|||||||
webdavMaxBackups,
|
webdavMaxBackups,
|
||||||
webdavSkipBackupFile,
|
webdavSkipBackupFile,
|
||||||
webdavDisableStream
|
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 deviceType = 'unknown'
|
||||||
let hostname = 'unknown'
|
let hostname = 'unknown'
|
||||||
try {
|
try {
|
||||||
@ -294,7 +307,12 @@ export async function backupToWebdav({
|
|||||||
|
|
||||||
// 从 webdav 恢复
|
// 从 webdav 恢复
|
||||||
export async function restoreFromWebdav(fileName?: string) {
|
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 = ''
|
let data = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -334,7 +352,18 @@ export async function backupToS3({
|
|||||||
|
|
||||||
store.dispatch(setS3SyncState({ syncing: true, lastSyncError: null }))
|
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 deviceType = 'unknown'
|
||||||
let hostname = 'unknown'
|
let hostname = 'unknown'
|
||||||
try {
|
try {
|
||||||
@ -445,7 +474,18 @@ export async function backupToS3({
|
|||||||
|
|
||||||
// 从 S3 恢复
|
// 从 S3 恢复
|
||||||
export async function restoreFromS3(fileName?: string) {
|
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) {
|
if (!fileName) {
|
||||||
const files = await window.api.backup.listS3Files(s3Config)
|
const files = await window.api.backup.listS3Files(s3Config)
|
||||||
@ -481,12 +521,22 @@ let isLocalAutoBackupRunning = false
|
|||||||
|
|
||||||
type BackupType = 'webdav' | 's3' | 'local'
|
type BackupType = 'webdav' | 's3' | 'local'
|
||||||
|
|
||||||
export function startAutoSync(immediate = false, type?: BackupType) {
|
export async function startAutoSync(immediate = false, type?: BackupType) {
|
||||||
// 如果没有指定类型,启动所有配置的自动同步
|
// 如果没有指定类型,启动所有配置的自动同步
|
||||||
if (!type) {
|
if (!type) {
|
||||||
const settings = store.getState().settings
|
const { webdavAutoSync, webdavHost, localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({
|
||||||
const { webdavAutoSync, webdavHost, localBackupAutoSync, localBackupDir } = settings
|
webdavAutoSync: 'data.backup.webdav.auto_sync',
|
||||||
const s3Settings = settings.s3
|
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) {
|
if (webdavAutoSync && webdavHost) {
|
||||||
startAutoSync(immediate, 'webdav')
|
startAutoSync(immediate, 'webdav')
|
||||||
@ -506,8 +556,10 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = store.getState().settings
|
const { webdavAutoSync, webdavHost } = await preferenceService.getMultiple({
|
||||||
const { webdavAutoSync, webdavHost } = settings
|
webdavAutoSync: 'data.backup.webdav.auto_sync',
|
||||||
|
webdavHost: 'data.backup.webdav.host'
|
||||||
|
})
|
||||||
|
|
||||||
if (!webdavAutoSync || !webdavHost) {
|
if (!webdavAutoSync || !webdavHost) {
|
||||||
logger.info('[WebdavAutoSync] Invalid sync settings, auto sync disabled')
|
logger.info('[WebdavAutoSync] Invalid sync settings, auto sync disabled')
|
||||||
@ -522,8 +574,10 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = store.getState().settings
|
const s3Settings = await preferenceService.getMultiple({
|
||||||
const s3Settings = settings.s3
|
autoSync: 'data.backup.s3.auto_sync',
|
||||||
|
endpoint: 'data.backup.s3.endpoint'
|
||||||
|
})
|
||||||
|
|
||||||
if (!s3Settings?.autoSync || !s3Settings?.endpoint) {
|
if (!s3Settings?.autoSync || !s3Settings?.endpoint) {
|
||||||
logger.verbose('Invalid sync settings, auto sync disabled')
|
logger.verbose('Invalid sync settings, auto sync disabled')
|
||||||
@ -538,8 +592,10 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = store.getState().settings
|
const { localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({
|
||||||
const { localBackupAutoSync, localBackupDir } = settings
|
localBackupAutoSync: 'data.backup.local.auto_sync',
|
||||||
|
localBackupDir: 'data.backup.local.dir'
|
||||||
|
})
|
||||||
|
|
||||||
if (!localBackupAutoSync || !localBackupDir) {
|
if (!localBackupAutoSync || !localBackupDir) {
|
||||||
logger.verbose('Invalid sync settings, auto sync disabled')
|
logger.verbose('Invalid sync settings, auto sync disabled')
|
||||||
@ -551,13 +607,15 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
scheduleNextBackup(immediate ? 'immediate' : 'fromLastSyncTime', 'local')
|
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 syncInterval: number
|
||||||
let lastSyncTime: number | undefined
|
let lastSyncTime: number | undefined
|
||||||
let logPrefix: string
|
let logPrefix: string
|
||||||
|
|
||||||
// 根据备份类型获取相应的配置和状态
|
// 根据备份类型获取相应的配置和状态
|
||||||
const settings = store.getState().settings
|
|
||||||
const backup = store.getState().backup
|
const backup = store.getState().backup
|
||||||
|
|
||||||
if (backupType === 'webdav') {
|
if (backupType === 'webdav') {
|
||||||
@ -565,7 +623,7 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
clearTimeout(webdavSyncTimeout)
|
clearTimeout(webdavSyncTimeout)
|
||||||
webdavSyncTimeout = null
|
webdavSyncTimeout = null
|
||||||
}
|
}
|
||||||
syncInterval = settings.webdavSyncInterval
|
syncInterval = await preferenceService.get('data.backup.webdav.sync_interval')
|
||||||
lastSyncTime = backup.webdavSync?.lastSyncTime || undefined
|
lastSyncTime = backup.webdavSync?.lastSyncTime || undefined
|
||||||
logPrefix = '[WebdavAutoSync]'
|
logPrefix = '[WebdavAutoSync]'
|
||||||
} else if (backupType === 's3') {
|
} else if (backupType === 's3') {
|
||||||
@ -573,7 +631,7 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
clearTimeout(s3SyncTimeout)
|
clearTimeout(s3SyncTimeout)
|
||||||
s3SyncTimeout = null
|
s3SyncTimeout = null
|
||||||
}
|
}
|
||||||
syncInterval = settings.s3?.syncInterval || 0
|
syncInterval = await preferenceService.get('data.backup.s3.sync_interval')
|
||||||
lastSyncTime = backup.s3Sync?.lastSyncTime || undefined
|
lastSyncTime = backup.s3Sync?.lastSyncTime || undefined
|
||||||
logPrefix = '[S3AutoSync]'
|
logPrefix = '[S3AutoSync]'
|
||||||
} else if (backupType === 'local') {
|
} else if (backupType === 'local') {
|
||||||
@ -581,7 +639,7 @@ export function startAutoSync(immediate = false, type?: BackupType) {
|
|||||||
clearTimeout(localSyncTimeout)
|
clearTimeout(localSyncTimeout)
|
||||||
localSyncTimeout = null
|
localSyncTimeout = null
|
||||||
}
|
}
|
||||||
syncInterval = settings.localBackupSyncInterval
|
syncInterval = await preferenceService.get('data.backup.local.sync_interval')
|
||||||
lastSyncTime = backup.localBackupSync?.lastSyncTime || undefined
|
lastSyncTime = backup.localBackupSync?.lastSyncTime || undefined
|
||||||
logPrefix = '[LocalAutoSync]'
|
logPrefix = '[LocalAutoSync]'
|
||||||
} else {
|
} else {
|
||||||
@ -921,11 +979,12 @@ export async function backupToLocal({
|
|||||||
|
|
||||||
store.dispatch(setLocalBackupSyncState({ syncing: true, lastSyncError: null }))
|
store.dispatch(setLocalBackupSyncState({ syncing: true, lastSyncError: null }))
|
||||||
|
|
||||||
const {
|
const { localBackupDirSetting, localBackupMaxBackups, localBackupSkipBackupFile } =
|
||||||
localBackupDir: localBackupDirSetting,
|
await preferenceService.getMultiple({
|
||||||
localBackupMaxBackups,
|
localBackupDirSetting: 'data.backup.local.dir',
|
||||||
localBackupSkipBackupFile
|
localBackupMaxBackups: 'data.backup.local.max_backups',
|
||||||
} = store.getState().settings
|
localBackupSkipBackupFile: 'data.backup.local.skip_backup_file'
|
||||||
|
})
|
||||||
const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
|
const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
|
||||||
let deviceType = 'unknown'
|
let deviceType = 'unknown'
|
||||||
let hostname = 'unknown'
|
let hostname = 'unknown'
|
||||||
@ -1049,7 +1108,7 @@ export async function backupToLocal({
|
|||||||
|
|
||||||
export async function restoreFromLocal(fileName: string) {
|
export async function restoreFromLocal(fileName: string) {
|
||||||
try {
|
try {
|
||||||
const { localBackupDir: localBackupDirSetting } = store.getState().settings
|
const localBackupDirSetting = await preferenceService.get('data.backup.local.dir')
|
||||||
const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
|
const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
|
||||||
const restoreData = await window.api.backup.restoreFromLocalBackup(fileName, localBackupDir)
|
const restoreData = await window.api.backup.restoreFromLocalBackup(fileName, localBackupDir)
|
||||||
const data = JSON.parse(restoreData)
|
const data = JSON.parse(restoreData)
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import store from '@renderer/store'
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { initialState as defaultNotificationSettings } from '@renderer/store/settings'
|
|
||||||
import type { Notification } from '@renderer/types/notification'
|
import type { Notification } from '@renderer/types/notification'
|
||||||
|
|
||||||
import { NotificationQueue } from '../queue/NotificationQueue'
|
import { NotificationQueue } from '../queue/NotificationQueue'
|
||||||
@ -25,7 +24,11 @@ export class NotificationService {
|
|||||||
* @param notification 要发送的通知
|
* @param notification 要发送的通知
|
||||||
*/
|
*/
|
||||||
public async send(notification: Notification): Promise<void> {
|
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]) {
|
if (notificationSettings[notification.source]) {
|
||||||
this.queue.add(notification)
|
this.queue.add(notification)
|
||||||
|
|||||||
@ -87,7 +87,7 @@ export const translateText = async (
|
|||||||
abortKey?: string
|
abortKey?: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
const assistant = await getDefaultTranslateAssistant(targetLanguage, text)
|
||||||
|
|
||||||
const translatedText = await fetchTranslate({ assistant, onResponse, abortKey })
|
const translatedText = await fetchTranslate({ assistant, onResponse, abortKey })
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import llm from './llm'
|
|||||||
import mcp from './mcp'
|
import mcp from './mcp'
|
||||||
import memory from './memory'
|
import memory from './memory'
|
||||||
import messageBlocksReducer from './messageBlock'
|
import messageBlocksReducer from './messageBlock'
|
||||||
import migrate from './migrate'
|
// import migrate from './migrate'
|
||||||
import minapps from './minapps'
|
import minapps from './minapps'
|
||||||
import newMessagesReducer from './newMessage'
|
import newMessagesReducer from './newMessage'
|
||||||
import { setNotesPath } from './note'
|
import { setNotesPath } from './note'
|
||||||
@ -68,8 +68,8 @@ const persistedReducer = persistReducer(
|
|||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 144,
|
version: 144,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs']
|
||||||
migrate
|
// migrate
|
||||||
},
|
},
|
||||||
rootReducer
|
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]
|
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' }, [
|
const msgWithEmptyContent = createMessage({ role: 'user', id: 'empty_block' }, [
|
||||||
{ type: MessageBlockType.MAIN_TEXT, content: '' }
|
{ type: MessageBlockType.MAIN_TEXT, content: '' }
|
||||||
])
|
])
|
||||||
const markdown = messageToMarkdown(msgWithEmptyContent)
|
const markdown = await messageToMarkdown(msgWithEmptyContent)
|
||||||
expect(markdown).toContain('## 🧑💻 User')
|
expect(markdown).toContain('## 🧑💻 User')
|
||||||
// Should handle empty content gracefully
|
// Should handle empty content gracefully
|
||||||
expect(markdown).toBeDefined()
|
expect(markdown).toBeDefined()
|
||||||
expect(markdown.split('\n\n').filter((s) => s.trim()).length).toBeGreaterThanOrEqual(1)
|
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')
|
const msg = mockedMessages.find((m) => m.id === 'u1')
|
||||||
expect(msg).toBeDefined()
|
expect(msg).toBeDefined()
|
||||||
const markdown = messageToMarkdown(msg!)
|
const markdown = await messageToMarkdown(msg!)
|
||||||
expect(markdown).toContain('## 🧑💻 User')
|
expect(markdown).toContain('## 🧑💻 User')
|
||||||
expect(markdown).toContain('hello user')
|
expect(markdown).toContain('hello user')
|
||||||
|
|
||||||
@ -281,10 +281,10 @@ describe('export', () => {
|
|||||||
expect(sections.length).toBeGreaterThanOrEqual(2) // title section and content section
|
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')
|
const msg = mockedMessages.find((m) => m.id === 'a1')
|
||||||
expect(msg).toBeDefined()
|
expect(msg).toBeDefined()
|
||||||
const markdown = messageToMarkdown(msg!)
|
const markdown = await messageToMarkdown(msg!)
|
||||||
expect(markdown).toContain('## 🤖 Assistant')
|
expect(markdown).toContain('## 🤖 Assistant')
|
||||||
expect(markdown).toContain('hi assistant')
|
expect(markdown).toContain('hi assistant')
|
||||||
|
|
||||||
@ -294,21 +294,21 @@ describe('export', () => {
|
|||||||
expect(sections.length).toBeGreaterThanOrEqual(2) // title section and content section
|
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' }, [])
|
const msg = createMessage({ role: 'user', id: 'u2' }, [])
|
||||||
mockedMessages.push(msg)
|
mockedMessages.push(msg)
|
||||||
const markdown = messageToMarkdown(msg)
|
const markdown = await messageToMarkdown(msg)
|
||||||
expect(markdown).toContain('## 🧑💻 User')
|
expect(markdown).toContain('## 🧑💻 User')
|
||||||
// Check that it doesn't fail when no content exists
|
// Check that it doesn't fail when no content exists
|
||||||
expect(markdown).toBeDefined()
|
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' }, [
|
const msgWithCitation = createMessage({ role: 'assistant', id: 'a_cite' }, [
|
||||||
{ type: MessageBlockType.MAIN_TEXT, content: 'Main content' },
|
{ type: MessageBlockType.MAIN_TEXT, content: 'Main content' },
|
||||||
{ type: MessageBlockType.CITATION }
|
{ type: MessageBlockType.CITATION }
|
||||||
])
|
])
|
||||||
const markdown = messageToMarkdown(msgWithCitation)
|
const markdown = await messageToMarkdown(msgWithCitation)
|
||||||
expect(markdown).toContain('## 🤖 Assistant')
|
expect(markdown).toContain('## 🤖 Assistant')
|
||||||
expect(markdown).toContain('Main content')
|
expect(markdown).toContain('Main content')
|
||||||
expect(markdown).toContain('[1] [https://example1.com](Example Citation 1)')
|
expect(markdown).toContain('[1] [https://example1.com](Example Citation 1)')
|
||||||
@ -337,10 +337,10 @@ describe('export', () => {
|
|||||||
mockedMessages = [msgWithReasoning, msgWithThinkTag, msgWithoutReasoning, msgWithReasoningAndCitation]
|
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')
|
const msg = mockedMessages.find((m) => m.id === 'a2')
|
||||||
expect(msg).toBeDefined()
|
expect(msg).toBeDefined()
|
||||||
const markdown = messageToMarkdownWithReasoning(msg!)
|
const markdown = await messageToMarkdownWithReasoning(msg!)
|
||||||
expect(markdown).toContain('## 🤖 Assistant')
|
expect(markdown).toContain('## 🤖 Assistant')
|
||||||
expect(markdown).toContain('Main Answer')
|
expect(markdown).toContain('Main Answer')
|
||||||
expect(markdown).toContain('<details')
|
expect(markdown).toContain('<details')
|
||||||
@ -404,9 +404,9 @@ describe('export', () => {
|
|||||||
mockedMessages = [userMsg, assistantMsg, singleUserMsg]
|
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 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('User query A')
|
||||||
expect(markdown).toContain('Assistant response B')
|
expect(markdown).toContain('Assistant response B')
|
||||||
|
|
||||||
@ -414,13 +414,13 @@ describe('export', () => {
|
|||||||
expect(markdown.split('\n---\n').length).toBe(2)
|
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('')
|
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 msgs = mockedMessages.filter((m) => m.id === 'u4')
|
||||||
const markdown = messagesToMarkdown(msgs)
|
const markdown = await messagesToMarkdown(msgs)
|
||||||
expect(markdown).toContain('Single user query')
|
expect(markdown).toContain('Single user query')
|
||||||
expect(markdown.split('\n\n---\n\n').length).toBe(1)
|
expect(markdown.split('\n\n---\n\n').length).toBe(1)
|
||||||
})
|
})
|
||||||
@ -458,7 +458,7 @@ describe('export', () => {
|
|||||||
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
const { TopicManager } = await import('@renderer/hooks/useTopic')
|
||||||
;(TopicManager.getTopicMessages as any).mockResolvedValue([userMsg, assistantMsg])
|
;(TopicManager.getTopicMessages as any).mockResolvedValue([userMsg, assistantMsg])
|
||||||
// Specific mock for this test to check formatting
|
// 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)
|
const plainText = await topicToPlainText(testTopic)
|
||||||
|
|
||||||
@ -471,13 +471,13 @@ describe('export', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('messageToPlainText', () => {
|
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' }, [
|
const testMessage = createMessage({ role: 'user', id: 'single_msg_plain' }, [
|
||||||
{ type: MessageBlockType.MAIN_TEXT, content: '### Single Message Content' }
|
{ 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(result).toBe('Single Message Content')
|
||||||
expect(markdownToPlainText).toHaveBeenCalledWith('### Single Message Content')
|
expect(markdownToPlainText).toHaveBeenCalledWith('### Single Message Content')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { preferenceService } from '@data/PreferenceService'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { Client } from '@notionhq/client'
|
import { Client } from '@notionhq/client'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
@ -159,9 +160,11 @@ export function getTitleFromString(str: string, length: number = 80): string {
|
|||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoleText = (role: string, modelName?: string, providerId?: string): string => {
|
const getRoleText = async (role: string, modelName?: string, providerId?: string): Promise<string> => {
|
||||||
const { showModelNameInMarkdown, showModelProviderInMarkdown } = store.getState().settings
|
const { showModelNameInMarkdown, showModelProviderInMarkdown } = await preferenceService.getMultiple({
|
||||||
|
showModelNameInMarkdown: 'data.export.markdown.show_model_name',
|
||||||
|
showModelProviderInMarkdown: 'data.export.markdown.show_model_provider'
|
||||||
|
})
|
||||||
if (role === 'user') {
|
if (role === 'user') {
|
||||||
return '🧑💻 User'
|
return '🧑💻 User'
|
||||||
} else if (role === 'system') {
|
} else if (role === 'system') {
|
||||||
@ -263,13 +266,13 @@ const formatCitationsAsFootnotes = (citations: string): string => {
|
|||||||
return footnotes.join('\n\n')
|
return footnotes.join('\n\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
const createBaseMarkdown = (
|
const createBaseMarkdown = async (
|
||||||
message: Message,
|
message: Message,
|
||||||
includeReasoning: boolean = false,
|
includeReasoning: boolean = false,
|
||||||
excludeCitations: boolean = false,
|
excludeCitations: boolean = false,
|
||||||
normalizeCitations: boolean = true
|
normalizeCitations: boolean = true
|
||||||
): { titleSection: string; reasoningSection: string; contentSection: string; citation: string } => {
|
): Promise<{ titleSection: string; reasoningSection: string; contentSection: string; citation: string }> => {
|
||||||
const { forceDollarMathInMarkdown } = store.getState().settings
|
const forceDollarMathInMarkdown = await preferenceService.get('data.export.markdown.force_dollar_math')
|
||||||
const roleText = getRoleText(message.role, message.model?.name, message.model?.provider)
|
const roleText = getRoleText(message.role, message.model?.name, message.model?.provider)
|
||||||
const titleSection = `## ${roleText}`
|
const titleSection = `## ${roleText}`
|
||||||
let reasoningSection = ''
|
let reasoningSection = ''
|
||||||
@ -313,10 +316,13 @@ const createBaseMarkdown = (
|
|||||||
return { titleSection, reasoningSection, contentSection: processedContent, citation }
|
return { titleSection, reasoningSection, contentSection: processedContent, citation }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messageToMarkdown = (message: Message, excludeCitations?: boolean): string => {
|
export const messageToMarkdown = async (message: Message, excludeCitations?: boolean): Promise<string> => {
|
||||||
const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings
|
const { excludeCitationsInExport, standardizeCitationsInExport } = await preferenceService.getMultiple({
|
||||||
|
excludeCitationsInExport: 'data.export.markdown.exclude_citations',
|
||||||
|
standardizeCitationsInExport: 'data.export.markdown.standardize_citations'
|
||||||
|
})
|
||||||
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
|
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
|
||||||
const { titleSection, contentSection, citation } = createBaseMarkdown(
|
const { titleSection, contentSection, citation } = await createBaseMarkdown(
|
||||||
message,
|
message,
|
||||||
false,
|
false,
|
||||||
shouldExcludeCitations,
|
shouldExcludeCitations,
|
||||||
@ -325,10 +331,13 @@ export const messageToMarkdown = (message: Message, excludeCitations?: boolean):
|
|||||||
return [titleSection, '', contentSection, citation].join('\n')
|
return [titleSection, '', contentSection, citation].join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messageToMarkdownWithReasoning = (message: Message, excludeCitations?: boolean): string => {
|
export const messageToMarkdownWithReasoning = async (message: Message, excludeCitations?: boolean): Promise<string> => {
|
||||||
const { excludeCitationsInExport, standardizeCitationsInExport } = store.getState().settings
|
const { excludeCitationsInExport, standardizeCitationsInExport } = await preferenceService.getMultiple({
|
||||||
|
excludeCitationsInExport: 'data.export.markdown.exclude_citations',
|
||||||
|
standardizeCitationsInExport: 'data.export.markdown.standardize_citations'
|
||||||
|
})
|
||||||
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
|
const shouldExcludeCitations = excludeCitations ?? excludeCitationsInExport
|
||||||
const { titleSection, reasoningSection, contentSection, citation } = createBaseMarkdown(
|
const { titleSection, reasoningSection, contentSection, citation } = await createBaseMarkdown(
|
||||||
message,
|
message,
|
||||||
true,
|
true,
|
||||||
shouldExcludeCitations,
|
shouldExcludeCitations,
|
||||||
@ -337,18 +346,14 @@ export const messageToMarkdownWithReasoning = (message: Message, excludeCitation
|
|||||||
return [titleSection, '', reasoningSection, contentSection, citation].join('\n')
|
return [titleSection, '', reasoningSection, contentSection, citation].join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messagesToMarkdown = (
|
export const messagesToMarkdown = async (
|
||||||
messages: Message[],
|
messages: Message[],
|
||||||
exportReasoning?: boolean,
|
exportReasoning?: boolean,
|
||||||
excludeCitations?: boolean
|
excludeCitations?: boolean
|
||||||
): string => {
|
): Promise<string> => {
|
||||||
return messages
|
const converter = exportReasoning ? messageToMarkdownWithReasoning : messageToMarkdown
|
||||||
.map((message) =>
|
const markdowns = await Promise.all(messages.map((message) => converter(message, excludeCitations)))
|
||||||
exportReasoning
|
return markdowns.join('\n---\n')
|
||||||
? messageToMarkdownWithReasoning(message, excludeCitations)
|
|
||||||
: messageToMarkdown(message, excludeCitations)
|
|
||||||
)
|
|
||||||
.join('\n---\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatMessageAsPlainText = (message: Message): string => {
|
const formatMessageAsPlainText = (message: Message): string => {
|
||||||
@ -377,7 +382,7 @@ export const topicToMarkdown = async (
|
|||||||
const messages = await fetchTopicMessages(topic.id)
|
const messages = await fetchTopicMessages(topic.id)
|
||||||
|
|
||||||
if (messages && messages.length > 0) {
|
if (messages && messages.length > 0) {
|
||||||
return topicName + '\n\n' + messagesToMarkdown(messages, exportReasoning, excludeCitations)
|
return topicName + '\n\n' + (await messagesToMarkdown(messages, exportReasoning, excludeCitations))
|
||||||
}
|
}
|
||||||
|
|
||||||
return topicName
|
return topicName
|
||||||
@ -407,7 +412,7 @@ export const exportTopicAsMarkdown = async (
|
|||||||
|
|
||||||
setExportingState(true)
|
setExportingState(true)
|
||||||
|
|
||||||
const { markdownExportPath } = store.getState().settings
|
const markdownExportPath = await preferenceService.get('data.export.markdown.path')
|
||||||
if (!markdownExportPath) {
|
if (!markdownExportPath) {
|
||||||
try {
|
try {
|
||||||
const fileName = removeSpecialCharactersForFileName(topic.name) + '.md'
|
const fileName = removeSpecialCharactersForFileName(topic.name) + '.md'
|
||||||
@ -453,14 +458,14 @@ export const exportMessageAsMarkdown = async (
|
|||||||
|
|
||||||
setExportingState(true)
|
setExportingState(true)
|
||||||
|
|
||||||
const { markdownExportPath } = store.getState().settings
|
const markdownExportPath = await preferenceService.get('data.export.markdown.path')
|
||||||
if (!markdownExportPath) {
|
if (!markdownExportPath) {
|
||||||
try {
|
try {
|
||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const fileName = removeSpecialCharactersForFileName(title) + '.md'
|
const fileName = removeSpecialCharactersForFileName(title) + '.md'
|
||||||
const markdown = exportReasoning
|
const markdown = exportReasoning
|
||||||
? messageToMarkdownWithReasoning(message, excludeCitations)
|
? await messageToMarkdownWithReasoning(message, excludeCitations)
|
||||||
: messageToMarkdown(message, excludeCitations)
|
: await messageToMarkdown(message, excludeCitations)
|
||||||
const result = await window.api.file.save(fileName, markdown)
|
const result = await window.api.file.save(fileName, markdown)
|
||||||
if (result) {
|
if (result) {
|
||||||
window.message.success({
|
window.message.success({
|
||||||
@ -480,8 +485,8 @@ export const exportMessageAsMarkdown = async (
|
|||||||
const title = await getMessageTitle(message)
|
const title = await getMessageTitle(message)
|
||||||
const fileName = removeSpecialCharactersForFileName(title) + ` ${timestamp}.md`
|
const fileName = removeSpecialCharactersForFileName(title) + ` ${timestamp}.md`
|
||||||
const markdown = exportReasoning
|
const markdown = exportReasoning
|
||||||
? messageToMarkdownWithReasoning(message, excludeCitations)
|
? await messageToMarkdownWithReasoning(message, excludeCitations)
|
||||||
: messageToMarkdown(message, excludeCitations)
|
: await messageToMarkdown(message, excludeCitations)
|
||||||
await window.api.file.write(markdownExportPath + '/' + fileName, markdown)
|
await window.api.file.write(markdownExportPath + '/' + fileName, markdown)
|
||||||
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
|
window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' })
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -579,7 +584,11 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<boo
|
|||||||
return false
|
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) {
|
if (!notionApiKey || !notionDatabaseID) {
|
||||||
window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' })
|
window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' })
|
||||||
return false
|
return false
|
||||||
@ -609,7 +618,7 @@ const executeNotionExport = async (title: string, allBlocks: any[]): Promise<boo
|
|||||||
const response = await notion.pages.create({
|
const response = await notion.pages.create({
|
||||||
parent: { database_id: notionDatabaseID },
|
parent: { database_id: notionDatabaseID },
|
||||||
properties: {
|
properties: {
|
||||||
[store.getState().settings.notionPageNameKey || 'Name']: {
|
[notionPageNameKey || 'Name']: {
|
||||||
title: [{ text: { content: title } }]
|
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> => {
|
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)
|
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> => {
|
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)
|
const topicMessages = await fetchTopicMessages(topic.id)
|
||||||
|
|
||||||
@ -677,7 +689,7 @@ export const exportTopicToNotion = async (topic: Topic): Promise<boolean> => {
|
|||||||
|
|
||||||
for (const message of topicMessages) {
|
for (const message of topicMessages) {
|
||||||
// 将单个消息转换为markdown
|
// 将单个消息转换为markdown
|
||||||
const messageMarkdown = messageToMarkdown(message, excludeCitationsInExport)
|
const messageMarkdown = await messageToMarkdown(message, excludeCitationsInExport)
|
||||||
const messageBlocks = await convertMarkdownToNotionBlocks(messageMarkdown)
|
const messageBlocks = await convertMarkdownToNotionBlocks(messageMarkdown)
|
||||||
|
|
||||||
if (notionExportReasoning) {
|
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> => {
|
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()) {
|
if (getExportState()) {
|
||||||
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'yuque-exporting' })
|
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'yuque-exporting' })
|
||||||
@ -897,7 +912,13 @@ export const exportMarkdownToJoplin = async (
|
|||||||
title: string,
|
title: string,
|
||||||
contentOrMessages: string | Message | Message[]
|
contentOrMessages: string | Message | Message[]
|
||||||
): Promise<any | null> => {
|
): 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()) {
|
if (getExportState()) {
|
||||||
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'joplin-exporting' })
|
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') {
|
if (typeof contentOrMessages === 'string') {
|
||||||
content = contentOrMessages
|
content = contentOrMessages
|
||||||
} else if (Array.isArray(contentOrMessages)) {
|
} else if (Array.isArray(contentOrMessages)) {
|
||||||
content = messagesToMarkdown(contentOrMessages, joplinExportReasoning, excludeCitationsInExport)
|
content = await messagesToMarkdown(contentOrMessages, joplinExportReasoning, excludeCitationsInExport)
|
||||||
} else {
|
} else {
|
||||||
// 单条Message
|
// 单条Message
|
||||||
content = joplinExportReasoning
|
content = joplinExportReasoning
|
||||||
? messageToMarkdownWithReasoning(contentOrMessages, excludeCitationsInExport)
|
? await messageToMarkdownWithReasoning(contentOrMessages, excludeCitationsInExport)
|
||||||
: messageToMarkdown(contentOrMessages, excludeCitationsInExport)
|
: await messageToMarkdown(contentOrMessages, excludeCitationsInExport)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -963,7 +984,12 @@ export const exportMarkdownToJoplin = async (
|
|||||||
* @param content 笔记内容
|
* @param content 笔记内容
|
||||||
*/
|
*/
|
||||||
export const exportMarkdownToSiyuan = async (title: string, content: string): Promise<void> => {
|
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()) {
|
if (getExportState()) {
|
||||||
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'siyuan-exporting' })
|
window.message.warning({ content: i18n.t('message.warn.export.exporting'), key: 'siyuan-exporting' })
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { preferenceService } from '@renderer/data/PreferenceService'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { MCPTool } from '@renderer/types'
|
import { MCPTool } from '@renderer/types'
|
||||||
|
import { defaultLanguage } from '@shared/config/constant'
|
||||||
|
|
||||||
const logger = loggerService.withContext('Utils:Prompt')
|
const logger = loggerService.withContext('Utils:Prompt')
|
||||||
|
|
||||||
@ -205,7 +207,7 @@ export const replacePromptVariables = async (userSystemPrompt: string, modelName
|
|||||||
|
|
||||||
if (userSystemPrompt.includes('{{username}}')) {
|
if (userSystemPrompt.includes('{{username}}')) {
|
||||||
try {
|
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)
|
userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, userName)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to get username:', error as 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}}')) {
|
if (userSystemPrompt.includes('{{language}}')) {
|
||||||
try {
|
try {
|
||||||
const language = store.getState().settings.language
|
const language = await preferenceService.get('app.language')
|
||||||
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language)
|
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language || navigator.language || defaultLanguage)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to get language:', error as Error)
|
logger.error('Failed to get language:', error as Error)
|
||||||
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, 'Unknown System Language')
|
userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, 'Unknown System Language')
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const PreferenceHookTests: React.FC = () => {
|
|||||||
const testBatchOperations = async () => {
|
const testBatchOperations = async () => {
|
||||||
try {
|
try {
|
||||||
const keys: PreferenceKeyType[] = ['ui.theme_mode', 'app.language']
|
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} 项`)
|
message.success(`批量获取成功: ${Object.keys(result).length} 项`)
|
||||||
logger.debug('Batch get result:', { result })
|
logger.debug('Batch get result:', { result })
|
||||||
|
|||||||
@ -87,13 +87,15 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
if (initialized.current || !action.selectedText) return
|
if (initialized.current || !action.selectedText) return
|
||||||
initialized.current = true
|
initialized.current = true
|
||||||
|
|
||||||
// Initialize assistant
|
runAsyncFunction(async () => {
|
||||||
const currentAssistant = getDefaultTranslateAssistant(targetLanguage, action.selectedText)
|
// Initialize assistant
|
||||||
|
const currentAssistant = await getDefaultTranslateAssistant(targetLanguage, action.selectedText!)
|
||||||
|
|
||||||
assistantRef.current = currentAssistant
|
assistantRef.current = currentAssistant
|
||||||
|
|
||||||
// Initialize topic
|
// Initialize topic
|
||||||
topicRef.current = getDefaultTopic(currentAssistant.id)
|
topicRef.current = getDefaultTopic(currentAssistant.id)
|
||||||
|
})
|
||||||
}, [action, targetLanguage, translateModelPrompt])
|
}, [action, targetLanguage, translateModelPrompt])
|
||||||
|
|
||||||
const fetchResult = useCallback(async () => {
|
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)
|
processMessages(assistantRef.current, topicRef.current, action.selectedText, setAskId, onStream, onFinish, onError)
|
||||||
}, [action, targetLanguage, alterLanguage, scrollToBottom])
|
}, [action, targetLanguage, alterLanguage, scrollToBottom])
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user