add: 增加任务完成的提示音
This commit is contained in:
@@ -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<string>("")
|
||||
const [availableUpdate, setAvailableUpdate] = useState<Update | null>(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() {
|
||||
</Select>
|
||||
</div>
|
||||
</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>
|
||||
</ScrollArea>
|
||||
)
|
||||
|
||||
@@ -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<InternalStore>({
|
||||
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(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,12 @@
|
||||
"downloadFailed": "فشل تنزيل حزمة التحديث. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"installFailed": "فشل تثبيت التحديث. يرجى إغلاق التطبيق ثم إعادة المحاولة.",
|
||||
"unknown": "فشل التحديث. يرجى المحاولة مرة أخرى لاحقًا."
|
||||
}
|
||||
},
|
||||
"notificationTitle": "الإشعارات",
|
||||
"notificationDescription": "تكوين إشعارات إتمام المهام وتنبيهات الصوت.",
|
||||
"notificationTaskCompletion": "إشعار عند إتمام المهمة",
|
||||
"notificationSoundEnabled": "تشغيل صوت الإشعار",
|
||||
"notificationSaveFailed": "فشل حفظ إعدادات الإشعارات: {message}"
|
||||
},
|
||||
"VersionControlSettings": {
|
||||
"loading": "جارٍ التحميل...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -145,7 +145,12 @@
|
||||
"downloadFailed": "更新パッケージのダウンロードに失敗しました。しばらくしてから再試行してください。",
|
||||
"installFailed": "更新のインストールに失敗しました。アプリを閉じて再試行してください。",
|
||||
"unknown": "更新に失敗しました。しばらくしてから再試行してください。"
|
||||
}
|
||||
},
|
||||
"notificationTitle": "通知",
|
||||
"notificationDescription": "タスク完了通知とサウンドアラートを設定します。",
|
||||
"notificationTaskCompletion": "タスク完了時に通知する",
|
||||
"notificationSoundEnabled": "通知音を再生する",
|
||||
"notificationSaveFailed": "通知設定の保存に失敗しました:{message}"
|
||||
},
|
||||
"VersionControlSettings": {
|
||||
"loading": "読み込み中...",
|
||||
|
||||
@@ -145,7 +145,12 @@
|
||||
"downloadFailed": "업데이트 패키지 다운로드에 실패했습니다. 잠시 후 다시 시도하세요.",
|
||||
"installFailed": "업데이트 설치에 실패했습니다. 앱을 종료한 뒤 다시 시도하세요.",
|
||||
"unknown": "업데이트에 실패했습니다. 잠시 후 다시 시도하세요."
|
||||
}
|
||||
},
|
||||
"notificationTitle": "알림",
|
||||
"notificationDescription": "작업 완료 알림 및 소리 알림을 설정합니다.",
|
||||
"notificationTaskCompletion": "작업 완료 시 알림",
|
||||
"notificationSoundEnabled": "알림 소리 재생",
|
||||
"notificationSaveFailed": "알림 설정 저장 실패: {message}"
|
||||
},
|
||||
"VersionControlSettings": {
|
||||
"loading": "로딩 중...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -145,7 +145,12 @@
|
||||
"downloadFailed": "下载更新包失败,请稍后重试。",
|
||||
"installFailed": "安装更新失败,请关闭应用后重试。",
|
||||
"unknown": "更新失败,请稍后重试。"
|
||||
}
|
||||
},
|
||||
"notificationTitle": "通知",
|
||||
"notificationDescription": "配置任务完成通知和声音提醒。",
|
||||
"notificationTaskCompletion": "任务完成时通知",
|
||||
"notificationSoundEnabled": "播放通知声音",
|
||||
"notificationSaveFailed": "保存通知设置失败:{message}"
|
||||
},
|
||||
"VersionControlSettings": {
|
||||
"loading": "加载中...",
|
||||
|
||||
@@ -145,7 +145,12 @@
|
||||
"downloadFailed": "下載更新包失敗,請稍後再試。",
|
||||
"installFailed": "安裝更新失敗,請關閉應用後重試。",
|
||||
"unknown": "更新失敗,請稍後再試。"
|
||||
}
|
||||
},
|
||||
"notificationTitle": "通知",
|
||||
"notificationDescription": "設定任務完成通知和聲音提醒。",
|
||||
"notificationTaskCompletion": "任務完成時通知",
|
||||
"notificationSoundEnabled": "播放通知聲音",
|
||||
"notificationSaveFailed": "儲存通知設定失敗:{message}"
|
||||
},
|
||||
"VersionControlSettings": {
|
||||
"loading": "載入中...",
|
||||
|
||||
@@ -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<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 ---
|
||||
|
||||
export async function detectGit(): Promise<GitDetectResult> {
|
||||
|
||||
@@ -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<void> {
|
||||
if (!document.hidden) return
|
||||
if (isDesktop()) {
|
||||
@@ -19,4 +41,8 @@ export async function sendSystemNotification(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.sound !== false) {
|
||||
playNotificationSound()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user