diff --git a/apps/desktop/src/components/editor-area/note-header/listen-button.tsx b/apps/desktop/src/components/editor-area/note-header/listen-button.tsx index a95463906..6aa4878c3 100644 --- a/apps/desktop/src/components/editor-area/note-header/listen-button.tsx +++ b/apps/desktop/src/components/editor-area/note-header/listen-button.tsx @@ -11,6 +11,8 @@ import { commands as analyticsCommands } from "@hypr/plugin-analytics"; import { commands as dbCommands } from "@hypr/plugin-db"; import { commands as listenerCommands } from "@hypr/plugin-listener"; import { commands as localSttCommands } from "@hypr/plugin-local-stt"; +import { commands as notificationCommands } from "@hypr/plugin-notification"; +import { commands as windowsCommands } from "@hypr/plugin-windows"; import { Button } from "@hypr/ui/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; import { Spinner } from "@hypr/ui/components/ui/spinner"; @@ -65,15 +67,41 @@ export default function ListenButton({ sessionId }: { sessionId: string }) { } }, [ongoingSessionStatus]); - const handleStartSession = () => { + const handleStartSession = async () => { if (ongoingSessionStatus === "inactive") { - ongoingSessionStore.start(sessionId); - - analyticsCommands.event({ - event: "onboarding_video_started", - distinct_id: userId, - session_id: sessionId, - }); + try { + await ongoingSessionStore.start(sessionId); + + analyticsCommands.event({ + event: "onboarding_video_started", + distinct_id: userId, + session_id: sessionId, + }); + } catch (error) { + console.error("Failed to start recording session:", error); + + const isWindowVisible = await windowsCommands.windowIsVisible({ type: "main" }); + + if (isWindowVisible) { + toast({ + id: "recording-failed", + title: "Recording Failed", + content: "Unable to start recording. Try it again.", + dismissible: true, + duration: 3000, + }); + } else { + const permission = await notificationCommands.checkNotificationPermission(); + if (permission === "Granted") { + await notificationCommands.sendNotification({ + title: "Recording Failed", + message: "Unable to start recording. Try it again.", + url: null, + timeout: { secs: 3, nanos: 0 }, + }); + } + } + } } }; diff --git a/apps/desktop/src/locales/en/messages.po b/apps/desktop/src/locales/en/messages.po index dd4509b97..e9f72e571 100644 --- a/apps/desktop/src/locales/en/messages.po +++ b/apps/desktop/src/locales/en/messages.po @@ -227,8 +227,8 @@ msgstr "(Beta) Upcoming meeting notifications" #. placeholder {0}: disabled ? "Wait..." : isHovered ? "Resume" : "Ended" #. placeholder {0}: disabled ? "Wait..." : "Play again" #: src/components/settings/components/calendar/cloud-calendar-integration-details.tsx:36 -#: src/components/editor-area/note-header/listen-button.tsx:275 -#: src/components/editor-area/note-header/listen-button.tsx:314 +#: src/components/editor-area/note-header/listen-button.tsx:303 +#: src/components/editor-area/note-header/listen-button.tsx:342 msgid "{0}" msgstr "{0}" @@ -439,7 +439,11 @@ msgstr "Create Note" msgid "Current Plan" msgstr "Current Plan" -#: src/components/editor-area/note-header/listen-button.tsx:221 +#: src/components/settings/views/ai.tsx:432 +#~ msgid "Custom Endpoint" +#~ msgstr "Custom Endpoint" + +#: src/components/editor-area/note-header/listen-button.tsx:249 msgid "Custom instruction" msgstr "Custom instruction" @@ -447,6 +451,10 @@ msgstr "Custom instruction" msgid "Custom LLM Endpoint" msgstr "Custom LLM Endpoint" +#: src/components/settings/views/ai.tsx:407 +#~ msgid "Default (llama-3.2-3b-q4)" +#~ msgstr "Default (llama-3.2-3b-q4)" + #: src/components/settings/views/template.tsx:86 #: src/components/settings/views/team.tsx:165 #: src/components/left-sidebar/notes-list.tsx:323 @@ -776,7 +784,7 @@ msgstr "Optional for participant suggestions" msgid "Owner" msgstr "Owner" -#: src/components/editor-area/note-header/listen-button.tsx:398 +#: src/components/editor-area/note-header/listen-button.tsx:426 msgid "Pause" msgstr "Pause" @@ -784,7 +792,7 @@ msgstr "Pause" msgid "people" msgstr "people" -#: src/components/editor-area/note-header/listen-button.tsx:294 +#: src/components/editor-area/note-header/listen-button.tsx:322 msgid "Play video" msgstr "Play video" @@ -796,7 +804,7 @@ msgstr "Pro" msgid "Profile" msgstr "Profile" -#: src/components/editor-area/note-header/listen-button.tsx:230 +#: src/components/editor-area/note-header/listen-button.tsx:258 msgid "Provide descriptions about the meeting. Company specific terms, acronyms, jargons... any thing!" msgstr "Provide descriptions about the meeting. Company specific terms, acronyms, jargons... any thing!" @@ -825,7 +833,7 @@ msgstr "Required to transcribe other people's voice during meetings" msgid "Required to transcribe your voice during meetings" msgstr "Required to transcribe your voice during meetings" -#: src/components/editor-area/note-header/listen-button.tsx:103 +#: src/components/editor-area/note-header/listen-button.tsx:131 msgid "Resume" msgstr "Resume" @@ -899,7 +907,7 @@ msgstr "Speech-to-Text Model" msgid "Start Annual Plan" msgstr "Start Annual Plan" -#: src/components/editor-area/note-header/listen-button.tsx:249 +#: src/components/editor-area/note-header/listen-button.tsx:277 msgid "Start Meeting" msgstr "Start Meeting" @@ -911,7 +919,7 @@ msgstr "Start Monthly Plan" #~ msgid "Start recording" #~ msgstr "Start recording" -#: src/components/editor-area/note-header/listen-button.tsx:406 +#: src/components/editor-area/note-header/listen-button.tsx:434 msgid "Stop" msgstr "Stop" diff --git a/apps/desktop/src/locales/ko/messages.po b/apps/desktop/src/locales/ko/messages.po index 3c191a016..6f4169562 100644 --- a/apps/desktop/src/locales/ko/messages.po +++ b/apps/desktop/src/locales/ko/messages.po @@ -227,8 +227,8 @@ msgstr "" #. placeholder {0}: disabled ? "Wait..." : isHovered ? "Resume" : "Ended" #. placeholder {0}: disabled ? "Wait..." : "Play again" #: src/components/settings/components/calendar/cloud-calendar-integration-details.tsx:36 -#: src/components/editor-area/note-header/listen-button.tsx:275 -#: src/components/editor-area/note-header/listen-button.tsx:314 +#: src/components/editor-area/note-header/listen-button.tsx:303 +#: src/components/editor-area/note-header/listen-button.tsx:342 msgid "{0}" msgstr "" @@ -439,7 +439,11 @@ msgstr "" msgid "Current Plan" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:221 +#: src/components/settings/views/ai.tsx:432 +#~ msgid "Custom Endpoint" +#~ msgstr "" + +#: src/components/editor-area/note-header/listen-button.tsx:249 msgid "Custom instruction" msgstr "" @@ -447,6 +451,10 @@ msgstr "" msgid "Custom LLM Endpoint" msgstr "" +#: src/components/settings/views/ai.tsx:407 +#~ msgid "Default (llama-3.2-3b-q4)" +#~ msgstr "" + #: src/components/settings/views/template.tsx:86 #: src/components/settings/views/team.tsx:165 #: src/components/left-sidebar/notes-list.tsx:323 @@ -776,7 +784,7 @@ msgstr "" msgid "Owner" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:398 +#: src/components/editor-area/note-header/listen-button.tsx:426 msgid "Pause" msgstr "" @@ -784,7 +792,7 @@ msgstr "" msgid "people" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:294 +#: src/components/editor-area/note-header/listen-button.tsx:322 msgid "Play video" msgstr "" @@ -796,7 +804,7 @@ msgstr "" msgid "Profile" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:230 +#: src/components/editor-area/note-header/listen-button.tsx:258 msgid "Provide descriptions about the meeting. Company specific terms, acronyms, jargons... any thing!" msgstr "" @@ -825,7 +833,7 @@ msgstr "" msgid "Required to transcribe your voice during meetings" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:103 +#: src/components/editor-area/note-header/listen-button.tsx:131 msgid "Resume" msgstr "" @@ -899,7 +907,7 @@ msgstr "" msgid "Start Annual Plan" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:249 +#: src/components/editor-area/note-header/listen-button.tsx:277 msgid "Start Meeting" msgstr "" @@ -911,7 +919,7 @@ msgstr "" #~ msgid "Start recording" #~ msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:406 +#: src/components/editor-area/note-header/listen-button.tsx:434 msgid "Stop" msgstr "" diff --git a/apps/docs/data/i18n.json b/apps/docs/data/i18n.json index 4be03df19..0784b032f 100644 --- a/apps/docs/data/i18n.json +++ b/apps/docs/data/i18n.json @@ -1,12 +1,12 @@ [ { "language": "ko", - "total": 243, - "missing": 243 + "total": 245, + "missing": 245 }, { "language": "en (source)", - "total": 243, + "total": 245, "missing": 0 } ] diff --git a/crates/notification2/src/lib.rs b/crates/notification2/src/lib.rs index dba05f4a4..3c177dc80 100644 --- a/crates/notification2/src/lib.rs +++ b/crates/notification2/src/lib.rs @@ -1,4 +1,21 @@ -pub use wezterm::ToastNotification as Notification; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct Notification { + pub title: String, + pub message: String, + pub url: Option, + pub timeout: Option, +} + +impl From for wezterm::ToastNotification { + fn from(notif: Notification) -> Self { + wezterm::ToastNotification { + title: notif.title, + message: notif.message, + url: notif.url, + timeout: notif.timeout, + } + } +} #[cfg(target_os = "macos")] mod macos; @@ -8,7 +25,7 @@ pub fn show(notif: Notification) { return; } - wezterm::show(notif); + wezterm::show(notif.into()); } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)] diff --git a/packages/utils/src/stores/ongoing-session.ts b/packages/utils/src/stores/ongoing-session.ts index 7140fa847..23912a086 100644 --- a/packages/utils/src/stores/ongoing-session.ts +++ b/packages/utils/src/stores/ongoing-session.ts @@ -23,7 +23,7 @@ type Actions = { cancelEnhance: () => void; setEnhanceController: (controller: AbortController | null) => void; setStatus: (status: ListenerState) => void; - start: (sessionId: string) => void; + start: (sessionId: string) => Promise; stop: () => void; pause: () => void; resume: () => void; @@ -90,12 +90,14 @@ export const createOngoingSessionStore = (sessionsStore: ReturnType { + // Note: Return the promise so errors can be caught by callers + return listenerCommands.startSession(sessionId).then(() => { set({ channel, status: "running_active", loading: false }); listenerCommands.subscribe(channel); }).catch((error) => { console.error(error); set(initialState); + throw new Error(error?.message ?? "Failed to start recording session"); }); }, stop: () => { diff --git a/plugins/notification/js/bindings.gen.ts b/plugins/notification/js/bindings.gen.ts index 98b9d2659..fdc3348c9 100644 --- a/plugins/notification/js/bindings.gen.ts +++ b/plugins/notification/js/bindings.gen.ts @@ -7,6 +7,9 @@ export const commands = { +async sendNotification(notif: Notification) : Promise { + return await TAURI_INVOKE("plugin:notification|send_notification", { notif }); +}, async getEventNotification() : Promise { return await TAURI_INVOKE("plugin:notification|get_event_notification"); }, @@ -52,6 +55,8 @@ async stopEventNotification() : Promise { /** user-defined types **/ +export type Duration = { secs: number; nanos: number } +export type Notification = { title: string; message: string; url: string | null; timeout: Duration | null } export type NotificationPermission = "Granted" | "NotGrantedAndShouldRequest" | "NotGrantedAndShouldAskManual" /** tauri-specta globals **/ diff --git a/plugins/notification/src/commands.rs b/plugins/notification/src/commands.rs index 23fd5a835..72f41b5d4 100644 --- a/plugins/notification/src/commands.rs +++ b/plugins/notification/src/commands.rs @@ -1,5 +1,14 @@ use crate::NotificationPluginExt; +#[tauri::command] +#[specta::specta] +pub(crate) async fn send_notification( + app: tauri::AppHandle, + notif: hypr_notification2::Notification, +) -> Result<(), String> { + app.send_notification(notif).map_err(|e| e.to_string()) +} + #[tauri::command] #[specta::specta] pub(crate) async fn get_event_notification( diff --git a/plugins/notification/src/ext.rs b/plugins/notification/src/ext.rs index c43edd586..c85199735 100644 --- a/plugins/notification/src/ext.rs +++ b/plugins/notification/src/ext.rs @@ -7,6 +7,8 @@ use tauri_plugin_store2::StorePluginExt; pub trait NotificationPluginExt { fn notification_store(&self) -> tauri_plugin_store2::ScopedStore; + fn send_notification(&self, notif: hypr_notification2::Notification) -> Result<(), Error>; + fn get_event_notification(&self) -> Result; fn set_event_notification(&self, enabled: bool) -> Result<(), Error>; @@ -31,6 +33,11 @@ impl> NotificationPluginExt for T { self.scoped_store(crate::PLUGIN_NAME).unwrap() } + fn send_notification(&self, notif: hypr_notification2::Notification) -> Result<(), Error> { + hypr_notification2::show(notif); + Ok(()) + } + #[tracing::instrument(skip(self))] fn get_event_notification(&self) -> Result { let store = self.notification_store(); diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs index 6d0a4301c..40b1ea263 100644 --- a/plugins/notification/src/lib.rs +++ b/plugins/notification/src/lib.rs @@ -25,6 +25,7 @@ fn make_specta_builder() -> tauri_specta::Builder { tauri_specta::Builder::::new() .plugin_name(PLUGIN_NAME) .commands(tauri_specta::collect_commands![ + commands::send_notification::, commands::get_event_notification::, commands::set_event_notification::, commands::get_detect_notification::, @@ -87,6 +88,7 @@ mod test { fn create_app(builder: tauri::Builder) -> tauri::App { builder + .plugin(tauri_plugin_store::Builder::default().build()) .plugin(init()) .plugin(tauri_plugin_store::Builder::default().build()) .build(tauri::test::mock_context(tauri::test::noop_assets()))