mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
fix: add channel property to notifications for backup and assistant messages (#8120)
* fix: add channel property to notifications for backup and assistant messages * Add notification tip and improve assistant notification logic Added a tooltip in the notification settings UI to clarify that only messages exceeding 30 seconds will trigger a reminder. Updated i18n files for all supported languages with the new tip. Modified notification logic to only send notifications for assistant responses or errors if the message duration exceeds 30 seconds and the user is not on the home page or the window is not focused. * Remove duplicate InfoCircleOutlined import Consolidated the import of InfoCircleOutlined from '@ant-design/icons' to avoid redundancy in GeneralSettings.tsx. * fix: add isFocused mock and simplify createMockStore Added a mock for isFocused in the window utility and refactored createMockStore to return the configured store directly. This improves test setup clarity and ensures all necessary window utilities are mocked.
This commit is contained in:
parent
397965f6e9
commit
a6db53873a
@ -1,8 +1,6 @@
|
||||
import { BrowserWindow, Notification as ElectronNotification } from 'electron'
|
||||
import { Notification } from 'src/renderer/src/types/notification'
|
||||
|
||||
import icon from '../../../build/icon.png?asset'
|
||||
|
||||
class NotificationService {
|
||||
private window: BrowserWindow
|
||||
|
||||
@ -15,8 +13,7 @@ class NotificationService {
|
||||
// 使用 Electron Notification API
|
||||
const electronNotification = new ElectronNotification({
|
||||
title: notification.title,
|
||||
body: notification.message,
|
||||
icon: icon
|
||||
body: notification.message
|
||||
})
|
||||
|
||||
electronNotification.on('click', () => {
|
||||
|
||||
@ -31,7 +31,8 @@ export default function useUpdateHandler() {
|
||||
title: t('button.update_available'),
|
||||
message: t('button.update_available', { version: releaseInfo.version }),
|
||||
timestamp: Date.now(),
|
||||
source: 'update'
|
||||
source: 'update',
|
||||
channel: 'system'
|
||||
})
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
|
||||
@ -906,7 +906,8 @@
|
||||
"notification": {
|
||||
"assistant": "Assistant Response",
|
||||
"knowledge.error": "{{error}}",
|
||||
"knowledge.success": "Successfully added {{type}} to the knowledge base"
|
||||
"knowledge.success": "Successfully added {{type}} to the knowledge base",
|
||||
"tip": "If the response is successful, then only messages exceeding 30 seconds will trigger a reminder"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.",
|
||||
|
||||
@ -906,7 +906,8 @@
|
||||
"notification": {
|
||||
"assistant": "助手回應",
|
||||
"knowledge.error": "{{error}}",
|
||||
"knowledge.success": "ナレッジベースに{{type}}を正常に追加しました"
|
||||
"knowledge.success": "ナレッジベースに{{type}}を正常に追加しました",
|
||||
"tip": "応答が成功した場合、30秒を超えるメッセージのみに通知を行います"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
|
||||
|
||||
@ -906,7 +906,8 @@
|
||||
"notification": {
|
||||
"assistant": "Ответ ассистента",
|
||||
"knowledge.error": "{{error}}",
|
||||
"knowledge.success": "Успешно добавлено {{type}} в базу знаний"
|
||||
"knowledge.success": "Успешно добавлено {{type}} в базу знаний",
|
||||
"tip": "Если ответ успешен, уведомление выдается только по сообщениям, превышающим 30 секунд"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
|
||||
|
||||
@ -906,7 +906,8 @@
|
||||
"notification": {
|
||||
"assistant": "助手响应",
|
||||
"knowledge.error": "{{error}}",
|
||||
"knowledge.success": "成功添加 {{type}} 到知识库"
|
||||
"knowledge.success": "成功添加 {{type}} 到知识库",
|
||||
"tip": "如果响应成功,则只针对超过30秒的消息进行提醒"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5 分钟)",
|
||||
|
||||
@ -906,7 +906,8 @@
|
||||
"notification": {
|
||||
"assistant": "助手回應",
|
||||
"knowledge.error": "無法將 {{type}} 加入知識庫: {{error}}",
|
||||
"knowledge.success": "成功將 {{type}} 新增至知識庫"
|
||||
"knowledge.success": "成功將 {{type}} 新增至知識庫",
|
||||
"tip": "如果回應成功,則只針對超過30秒的訊息發出提醒"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import Selector from '@renderer/components/Selector'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
@ -16,7 +17,7 @@ import { LanguageVarious } from '@renderer/types'
|
||||
import { NotificationSource } from '@renderer/types/notification'
|
||||
import { isValidProxyUrl } from '@renderer/utils'
|
||||
import { defaultLanguage } from '@shared/config/constant'
|
||||
import { Flex, Input, Switch } from 'antd'
|
||||
import { Flex, Input, Switch, Tooltip } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
@ -261,7 +262,12 @@ const GeneralSettings: FC = () => {
|
||||
<SettingTitle>{t('settings.notification.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.notification.assistant')}</SettingRowTitle>
|
||||
<SettingRowTitle style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<span>{t('settings.notification.assistant')}</span>
|
||||
<Tooltip title={t('notification.tip')} placement="right">
|
||||
<InfoCircleOutlined style={{ cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<Switch checked={notificationSettings.assistant} onChange={(v) => handleNotificationChange('assistant', v)} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
|
||||
@ -94,7 +94,8 @@ export async function restore() {
|
||||
message: i18n.t('message.restore.success'),
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
} catch (error) {
|
||||
Logger.error('[Backup] restore: Error restoring backup file:', error)
|
||||
@ -129,6 +130,8 @@ export async function reset() {
|
||||
|
||||
// 备份到 webdav
|
||||
/**
|
||||
* @param showMessage
|
||||
* @param customFileName
|
||||
* @param autoBackupProcess
|
||||
* if call in auto backup process, not show any message, any error will be thrown
|
||||
*/
|
||||
@ -189,7 +192,8 @@ export async function backupToWebdav({
|
||||
message: i18n.t('message.backup.success'),
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
|
||||
|
||||
@ -258,7 +262,8 @@ export async function backupToWebdav({
|
||||
message: error.message,
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
|
||||
showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
|
||||
@ -354,7 +359,8 @@ export async function backupToS3({
|
||||
message: i18n.t('message.backup.success'),
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
|
||||
|
||||
@ -407,7 +413,8 @@ export async function backupToS3({
|
||||
message: error.message,
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
store.dispatch(setS3SyncState({ lastSyncError: error.message }))
|
||||
console.error('[Backup] backupToS3: Error uploading file to S3:', error)
|
||||
@ -935,7 +942,8 @@ export async function backupToLocal({
|
||||
message: i18n.t('message.backup.success'),
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'backup'
|
||||
source: 'backup',
|
||||
channel: 'system'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +142,8 @@ vi.mock('@renderer/services/EventService', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/utils/window', () => ({
|
||||
isOnHomePage: vi.fn(() => true)
|
||||
isOnHomePage: vi.fn(() => true),
|
||||
isFocused: vi.fn(() => true)
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/hooks/useTopic', () => ({
|
||||
@ -231,11 +232,10 @@ const reducer = combineReducers({
|
||||
})
|
||||
|
||||
const createMockStore = () => {
|
||||
const store = configureStore({
|
||||
return configureStore({
|
||||
reducer: reducer,
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false })
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
// Helper function to simulate processing chunks through the stream processor
|
||||
|
||||
@ -42,9 +42,8 @@ import {
|
||||
resetAssistantMessage
|
||||
} from '@renderer/utils/messageUtils/create'
|
||||
import { findMainTextBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
import { getTopicQueue } from '@renderer/utils/queue'
|
||||
import { waitForTopicQueue } from '@renderer/utils/queue'
|
||||
import { isOnHomePage } from '@renderer/utils/window'
|
||||
import { getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue'
|
||||
import { isFocused, isOnHomePage } from '@renderer/utils/window'
|
||||
import { t } from 'i18next'
|
||||
import { isEmpty, throttle } from 'lodash'
|
||||
import { LRUCache } from 'lru-cache'
|
||||
@ -719,7 +718,8 @@ export const streamCallback = (
|
||||
status: error.status || error.code,
|
||||
requestId: error.request_id
|
||||
}
|
||||
if (!isOnHomePage()) {
|
||||
const msgDuration = Date.now() - startTime
|
||||
if ((!isOnHomePage() && msgDuration > 30 * 1000) || (!isFocused() && msgDuration > 30 * 1000)) {
|
||||
await notificationService.send({
|
||||
id: uuid(),
|
||||
type: 'error',
|
||||
@ -789,10 +789,9 @@ export const streamCallback = (
|
||||
smartBlockUpdate(possibleBlockId, changes, lastBlockType!, true)
|
||||
}
|
||||
|
||||
const endTime = Date.now()
|
||||
const duration = endTime - startTime
|
||||
const content = getMainTextContent(finalAssistantMsg)
|
||||
if (!isOnHomePage() && duration > 60 * 1000) {
|
||||
const msgDuration = Date.now() - startTime
|
||||
if ((!isOnHomePage() && msgDuration > 30 * 1000) || (!isFocused() && msgDuration > 30 * 1000)) {
|
||||
await notificationService.send({
|
||||
id: uuid(),
|
||||
type: 'success',
|
||||
@ -800,7 +799,8 @@ export const streamCallback = (
|
||||
message: content.length > 50 ? content.slice(0, 47) + '...' : content,
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'assistant'
|
||||
source: 'assistant',
|
||||
channel: 'system'
|
||||
})
|
||||
}
|
||||
|
||||
@ -813,8 +813,7 @@ export const streamCallback = (
|
||||
response?.usage?.prompt_tokens === 0 ||
|
||||
response?.usage?.completion_tokens === 0)
|
||||
) {
|
||||
const usage = await estimateMessagesUsage({ assistant, messages: finalContextWithAssistant })
|
||||
response.usage = usage
|
||||
response.usage = await estimateMessagesUsage({ assistant, messages: finalContextWithAssistant })
|
||||
}
|
||||
// dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false }))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user