add: 增加任务完成的提示音

This commit is contained in:
2026-04-26 13:10:14 +08:00
parent ce10295e89
commit 3c0d98f4cc
20 changed files with 265 additions and 22 deletions

Binary file not shown.

View File

@@ -6,7 +6,7 @@ use crate::app_error::AppCommandError;
use crate::db::service::app_metadata_service; use crate::db::service::app_metadata_service;
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
use crate::db::AppDatabase; use crate::db::AppDatabase;
use crate::models::{SystemLanguageSettings, SystemProxySettings}; use crate::models::{SystemLanguageSettings, SystemNotificationSettings, SystemProxySettings};
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
use crate::models::SystemRenderingSettings; use crate::models::SystemRenderingSettings;
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
@@ -16,6 +16,7 @@ use crate::network::proxy;
const SYSTEM_PROXY_SETTINGS_KEY: &str = "system_proxy_settings"; const SYSTEM_PROXY_SETTINGS_KEY: &str = "system_proxy_settings";
const SYSTEM_LANGUAGE_SETTINGS_KEY: &str = "system_language_settings"; const SYSTEM_LANGUAGE_SETTINGS_KEY: &str = "system_language_settings";
const SYSTEM_NOTIFICATION_SETTINGS_KEY: &str = "system_notification_settings";
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
const LANGUAGE_SETTINGS_UPDATED_EVENT: &str = "app://language-settings-updated"; const LANGUAGE_SETTINGS_UPDATED_EVENT: &str = "app://language-settings-updated";
@@ -174,3 +175,46 @@ pub async fn update_system_rendering_settings(
})?; })?;
Ok(settings) Ok(settings)
} }
pub(crate) async fn load_system_notification_settings(
conn: &DatabaseConnection,
) -> Result<SystemNotificationSettings, AppCommandError> {
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::<SystemNotificationSettings>(&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<SystemNotificationSettings, AppCommandError> {
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<SystemNotificationSettings, AppCommandError> {
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)
}

View File

@@ -312,6 +312,8 @@ mod tauri_app {
system_settings::update_system_language_settings, system_settings::update_system_language_settings,
system_settings::get_system_rendering_settings, system_settings::get_system_rendering_settings,
system_settings::update_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::detect_git,
version_control::test_git_path, version_control::test_git_path,
version_control::get_git_settings, version_control::get_git_settings,

View File

@@ -21,7 +21,7 @@ pub use message::{
}; };
pub use system::{ pub use system::{
GitCredentials, GitDetectResult, GitHubAccountsSettings, GitHubTokenValidation, GitSettings, GitCredentials, GitDetectResult, GitHubAccountsSettings, GitHubTokenValidation, GitSettings,
SystemLanguageSettings, SystemProxySettings, SystemLanguageSettings, SystemNotificationSettings, SystemProxySettings,
}; };
#[cfg(feature = "tauri-runtime")] #[cfg(feature = "tauri-runtime")]
pub use system::SystemRenderingSettings; pub use system::SystemRenderingSettings;

View File

@@ -44,6 +44,22 @@ pub struct SystemRenderingSettings {
pub disable_hardware_acceleration: bool, 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 --- // --- Version Control ---
/// Explicit credentials for a single git remote operation. /// Explicit credentials for a single git remote operation.

View File

@@ -3,11 +3,13 @@
import { useCallback, useEffect, useMemo, useState } from "react" import { useCallback, useEffect, useMemo, useState } from "react"
import { import {
ArrowUpCircle, ArrowUpCircle,
Bell,
CheckCircle2, CheckCircle2,
Languages, Languages,
Loader2, Loader2,
MonitorCog, MonitorCog,
RefreshCw, RefreshCw,
Volume2,
Wifi, Wifi,
} from "lucide-react" } from "lucide-react"
import { Github } from "@lobehub/icons" import { Github } from "@lobehub/icons"
@@ -31,9 +33,11 @@ import {
import { import {
getSystemProxySettings, getSystemProxySettings,
getSystemRenderingSettings, getSystemRenderingSettings,
getSystemNotificationSettings,
updateSystemLanguageSettings, updateSystemLanguageSettings,
updateSystemProxySettings, updateSystemProxySettings,
updateSystemRenderingSettings, updateSystemRenderingSettings,
updateSystemNotificationSettings,
} from "@/lib/api" } from "@/lib/api"
import { isDesktop, openUrl } from "@/lib/platform" import { isDesktop, openUrl } from "@/lib/platform"
import type { AppLocale } from "@/lib/types" import type { AppLocale } from "@/lib/types"
@@ -96,6 +100,9 @@ export function SystemNetworkSettings() {
) )
const renderingDirty = const renderingDirty =
processStartLoaded && persistedDisableHwAccel !== processStartDisableHwAccel processStartLoaded && persistedDisableHwAccel !== processStartDisableHwAccel
const [notifTaskCompletion, setNotifTaskCompletion] = useState(true)
const [notifSoundEnabled, setNotifSoundEnabled] = useState(true)
const [savingNotification, setSavingNotification] = useState(false)
const [currentVersion, setCurrentVersion] = useState<string>("") const [currentVersion, setCurrentVersion] = useState<string>("")
const [availableUpdate, setAvailableUpdate] = useState<Update | null>(null) const [availableUpdate, setAvailableUpdate] = useState<Update | null>(null)
const [checkingUpdate, setCheckingUpdate] = useState(false) const [checkingUpdate, setCheckingUpdate] = useState(false)
@@ -171,13 +178,15 @@ export function SystemNetworkSettings() {
setLoadError(null) setLoadError(null)
try { try {
const [proxySettings, version, renderingSettings] = await Promise.all([ const [proxySettings, version, renderingSettings, notificationSettings] =
getSystemProxySettings(), await Promise.all([
getCurrentAppVersion(), getSystemProxySettings(),
renderingSettingsLoadable getCurrentAppVersion(),
? getSystemRenderingSettings() renderingSettingsLoadable
: Promise.resolve(null), ? getSystemRenderingSettings()
]) : Promise.resolve(null),
getSystemNotificationSettings(),
])
setEnabled(proxySettings.enabled) setEnabled(proxySettings.enabled)
setProxyUrl(proxySettings.proxy_url ?? "") setProxyUrl(proxySettings.proxy_url ?? "")
@@ -191,6 +200,10 @@ export function SystemNetworkSettings() {
setProcessStartLoaded(true) setProcessStartLoaded(true)
} }
} }
if (notificationSettings) {
setNotifTaskCompletion(notificationSettings.task_completion)
setNotifSoundEnabled(notificationSettings.sound_enabled)
}
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err) const message = err instanceof Error ? err.message : String(err)
setLoadError(message) setLoadError(message)
@@ -291,6 +304,26 @@ export function SystemNetworkSettings() {
[languageSettings.language, setLanguageSettings, t] [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( const formatUpdateError = useCallback(
(error: unknown, action: UpdateAction): string => { (error: unknown, action: UpdateAction): string => {
const { kind, rawMessage } = normalizeAppUpdateError(error) const { kind, rawMessage } = normalizeAppUpdateError(error)
@@ -716,6 +749,46 @@ export function SystemNetworkSettings() {
</Select> </Select>
</div> </div>
</section> </section>
<section className="rounded-xl border bg-card p-4 space-y-4">
<div className="flex items-center gap-2">
<Bell className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-semibold">{t("notificationTitle")}</h2>
</div>
<p className="text-xs text-muted-foreground leading-5">
{t("notificationDescription")}
</p>
<label className="inline-flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={notifTaskCompletion}
disabled={savingNotification}
onChange={(event) => {
const next = event.target.checked
setNotifTaskCompletion(next)
saveNotificationSettings(next, notifSoundEnabled)
}}
/>
{t("notificationTaskCompletion")}
</label>
<label className="inline-flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={notifSoundEnabled}
disabled={savingNotification || !notifTaskCompletion}
onChange={(event) => {
const next = event.target.checked
setNotifSoundEnabled(next)
saveNotificationSettings(notifTaskCompletion, next)
}}
/>
<Volume2 className="h-3.5 w-3.5 text-muted-foreground" />
{t("notificationSoundEnabled")}
</label>
</section>
</div> </div>
</ScrollArea> </ScrollArea>
) )

View File

@@ -22,6 +22,7 @@ import {
acpCancel, acpCancel,
acpRespondPermission, acpRespondPermission,
acpDisconnect, acpDisconnect,
getSystemNotificationSettings,
} from "@/lib/api" } from "@/lib/api"
import type { import type {
AgentType, AgentType,
@@ -1364,6 +1365,19 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
pushAlertRef.current = pushAlert pushAlertRef.current = pushAlert
}, [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 // Ref-based store — mutations don't trigger React state updates
const storeRef = useRef<InternalStore>({ const storeRef = useRef<InternalStore>({
connections: new Map(), connections: new Map(),
@@ -1948,14 +1962,16 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
} }
// Send OS notification when window is not focused // Send OS notification when window is not focused
{ {
const ns = notificationSettingsRef.current
const nc = storeRef.current.connections.get(contextKey) const nc = storeRef.current.connections.get(contextKey)
if (nc) { if (nc && ns.task_completion) {
const agentLabel = AGENT_LABELS[nc.agentType] const agentLabel = AGENT_LABELS[nc.agentType]
const fn = folderNameRef.current const fn = folderNameRef.current
const title = fn ? `${fn} - Codeg` : "Codeg" const title = fn ? `${fn} - Codeg` : "Codeg"
sendSystemNotification( sendSystemNotification(
title, title,
t("notificationTurnComplete", { agent: agentLabel }) t("notificationTurnComplete", { agent: agentLabel }),
{ sound: ns.sound_enabled }
).catch(() => {}) ).catch(() => {})
} }
} }

View File

@@ -145,7 +145,12 @@
"downloadFailed": "فشل تنزيل حزمة التحديث. يرجى المحاولة مرة أخرى لاحقًا.", "downloadFailed": "فشل تنزيل حزمة التحديث. يرجى المحاولة مرة أخرى لاحقًا.",
"installFailed": "فشل تثبيت التحديث. يرجى إغلاق التطبيق ثم إعادة المحاولة.", "installFailed": "فشل تثبيت التحديث. يرجى إغلاق التطبيق ثم إعادة المحاولة.",
"unknown": "فشل التحديث. يرجى المحاولة مرة أخرى لاحقًا." "unknown": "فشل التحديث. يرجى المحاولة مرة أخرى لاحقًا."
} },
"notificationTitle": "الإشعارات",
"notificationDescription": "تكوين إشعارات إتمام المهام وتنبيهات الصوت.",
"notificationTaskCompletion": "إشعار عند إتمام المهمة",
"notificationSoundEnabled": "تشغيل صوت الإشعار",
"notificationSaveFailed": "فشل حفظ إعدادات الإشعارات: {message}"
}, },
"VersionControlSettings": { "VersionControlSettings": {
"loading": "جارٍ التحميل...", "loading": "جارٍ التحميل...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "Das Update-Paket konnte nicht heruntergeladen werden. Bitte später erneut versuchen.", "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.", "installFailed": "Das Update konnte nicht installiert werden. Bitte App schließen und erneut versuchen.",
"unknown": "Update fehlgeschlagen. Bitte später 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": { "VersionControlSettings": {
"loading": "Laden...", "loading": "Laden...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "Failed to download update package. Please try again later.", "downloadFailed": "Failed to download update package. Please try again later.",
"installFailed": "Failed to install update. Please close the app and try again.", "installFailed": "Failed to install update. Please close the app and try again.",
"unknown": "Update failed. Please try again later." "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": { "VersionControlSettings": {
"loading": "Loading...", "loading": "Loading...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "No se pudo descargar el paquete de actualización. Inténtalo más tarde.", "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.", "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." "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": { "VersionControlSettings": {
"loading": "Cargando...", "loading": "Cargando...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "Impossible de télécharger le paquet de mise à jour. Veuillez réessayer plus tard.", "downloadFailed": "Impossible de télécharger le paquet de mise à jour. Veuillez réessayer plus tard.",
"installFailed": "Impossible dinstaller la mise à jour. Fermez lapp puis réessayez.", "installFailed": "Impossible dinstaller la mise à jour. Fermez lapp puis réessayez.",
"unknown": "La mise à jour a échoué. Veuillez réessayer plus tard." "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": { "VersionControlSettings": {
"loading": "Chargement...", "loading": "Chargement...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "更新パッケージのダウンロードに失敗しました。しばらくしてから再試行してください。", "downloadFailed": "更新パッケージのダウンロードに失敗しました。しばらくしてから再試行してください。",
"installFailed": "更新のインストールに失敗しました。アプリを閉じて再試行してください。", "installFailed": "更新のインストールに失敗しました。アプリを閉じて再試行してください。",
"unknown": "更新に失敗しました。しばらくしてから再試行してください。" "unknown": "更新に失敗しました。しばらくしてから再試行してください。"
} },
"notificationTitle": "通知",
"notificationDescription": "タスク完了通知とサウンドアラートを設定します。",
"notificationTaskCompletion": "タスク完了時に通知する",
"notificationSoundEnabled": "通知音を再生する",
"notificationSaveFailed": "通知設定の保存に失敗しました:{message}"
}, },
"VersionControlSettings": { "VersionControlSettings": {
"loading": "読み込み中...", "loading": "読み込み中...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "업데이트 패키지 다운로드에 실패했습니다. 잠시 후 다시 시도하세요.", "downloadFailed": "업데이트 패키지 다운로드에 실패했습니다. 잠시 후 다시 시도하세요.",
"installFailed": "업데이트 설치에 실패했습니다. 앱을 종료한 뒤 다시 시도하세요.", "installFailed": "업데이트 설치에 실패했습니다. 앱을 종료한 뒤 다시 시도하세요.",
"unknown": "업데이트에 실패했습니다. 잠시 후 다시 시도하세요." "unknown": "업데이트에 실패했습니다. 잠시 후 다시 시도하세요."
} },
"notificationTitle": "알림",
"notificationDescription": "작업 완료 알림 및 소리 알림을 설정합니다.",
"notificationTaskCompletion": "작업 완료 시 알림",
"notificationSoundEnabled": "알림 소리 재생",
"notificationSaveFailed": "알림 설정 저장 실패: {message}"
}, },
"VersionControlSettings": { "VersionControlSettings": {
"loading": "로딩 중...", "loading": "로딩 중...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "Falha ao baixar o pacote de atualização. Tente novamente mais tarde.", "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.", "installFailed": "Falha ao instalar a atualização. Feche o app e tente novamente.",
"unknown": "Falha na atualização. Tente novamente mais tarde." "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": { "VersionControlSettings": {
"loading": "Carregando...", "loading": "Carregando...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "下载更新包失败,请稍后重试。", "downloadFailed": "下载更新包失败,请稍后重试。",
"installFailed": "安装更新失败,请关闭应用后重试。", "installFailed": "安装更新失败,请关闭应用后重试。",
"unknown": "更新失败,请稍后重试。" "unknown": "更新失败,请稍后重试。"
} },
"notificationTitle": "通知",
"notificationDescription": "配置任务完成通知和声音提醒。",
"notificationTaskCompletion": "任务完成时通知",
"notificationSoundEnabled": "播放通知声音",
"notificationSaveFailed": "保存通知设置失败:{message}"
}, },
"VersionControlSettings": { "VersionControlSettings": {
"loading": "加载中...", "loading": "加载中...",

View File

@@ -145,7 +145,12 @@
"downloadFailed": "下載更新包失敗,請稍後再試。", "downloadFailed": "下載更新包失敗,請稍後再試。",
"installFailed": "安裝更新失敗,請關閉應用後重試。", "installFailed": "安裝更新失敗,請關閉應用後重試。",
"unknown": "更新失敗,請稍後再試。" "unknown": "更新失敗,請稍後再試。"
} },
"notificationTitle": "通知",
"notificationDescription": "設定任務完成通知和聲音提醒。",
"notificationTaskCompletion": "任務完成時通知",
"notificationSoundEnabled": "播放通知聲音",
"notificationSaveFailed": "儲存通知設定失敗:{message}"
}, },
"VersionControlSettings": { "VersionControlSettings": {
"loading": "載入中...", "loading": "載入中...",

View File

@@ -48,6 +48,7 @@ import type {
SystemLanguageSettings, SystemLanguageSettings,
SystemProxySettings, SystemProxySettings,
SystemRenderingSettings, SystemRenderingSettings,
SystemNotificationSettings,
GitCredentials, GitCredentials,
GitDetectResult, GitDetectResult,
PackageManagerInfo, PackageManagerInfo,
@@ -469,6 +470,16 @@ export async function updateSystemRenderingSettings(
return getTransport().call("update_system_rendering_settings", { settings }) return getTransport().call("update_system_rendering_settings", { settings })
} }
export async function getSystemNotificationSettings(): Promise<SystemNotificationSettings> {
return getTransport().call("get_system_notification_settings")
}
export async function updateSystemNotificationSettings(
settings: SystemNotificationSettings
): Promise<SystemNotificationSettings> {
return getTransport().call("update_system_notification_settings", { settings })
}
// --- Version Control --- // --- Version Control ---
export async function detectGit(): Promise<GitDetectResult> { export async function detectGit(): Promise<GitDetectResult> {

View File

@@ -1,9 +1,31 @@
import { getTransport } from "./transport" import { getTransport } from "./transport"
import { isDesktop } 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( export async function sendSystemNotification(
title: string, title: string,
body: string body: string,
options?: { sound?: boolean }
): Promise<void> { ): Promise<void> {
if (!document.hidden) return if (!document.hidden) return
if (isDesktop()) { if (isDesktop()) {
@@ -19,4 +41,8 @@ export async function sendSystemNotification(
} }
} }
} }
if (options?.sound !== false) {
playNotificationSound()
}
} }

View File

@@ -638,6 +638,11 @@ export interface SystemRenderingSettings {
disable_hardware_acceleration: boolean disable_hardware_acceleration: boolean
} }
export interface SystemNotificationSettings {
task_completion: boolean
sound_enabled: boolean
}
// --- Version Control --- // --- Version Control ---
export interface GitCredentials { export interface GitCredentials {