From a6db53873a3f21ab844e066b5ca6ebee6bf00e8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=87=AA=E7=94=B1=E7=9A=84=E4=B8=96=E7=95=8C=E4=BA=BA?=
<3196812536@qq.com>
Date: Tue, 15 Jul 2025 19:25:55 +0800
Subject: [PATCH] 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.
---
src/main/services/NotificationService.ts | 5 +----
src/renderer/src/hooks/useUpdateHandler.ts | 3 ++-
src/renderer/src/i18n/locales/en-us.json | 3 ++-
src/renderer/src/i18n/locales/ja-jp.json | 3 ++-
src/renderer/src/i18n/locales/ru-ru.json | 3 ++-
src/renderer/src/i18n/locales/zh-cn.json | 3 ++-
src/renderer/src/i18n/locales/zh-tw.json | 3 ++-
.../src/pages/settings/GeneralSettings.tsx | 10 ++++++++--
src/renderer/src/services/BackupService.ts | 20 +++++++++++++------
.../streamCallback.integration.test.ts | 6 +++---
src/renderer/src/store/thunk/messageThunk.ts | 19 +++++++++---------
11 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/src/main/services/NotificationService.ts b/src/main/services/NotificationService.ts
index e06036b52..5ba0d82ce 100644
--- a/src/main/services/NotificationService.ts
+++ b/src/main/services/NotificationService.ts
@@ -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', () => {
diff --git a/src/renderer/src/hooks/useUpdateHandler.ts b/src/renderer/src/hooks/useUpdateHandler.ts
index fe4d8f631..4724ddd68 100644
--- a/src/renderer/src/hooks/useUpdateHandler.ts
+++ b/src/renderer/src/hooks/useUpdateHandler.ts
@@ -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({
diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index e24f0c0ea..301f97505 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -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.",
diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json
index 76ba1c3da..13f55bac9 100644
--- a/src/renderer/src/i18n/locales/ja-jp.json
+++ b/src/renderer/src/i18n/locales/ja-jp.json
@@ -906,7 +906,8 @@
"notification": {
"assistant": "助手回應",
"knowledge.error": "{{error}}",
- "knowledge.success": "ナレッジベースに{{type}}を正常に追加しました"
+ "knowledge.success": "ナレッジベースに{{type}}を正常に追加しました",
+ "tip": "応答が成功した場合、30秒を超えるメッセージのみに通知を行います"
},
"ollama": {
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json
index 9dbe4698e..a59b9d658 100644
--- a/src/renderer/src/i18n/locales/ru-ru.json
+++ b/src/renderer/src/i18n/locales/ru-ru.json
@@ -906,7 +906,8 @@
"notification": {
"assistant": "Ответ ассистента",
"knowledge.error": "{{error}}",
- "knowledge.success": "Успешно добавлено {{type}} в базу знаний"
+ "knowledge.success": "Успешно добавлено {{type}} в базу знаний",
+ "tip": "Если ответ успешен, уведомление выдается только по сообщениям, превышающим 30 секунд"
},
"ollama": {
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index 5c636eb8b..52fea95ad 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -906,7 +906,8 @@
"notification": {
"assistant": "助手响应",
"knowledge.error": "{{error}}",
- "knowledge.success": "成功添加 {{type}} 到知识库"
+ "knowledge.success": "成功添加 {{type}} 到知识库",
+ "tip": "如果响应成功,则只针对超过30秒的消息进行提醒"
},
"ollama": {
"keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5 分钟)",
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index af1b21fe2..21c937d2d 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -906,7 +906,8 @@
"notification": {
"assistant": "助手回應",
"knowledge.error": "無法將 {{type}} 加入知識庫: {{error}}",
- "knowledge.success": "成功將 {{type}} 新增至知識庫"
+ "knowledge.success": "成功將 {{type}} 新增至知識庫",
+ "tip": "如果回應成功,則只針對超過30秒的訊息發出提醒"
},
"ollama": {
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)",
diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx
index 9e47317a5..b9f55cc60 100644
--- a/src/renderer/src/pages/settings/GeneralSettings.tsx
+++ b/src/renderer/src/pages/settings/GeneralSettings.tsx
@@ -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 = () => {
{t('settings.notification.title')}
- {t('settings.notification.assistant')}
+
+ {t('settings.notification.assistant')}
+
+
+
+
handleNotificationChange('assistant', v)} />
diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts
index 9e594f85c..74cd25367 100644
--- a/src/renderer/src/services/BackupService.ts
+++ b/src/renderer/src/services/BackupService.ts
@@ -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'
})
}
diff --git a/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts b/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts
index 15a86ce64..da07cc3b3 100644
--- a/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts
+++ b/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts
@@ -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
diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts
index 5aba6499d..7fe4b303e 100644
--- a/src/renderer/src/store/thunk/messageThunk.ts
+++ b/src/renderer/src/store/thunk/messageThunk.ts
@@ -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 }))
}