From 3c0d98f4cc8732989346cf4b65f64801763aea55 Mon Sep 17 00:00:00 2001 From: wuxu Date: Sun, 26 Apr 2026 13:10:14 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E5=A2=9E=E5=8A=A0=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9A=84=E6=8F=90=E7=A4=BA=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/sounds/notification.wav | Bin 0 -> 22094 bytes src-tauri/src/commands/system_settings.rs | 46 ++++++++- src-tauri/src/lib.rs | 2 + src-tauri/src/models/mod.rs | 2 +- src-tauri/src/models/system.rs | 16 ++++ .../settings/system-network-settings.tsx | 87 ++++++++++++++++-- src/contexts/acp-connections-context.tsx | 20 +++- src/i18n/messages/ar.json | 7 +- src/i18n/messages/de.json | 7 +- src/i18n/messages/en.json | 7 +- src/i18n/messages/es.json | 7 +- src/i18n/messages/fr.json | 7 +- src/i18n/messages/ja.json | 7 +- src/i18n/messages/ko.json | 7 +- src/i18n/messages/pt.json | 7 +- src/i18n/messages/zh-CN.json | 7 +- src/i18n/messages/zh-TW.json | 7 +- src/lib/api.ts | 11 +++ src/lib/notification.ts | 28 +++++- src/lib/types.ts | 5 + 20 files changed, 265 insertions(+), 22 deletions(-) create mode 100644 public/sounds/notification.wav diff --git a/public/sounds/notification.wav b/public/sounds/notification.wav new file mode 100644 index 0000000000000000000000000000000000000000..d74a1f8cc945fa46aeb217c6dd4766d6d8e89a1a GIT binary patch literal 22094 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 Result { + let raw = app_metadata_service::get_value(conn, SYSTEM_NOTIFICATION_SETTINGS_KEY) + .await + .map_err(AppCommandError::from)?; + + let Some(raw) = raw else { + return Ok(SystemNotificationSettings::new_enabled()); + }; + + serde_json::from_str::(&raw).map_err(|e| { + AppCommandError::configuration_invalid("Failed to parse stored notification settings") + .with_detail(e.to_string()) + }) +} + +#[cfg(feature = "tauri-runtime")] +#[cfg_attr(feature = "tauri-runtime", tauri::command)] +pub async fn get_system_notification_settings( + db: State<'_, AppDatabase>, +) -> Result { + load_system_notification_settings(&db.conn).await +} + +#[cfg(feature = "tauri-runtime")] +#[cfg_attr(feature = "tauri-runtime", tauri::command)] +pub async fn update_system_notification_settings( + settings: SystemNotificationSettings, + db: State<'_, AppDatabase>, +) -> Result { + let serialized = serde_json::to_string(&settings).map_err(|e| { + AppCommandError::invalid_input("Failed to serialize notification settings") + .with_detail(e.to_string()) + })?; + + app_metadata_service::upsert_value(&db.conn, SYSTEM_NOTIFICATION_SETTINGS_KEY, &serialized) + .await + .map_err(AppCommandError::from)?; + + Ok(settings) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e632c89..98e0db6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -312,6 +312,8 @@ mod tauri_app { system_settings::update_system_language_settings, system_settings::get_system_rendering_settings, system_settings::update_system_rendering_settings, + system_settings::get_system_notification_settings, + system_settings::update_system_notification_settings, version_control::detect_git, version_control::test_git_path, version_control::get_git_settings, diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index 7622a7a..2abd788 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -21,7 +21,7 @@ pub use message::{ }; pub use system::{ GitCredentials, GitDetectResult, GitHubAccountsSettings, GitHubTokenValidation, GitSettings, - SystemLanguageSettings, SystemProxySettings, + SystemLanguageSettings, SystemNotificationSettings, SystemProxySettings, }; #[cfg(feature = "tauri-runtime")] pub use system::SystemRenderingSettings; diff --git a/src-tauri/src/models/system.rs b/src-tauri/src/models/system.rs index 864b6ae..ed3c5e6 100644 --- a/src-tauri/src/models/system.rs +++ b/src-tauri/src/models/system.rs @@ -44,6 +44,22 @@ pub struct SystemRenderingSettings { pub disable_hardware_acceleration: bool, } +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct SystemNotificationSettings { + pub task_completion: bool, + pub sound_enabled: bool, +} + +impl SystemNotificationSettings { + pub fn new_enabled() -> Self { + Self { + task_completion: true, + sound_enabled: true, + } + } +} + // --- Version Control --- /// Explicit credentials for a single git remote operation. diff --git a/src/components/settings/system-network-settings.tsx b/src/components/settings/system-network-settings.tsx index 2eca233..f7a8f59 100644 --- a/src/components/settings/system-network-settings.tsx +++ b/src/components/settings/system-network-settings.tsx @@ -3,11 +3,13 @@ import { useCallback, useEffect, useMemo, useState } from "react" import { ArrowUpCircle, + Bell, CheckCircle2, Languages, Loader2, MonitorCog, RefreshCw, + Volume2, Wifi, } from "lucide-react" import { Github } from "@lobehub/icons" @@ -31,9 +33,11 @@ import { import { getSystemProxySettings, getSystemRenderingSettings, + getSystemNotificationSettings, updateSystemLanguageSettings, updateSystemProxySettings, updateSystemRenderingSettings, + updateSystemNotificationSettings, } from "@/lib/api" import { isDesktop, openUrl } from "@/lib/platform" import type { AppLocale } from "@/lib/types" @@ -96,6 +100,9 @@ export function SystemNetworkSettings() { ) const renderingDirty = processStartLoaded && persistedDisableHwAccel !== processStartDisableHwAccel + const [notifTaskCompletion, setNotifTaskCompletion] = useState(true) + const [notifSoundEnabled, setNotifSoundEnabled] = useState(true) + const [savingNotification, setSavingNotification] = useState(false) const [currentVersion, setCurrentVersion] = useState("") const [availableUpdate, setAvailableUpdate] = useState(null) const [checkingUpdate, setCheckingUpdate] = useState(false) @@ -171,13 +178,15 @@ export function SystemNetworkSettings() { setLoadError(null) try { - const [proxySettings, version, renderingSettings] = await Promise.all([ - getSystemProxySettings(), - getCurrentAppVersion(), - renderingSettingsLoadable - ? getSystemRenderingSettings() - : Promise.resolve(null), - ]) + const [proxySettings, version, renderingSettings, notificationSettings] = + await Promise.all([ + getSystemProxySettings(), + getCurrentAppVersion(), + renderingSettingsLoadable + ? getSystemRenderingSettings() + : Promise.resolve(null), + getSystemNotificationSettings(), + ]) setEnabled(proxySettings.enabled) setProxyUrl(proxySettings.proxy_url ?? "") @@ -191,6 +200,10 @@ export function SystemNetworkSettings() { setProcessStartLoaded(true) } } + if (notificationSettings) { + setNotifTaskCompletion(notificationSettings.task_completion) + setNotifSoundEnabled(notificationSettings.sound_enabled) + } } catch (err) { const message = err instanceof Error ? err.message : String(err) setLoadError(message) @@ -291,6 +304,26 @@ export function SystemNetworkSettings() { [languageSettings.language, setLanguageSettings, t] ) + const saveNotificationSettings = useCallback( + async (taskCompletion: boolean, soundEnabled: boolean) => { + setSavingNotification(true) + try { + const result = await updateSystemNotificationSettings({ + task_completion: taskCompletion, + sound_enabled: soundEnabled, + }) + setNotifTaskCompletion(result.task_completion) + setNotifSoundEnabled(result.sound_enabled) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + toast.error(t("notificationSaveFailed", { message })) + } finally { + setSavingNotification(false) + } + }, + [t] + ) + const formatUpdateError = useCallback( (error: unknown, action: UpdateAction): string => { const { kind, rawMessage } = normalizeAppUpdateError(error) @@ -716,6 +749,46 @@ export function SystemNetworkSettings() { + +
+
+ +

{t("notificationTitle")}

+
+ +

+ {t("notificationDescription")} +

+ + + + +
) diff --git a/src/contexts/acp-connections-context.tsx b/src/contexts/acp-connections-context.tsx index cff4172..5e0ad2f 100644 --- a/src/contexts/acp-connections-context.tsx +++ b/src/contexts/acp-connections-context.tsx @@ -22,6 +22,7 @@ import { acpCancel, acpRespondPermission, acpDisconnect, + getSystemNotificationSettings, } from "@/lib/api" import type { AgentType, @@ -1364,6 +1365,19 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { pushAlertRef.current = pushAlert }, [pushAlert]) + // Notification settings (loaded once, refreshed lazily) + const notificationSettingsRef = useRef({ + task_completion: true, + sound_enabled: true, + }) + useEffect(() => { + getSystemNotificationSettings() + .then((s) => { + notificationSettingsRef.current = s + }) + .catch(() => {}) + }, []) + // Ref-based store — mutations don't trigger React state updates const storeRef = useRef({ connections: new Map(), @@ -1948,14 +1962,16 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { } // Send OS notification when window is not focused { + const ns = notificationSettingsRef.current const nc = storeRef.current.connections.get(contextKey) - if (nc) { + if (nc && ns.task_completion) { const agentLabel = AGENT_LABELS[nc.agentType] const fn = folderNameRef.current const title = fn ? `${fn} - Codeg` : "Codeg" sendSystemNotification( title, - t("notificationTurnComplete", { agent: agentLabel }) + t("notificationTurnComplete", { agent: agentLabel }), + { sound: ns.sound_enabled } ).catch(() => {}) } } diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 66b6715..98532d8 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -145,7 +145,12 @@ "downloadFailed": "فشل تنزيل حزمة التحديث. يرجى المحاولة مرة أخرى لاحقًا.", "installFailed": "فشل تثبيت التحديث. يرجى إغلاق التطبيق ثم إعادة المحاولة.", "unknown": "فشل التحديث. يرجى المحاولة مرة أخرى لاحقًا." - } + }, + "notificationTitle": "الإشعارات", + "notificationDescription": "تكوين إشعارات إتمام المهام وتنبيهات الصوت.", + "notificationTaskCompletion": "إشعار عند إتمام المهمة", + "notificationSoundEnabled": "تشغيل صوت الإشعار", + "notificationSaveFailed": "فشل حفظ إعدادات الإشعارات: {message}" }, "VersionControlSettings": { "loading": "جارٍ التحميل...", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 70220a0..da7b1f9 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -145,7 +145,12 @@ "downloadFailed": "Das Update-Paket konnte nicht heruntergeladen werden. Bitte später erneut versuchen.", "installFailed": "Das Update konnte nicht installiert werden. Bitte App schließen und erneut versuchen.", "unknown": "Update fehlgeschlagen. Bitte später erneut versuchen." - } + }, + "notificationTitle": "Benachrichtigungen", + "notificationDescription": "Benachrichtigungen bei Aufgabenerledigung und Soundalarme konfigurieren.", + "notificationTaskCompletion": "Bei Aufgabenerledigung benachrichtigen", + "notificationSoundEnabled": "Benachrichtigungston abspielen", + "notificationSaveFailed": "Fehler beim Speichern der Benachrichtigungseinstellungen: {message}" }, "VersionControlSettings": { "loading": "Laden...", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 524c454..8fb15a5 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -145,7 +145,12 @@ "downloadFailed": "Failed to download update package. Please try again later.", "installFailed": "Failed to install update. Please close the app and try again.", "unknown": "Update failed. Please try again later." - } + }, + "notificationTitle": "Notifications", + "notificationDescription": "Configure task completion notifications and sound alerts.", + "notificationTaskCompletion": "Notify on task completion", + "notificationSoundEnabled": "Play notification sound", + "notificationSaveFailed": "Failed to save notification settings: {message}" }, "VersionControlSettings": { "loading": "Loading...", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index d13f2f5..23f92f2 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -145,7 +145,12 @@ "downloadFailed": "No se pudo descargar el paquete de actualización. Inténtalo más tarde.", "installFailed": "No se pudo instalar la actualización. Cierra la app e inténtalo de nuevo.", "unknown": "La actualización falló. Inténtalo más tarde." - } + }, + "notificationTitle": "Notificaciones", + "notificationDescription": "Configurar notificaciones de finalización de tareas y alertas de sonido.", + "notificationTaskCompletion": "Notificar al completar tarea", + "notificationSoundEnabled": "Reproducir sonido de notificación", + "notificationSaveFailed": "Error al guardar configuración de notificaciones: {message}" }, "VersionControlSettings": { "loading": "Cargando...", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index c5862a3..408ecb8 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -145,7 +145,12 @@ "downloadFailed": "Impossible de télécharger le paquet de mise à jour. Veuillez réessayer plus tard.", "installFailed": "Impossible d’installer la mise à jour. Fermez l’app puis réessayez.", "unknown": "La mise à jour a échoué. Veuillez réessayer plus tard." - } + }, + "notificationTitle": "Notifications", + "notificationDescription": "Configurer les notifications de fin de tâche et les alertes sonores.", + "notificationTaskCompletion": "Notifier à la fin de la tâche", + "notificationSoundEnabled": "Jouer un son de notification", + "notificationSaveFailed": "Échec de l'enregistrement des paramètres de notification : {message}" }, "VersionControlSettings": { "loading": "Chargement...", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 30b1df5..19d265a 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -145,7 +145,12 @@ "downloadFailed": "更新パッケージのダウンロードに失敗しました。しばらくしてから再試行してください。", "installFailed": "更新のインストールに失敗しました。アプリを閉じて再試行してください。", "unknown": "更新に失敗しました。しばらくしてから再試行してください。" - } + }, + "notificationTitle": "通知", + "notificationDescription": "タスク完了通知とサウンドアラートを設定します。", + "notificationTaskCompletion": "タスク完了時に通知する", + "notificationSoundEnabled": "通知音を再生する", + "notificationSaveFailed": "通知設定の保存に失敗しました:{message}" }, "VersionControlSettings": { "loading": "読み込み中...", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 5ee5b3a..21dc02e 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -145,7 +145,12 @@ "downloadFailed": "업데이트 패키지 다운로드에 실패했습니다. 잠시 후 다시 시도하세요.", "installFailed": "업데이트 설치에 실패했습니다. 앱을 종료한 뒤 다시 시도하세요.", "unknown": "업데이트에 실패했습니다. 잠시 후 다시 시도하세요." - } + }, + "notificationTitle": "알림", + "notificationDescription": "작업 완료 알림 및 소리 알림을 설정합니다.", + "notificationTaskCompletion": "작업 완료 시 알림", + "notificationSoundEnabled": "알림 소리 재생", + "notificationSaveFailed": "알림 설정 저장 실패: {message}" }, "VersionControlSettings": { "loading": "로딩 중...", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 0d5c3b4..5b471e0 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -145,7 +145,12 @@ "downloadFailed": "Falha ao baixar o pacote de atualização. Tente novamente mais tarde.", "installFailed": "Falha ao instalar a atualização. Feche o app e tente novamente.", "unknown": "Falha na atualização. Tente novamente mais tarde." - } + }, + "notificationTitle": "Notificações", + "notificationDescription": "Configurar notificações de conclusão de tarefas e alertas sonoros.", + "notificationTaskCompletion": "Notificar ao concluir tarefa", + "notificationSoundEnabled": "Reproduzir som de notificação", + "notificationSaveFailed": "Falha ao salvar configurações de notificação: {message}" }, "VersionControlSettings": { "loading": "Carregando...", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 8abe47e..05aec5f 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -145,7 +145,12 @@ "downloadFailed": "下载更新包失败,请稍后重试。", "installFailed": "安装更新失败,请关闭应用后重试。", "unknown": "更新失败,请稍后重试。" - } + }, + "notificationTitle": "通知", + "notificationDescription": "配置任务完成通知和声音提醒。", + "notificationTaskCompletion": "任务完成时通知", + "notificationSoundEnabled": "播放通知声音", + "notificationSaveFailed": "保存通知设置失败:{message}" }, "VersionControlSettings": { "loading": "加载中...", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 828b755..4aa26c2 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -145,7 +145,12 @@ "downloadFailed": "下載更新包失敗,請稍後再試。", "installFailed": "安裝更新失敗,請關閉應用後重試。", "unknown": "更新失敗,請稍後再試。" - } + }, + "notificationTitle": "通知", + "notificationDescription": "設定任務完成通知和聲音提醒。", + "notificationTaskCompletion": "任務完成時通知", + "notificationSoundEnabled": "播放通知聲音", + "notificationSaveFailed": "儲存通知設定失敗:{message}" }, "VersionControlSettings": { "loading": "載入中...", diff --git a/src/lib/api.ts b/src/lib/api.ts index ec91105..4bb5994 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -48,6 +48,7 @@ import type { SystemLanguageSettings, SystemProxySettings, SystemRenderingSettings, + SystemNotificationSettings, GitCredentials, GitDetectResult, PackageManagerInfo, @@ -469,6 +470,16 @@ export async function updateSystemRenderingSettings( return getTransport().call("update_system_rendering_settings", { settings }) } +export async function getSystemNotificationSettings(): Promise { + return getTransport().call("get_system_notification_settings") +} + +export async function updateSystemNotificationSettings( + settings: SystemNotificationSettings +): Promise { + return getTransport().call("update_system_notification_settings", { settings }) +} + // --- Version Control --- export async function detectGit(): Promise { diff --git a/src/lib/notification.ts b/src/lib/notification.ts index 5c538d9..6a60237 100644 --- a/src/lib/notification.ts +++ b/src/lib/notification.ts @@ -1,9 +1,31 @@ import { getTransport } from "./transport" import { isDesktop } from "./transport" +let cachedAudio: HTMLAudioElement | null = null + +function getNotificationAudio(): HTMLAudioElement { + if (!cachedAudio) { + cachedAudio = new Audio("/sounds/notification.wav") + } + return cachedAudio +} + +export function playNotificationSound(): void { + try { + const audio = getNotificationAudio() + audio.currentTime = 0 + audio.play().catch(() => { + // Autoplay may be blocked; silently ignore + }) + } catch { + // Ignore audio playback errors + } +} + export async function sendSystemNotification( title: string, - body: string + body: string, + options?: { sound?: boolean } ): Promise { if (!document.hidden) return if (isDesktop()) { @@ -19,4 +41,8 @@ export async function sendSystemNotification( } } } + + if (options?.sound !== false) { + playNotificationSound() + } } diff --git a/src/lib/types.ts b/src/lib/types.ts index 369832c..8d38238 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -638,6 +638,11 @@ export interface SystemRenderingSettings { disable_hardware_acceleration: boolean } +export interface SystemNotificationSettings { + task_completion: boolean + sound_enabled: boolean +} + // --- Version Control --- export interface GitCredentials {