diff --git a/src/main/services/NotificationService.ts b/src/main/services/NotificationService.ts index e06036b523..5ba0d82ce4 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 fe4d8f631c..4724ddd688 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 e24f0c0eaa..301f97505f 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 76ba1c3daa..13f55bac96 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 9dbe4698e4..a59b9d6581 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 5c636eb8bf..52fea95ad0 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 af1b21fe23..21c937d2d5 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 9e47317a55..b9f55cc604 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 9e594f85c2..74cd253679 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 15a86ce64d..da07cc3b34 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 5aba6499d9..7fe4b303e5 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 })) }