diff --git a/.vscode/settings.json b/.vscode/settings.json index d6b2adb8d..79af9c7d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { + "explorer.excludeGitIgnore": false, + "search.useParentIgnoreFiles": false, + "search.useIgnoreFiles": false, "files.watcherExclude": { "**/routeTree.gen.ts": true }, diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index 1f483be3e..3d541d212 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -26,50 +26,41 @@ { "identifier": "opener:allow-open-path", "allow": [ - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" }, - { "path": "$DOWNLOAD/*" }, - { "path": "$DOWNLOAD/**" } + { "path": "$DATA/hyprnote/**/*" }, + { "path": "$DOWNLOAD/**/*" } ] }, { "identifier": "fs:allow-mkdir", "allow": [ - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" } + { "path": "$DATA/hyprnote/**/*" } ] }, { "identifier": "fs:allow-exists", "allow": [ { "path": "/Applications/*" }, - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" } + { "path": "$DATA/hyprnote/**/*" } ] }, { "identifier": "fs:allow-read-file", "allow": [ - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" } + { "path": "$DATA/hyprnote/**/*" } ] }, { "identifier": "fs:allow-write-text-file", "allow": [ - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" }, - { "path": "$DOWNLOAD/*" }, - { "path": "$DOWNLOAD/**" } + { "path": "$DATA/hyprnote/**/*" }, + { "path": "$DOWNLOAD/**/*" } ] }, { "identifier": "fs:allow-write-file", "allow": [ - { "path": "$APPDATA/*" }, - { "path": "$APPDATA/**" }, - { "path": "$DOWNLOAD/*" }, - { "path": "$DOWNLOAD/**" } + { "path": "$DATA/hyprnote/**/*" }, + { "path": "$DOWNLOAD/**/*" } ] }, "db2:default", @@ -88,7 +79,19 @@ "template:default", "notification:default", "shell:allow-open", - "shell:allow-execute", + { + "identifier": "shell:allow-execute", + "allow": [ + { + "name": "exec-sh", + "cmd": "sh", + "args": [ + "-c", + { "validator": ".+" } + ] + } + ] + }, "misc:default", "os:default", "detect:default", diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 8e96a5568..690927be8 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -16,7 +16,7 @@ "security": { "assetProtocol": { "enable": true, - "scope": ["$APPDATA/**", "$APPCACHE/**", "$APPLOCALDATA/**", "$APPLOG/**"] + "scope": ["**/*"] } } }, diff --git a/apps/desktop/src/components/main/body/sessions/note-input/header.tsx b/apps/desktop/src/components/main/body/sessions/note-input/header.tsx index 5d3372aae..000cd2a49 100644 --- a/apps/desktop/src/components/main/body/sessions/note-input/header.tsx +++ b/apps/desktop/src/components/main/body/sessions/note-input/header.tsx @@ -296,7 +296,6 @@ export function Header({ editorTabs, currentTab, handleTabChange, - isInactive, isEditing, setIsEditing, }: { @@ -304,20 +303,21 @@ export function Header({ editorTabs: EditorView[]; currentTab: EditorView; handleTabChange: (view: EditorView) => void; - isInactive: boolean; isEditing: boolean; setIsEditing: (isEditing: boolean) => void; }) { - const isBatchProcessing = useListener((state) => sessionId in state.batch); + const sessionMode = useListener((state) => state.getSessionMode(sessionId)); + const isBatchProcessing = sessionMode === "running_batch"; + const isLiveProcessing = sessionMode === "running_active"; if (editorTabs.length === 1 && editorTabs[0].type === "raw") { return null; } const showProgress = - currentTab.type === "transcript" && (isInactive || isBatchProcessing); + currentTab.type === "transcript" && !isLiveProcessing && isBatchProcessing; const showEditingControls = - currentTab.type === "transcript" && isInactive && !isBatchProcessing; + currentTab.type === "transcript" && isLiveProcessing && !isBatchProcessing; return (
diff --git a/apps/desktop/src/components/main/body/sessions/note-input/index.tsx b/apps/desktop/src/components/main/body/sessions/note-input/index.tsx index 7ec5e0d1a..0033bb020 100644 --- a/apps/desktop/src/components/main/body/sessions/note-input/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/note-input/index.tsx @@ -4,7 +4,6 @@ import { useHotkeys } from "react-hotkeys-hook"; import type { TiptapEditor } from "@hypr/tiptap/editor"; import { cn } from "@hypr/utils"; -import { useListener } from "../../../../../contexts/listener"; import { useAutoEnhance } from "../../../../../hooks/useAutoEnhance"; import { useAutoTitle } from "../../../../../hooks/useAutoTitle"; import { type Tab, useTabs } from "../../../../../store/zustand/tabs"; @@ -23,7 +22,6 @@ export function NoteInput({ const editorTabs = useEditorTabs({ sessionId: tab.id }); const updateSessionTabState = useTabs((state) => state.updateSessionTabState); const editorRef = useRef<{ editor: TiptapEditor | null }>(null); - const inactive = useListener((state) => state.live.status === "inactive"); const [isEditing, setIsEditing] = useState(false); const sessionId = tab.id; @@ -62,7 +60,6 @@ export function NoteInput({ editorTabs={editorTabs} currentTab={currentTab} handleTabChange={handleTabChange} - isInactive={inactive} isEditing={isEditing} setIsEditing={setIsEditing} /> diff --git a/apps/desktop/src/components/settings/ai/llm/configure.tsx b/apps/desktop/src/components/settings/ai/llm/configure.tsx index df784d2dc..d591dae9c 100644 --- a/apps/desktop/src/components/settings/ai/llm/configure.tsx +++ b/apps/desktop/src/components/settings/ai/llm/configure.tsx @@ -13,12 +13,7 @@ import { cn } from "@hypr/utils"; import { useBillingAccess } from "../../../../billing"; import { aiProviderSchema } from "../../../../store/tinybase/main"; import * as main from "../../../../store/tinybase/main"; -import { - FormField, - PlanLockMessage, - StyledStreamdown, - useProvider, -} from "../shared"; +import { FormField, StyledStreamdown, useProvider } from "../shared"; import { ProviderId, PROVIDERS } from "./shared"; export function ConfigureProviders() { @@ -107,56 +102,49 @@ function NonHyprProviderCard({ - {locked ? ( - - ) : ( -
{ - e.preventDefault(); - e.stopPropagation(); - }} - > - {!config.baseUrl && ( - - {(field) => ( - - )} - - )} - {config?.apiKey && ( - - {(field) => ( - - )} - - )} - {config.baseUrl && ( -
- - Advanced - -
- - {(field) => ( - - )} - -
-
- )} -
- )} + +
{ + e.preventDefault(); + e.stopPropagation(); + }} + > + {!config.baseUrl && ( + + {(field) => ( + + )} + + )} + {config?.apiKey && ( + + {(field) => ( + + )} + + )} + {config.baseUrl && ( +
+ + Advanced + +
+ + {(field) => ( + + )} + +
+
+ )} +
); @@ -190,15 +178,12 @@ function HyprProviderCard({ {icon} {providerName} - Recommended + {locked ? "Pro Required" : "Recommended"}
- {locked ? ( - - ) : null} ); diff --git a/apps/desktop/src/components/settings/ai/llm/select.tsx b/apps/desktop/src/components/settings/ai/llm/select.tsx index 9a6a719c2..a36a24eaa 100644 --- a/apps/desktop/src/components/settings/ai/llm/select.tsx +++ b/apps/desktop/src/components/settings/ai/llm/select.tsx @@ -110,6 +110,7 @@ export function SelectProviderAndModel() { {PROVIDERS.map((provider) => { const locked = provider.requiresPro && !billing.isPro; const configured = configuredProviders[provider.id]; + return ( {provider.displayName} - {locked ? ( - - Upgrade to Pro to use this provider. - - ) : null} ); diff --git a/apps/desktop/src/components/settings/ai/shared/index.tsx b/apps/desktop/src/components/settings/ai/shared/index.tsx index b006d1906..773eab7af 100644 --- a/apps/desktop/src/components/settings/ai/shared/index.tsx +++ b/apps/desktop/src/components/settings/ai/shared/index.tsx @@ -120,11 +120,3 @@ export function FormField({ ); } - -export function PlanLockMessage({ message }: { message: string }) { - return ( -
- {message} -
- ); -} diff --git a/apps/desktop/src/components/settings/ai/stt/configure.tsx b/apps/desktop/src/components/settings/ai/stt/configure.tsx index 0740311e2..08d8c06ae 100644 --- a/apps/desktop/src/components/settings/ai/stt/configure.tsx +++ b/apps/desktop/src/components/settings/ai/stt/configure.tsx @@ -27,12 +27,7 @@ import { registerDownloadProgressCallback, unregisterDownloadProgressCallback, } from "../../../task-manager"; -import { - FormField, - PlanLockMessage, - StyledStreamdown, - useProvider, -} from "../shared"; +import { FormField, StyledStreamdown, useProvider } from "../shared"; import { ProviderId, PROVIDERS, sttModelQueries } from "./shared"; export function ConfigureProviders() { @@ -111,54 +106,47 @@ function NonHyprProviderCard({ - {locked ? ( - - ) : ( -
{ - e.preventDefault(); - e.stopPropagation(); - }} - > - {!config.baseUrl && ( - - {(field) => ( - - )} - - )} - + + { + e.preventDefault(); + e.stopPropagation(); + }} + > + {!config.baseUrl && ( + {(field) => ( - + )} - {config.baseUrl && ( -
- - Advanced - -
- - {(field) => ( - - )} - -
-
+ )} + + {(field) => ( + )} - - )} + + {config.baseUrl && ( +
+ + Advanced + +
+ + {(field) => ( + + )} + +
+
+ )} +
); diff --git a/apps/desktop/src/components/settings/ai/stt/health.tsx b/apps/desktop/src/components/settings/ai/stt/health.tsx index 1e2c9db1c..22e2e1bda 100644 --- a/apps/desktop/src/components/settings/ai/stt/health.tsx +++ b/apps/desktop/src/components/settings/ai/stt/health.tsx @@ -24,9 +24,9 @@ function useConnectionHealth(): Parameters[0] { ] as const); const isCloud = - current_stt_provider === "hyprnote" || current_stt_model === "cloud"; + (current_stt_provider === "hyprnote" && current_stt_model === "cloud") || + current_stt_provider !== "hyprnote"; - console.log(conn); if (isCloud) { return conn ? { status: "success" } diff --git a/apps/desktop/src/config/registry.ts b/apps/desktop/src/config/registry.ts index 9a765c054..02b3f65b5 100644 --- a/apps/desktop/src/config/registry.ts +++ b/apps/desktop/src/config/registry.ts @@ -119,6 +119,8 @@ export const CONFIG_REGISTRY = { (model.startsWith("am-") || model.startsWith("Quantized")) ) { await localSttCommands.startServer(model as SupportedSttModel); + } else { + await localSttCommands.stopServer(null); } }, }, diff --git a/apps/desktop/src/config/use-config.ts b/apps/desktop/src/config/use-config.ts index 9ffb5b371..757ff8a37 100644 --- a/apps/desktop/src/config/use-config.ts +++ b/apps/desktop/src/config/use-config.ts @@ -99,12 +99,21 @@ export function useConfigSideEffects() { type GetConfigFn = (k: K) => ConfigValueType; const storedValue = configs[key] !== undefined ? configs[key] : definition.default; - ( - definition.sideEffect as ( - value: unknown, - getConfig: GetConfigFn, - ) => void | Promise - )(storedValue, getConfig); + + try { + const result = ( + definition.sideEffect as ( + value: unknown, + getConfig: GetConfigFn, + ) => void | Promise + )(storedValue, getConfig); + + Promise.resolve(result).catch((error) => { + console.error("Side effect error:", error); + }); + } catch (error) { + console.error("Side effect error:", error); + } } } } diff --git a/apps/desktop/src/store/tinybase/main.ts b/apps/desktop/src/store/tinybase/main.ts index 9e6fa2f34..aac997a7b 100644 --- a/apps/desktop/src/store/tinybase/main.ts +++ b/apps/desktop/src/store/tinybase/main.ts @@ -159,23 +159,30 @@ export const StoreComponent = ({ persist = true }: { persist?: boolean }) => { const localPersister2 = useCreatePersister( store, async (store) => { - exists("sessions", { baseDir: BaseDirectory.AppData }).then( - async (exists) => { - if (!exists) { - mkdir("sessions", { baseDir: BaseDirectory.AppData }); - } - }, - ); + try { + const dirExists = await exists("hyprnote/sessions", { + baseDir: BaseDirectory.Data, + }); + if (!dirExists) { + await mkdir("hyprnote/sessions", { + baseDir: BaseDirectory.Data, + recursive: true, + }); + } + } catch (error) { + console.error("Failed to create sessions directory:", error); + throw error; + } const persister = createLocalPersister2( store as Store, async (session) => { if (session.enhanced_md) { await writeTextFile( - `sessions/${session.id}.md`, + `hyprnote/sessions/${session.id}.md`, session.enhanced_md, { - baseDir: BaseDirectory.AppData, + baseDir: BaseDirectory.Data, }, ); } diff --git a/plugins/db/src/ext.rs b/plugins/db/src/ext.rs index e6a388301..503d2bc9b 100644 --- a/plugins/db/src/ext.rs +++ b/plugins/db/src/ext.rs @@ -34,8 +34,9 @@ impl> DatabasePluginExt for T { } fn db_local_path(&self) -> Result { + use tauri::path::BaseDirectory; let v = { - let dir = dirs::data_dir().unwrap().join("hyprnote"); + let dir = self.path().resolve("hyprnote", BaseDirectory::Data)?; std::fs::create_dir_all(&dir)?; dir.join("db.sqlite").to_str().unwrap().to_string() diff --git a/plugins/db2/src/ext.rs b/plugins/db2/src/ext.rs index bacfaad76..84595796f 100644 --- a/plugins/db2/src/ext.rs +++ b/plugins/db2/src/ext.rs @@ -27,7 +27,8 @@ impl> Database2PluginExt for T { .await .unwrap() } else { - let dir_path = dirs::data_dir().unwrap().join("hyprnote"); + use tauri::path::BaseDirectory; + let dir_path = self.path().resolve("hyprnote", BaseDirectory::Data)?; std::fs::create_dir_all(&dir_path)?; let file_path = dir_path.join("db.sqlite"); diff --git a/plugins/listener/src/actors/controller.rs b/plugins/listener/src/actors/controller.rs index a1177c8fb..8e375a358 100644 --- a/plugins/listener/src/actors/controller.rs +++ b/plugins/listener/src/actors/controller.rs @@ -275,11 +275,17 @@ impl ControllerActor { supervisor: ActorCell, state: &ControllerState, ) -> Result, ActorProcessingErr> { + use tauri::{path::BaseDirectory, Manager}; + let app_dir = state + .app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| Box::new(e) as ActorProcessingErr)?; let (rec_ref, _) = Actor::spawn_linked( Some(RecorderActor::name()), RecorderActor, RecArgs { - app_dir: dirs::data_dir().unwrap().join("hyprnote").join("sessions"), + app_dir, session_id: state.params.session_id.clone(), }, supervisor, diff --git a/plugins/local-llm/src/lib.rs b/plugins/local-llm/src/lib.rs index 407f336eb..7b824dae2 100644 --- a/plugins/local-llm/src/lib.rs +++ b/plugins/local-llm/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; -use tauri::{path::BaseDirectory, Manager, Wry}; +use tauri::{Manager, Wry}; use tokio::sync::Mutex; use hypr_llm::ModelManager; @@ -67,9 +67,11 @@ pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new(PLUGIN_NAME) .invoke_handler(specta_builder.invoke_handler()) .setup(move |app, _api| { + use tauri::path::BaseDirectory; + use tauri::Manager as _; specta_builder.mount_events(app); - let data_dir = dirs::data_dir().unwrap().join("hyprnote"); + let data_dir = app.path().resolve("hyprnote", BaseDirectory::Data)?; let models_dir = app.models_dir(); // for backward compatibility diff --git a/plugins/local-stt/src/ext.rs b/plugins/local-stt/src/ext.rs index bf3ef5dc9..430dbf3ab 100644 --- a/plugins/local-stt/src/ext.rs +++ b/plugins/local-stt/src/ext.rs @@ -49,11 +49,16 @@ pub trait LocalSttPluginExt { impl> LocalSttPluginExt for T { fn models_dir(&self) -> PathBuf { - dirs::data_dir() - .unwrap() - .join("hyprnote") - .join("models") - .join("stt") + use tauri::path::BaseDirectory; + self.path() + .resolve("hyprnote/models/stt", BaseDirectory::Data) + .unwrap_or_else(|_| { + dirs::data_dir() + .unwrap_or_default() + .join("hyprnote") + .join("models") + .join("stt") + }) } fn list_ggml_backends(&self) -> Vec { diff --git a/plugins/local-stt/src/server/supervisor.rs b/plugins/local-stt/src/server/supervisor.rs index d449adce6..5165f2b81 100644 --- a/plugins/local-stt/src/server/supervisor.rs +++ b/plugins/local-stt/src/server/supervisor.rs @@ -20,8 +20,8 @@ pub async fn spawn_stt_supervisor( ) -> Result<(ActorRef, crate::SupervisorHandle), ActorProcessingErr> { let options = DynamicSupervisorOptions { max_children: Some(1), - max_restarts: 15, - max_window: Duration::from_secs(10), + max_restarts: 100, + max_window: Duration::from_secs(60 * 3), reset_after: Some(Duration::from_secs(30)), }; diff --git a/plugins/misc/src/commands.rs b/plugins/misc/src/commands.rs index a625f9fb9..5f4bc5d58 100644 --- a/plugins/misc/src/commands.rs +++ b/plugins/misc/src/commands.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use tauri::{path::BaseDirectory, Manager}; use tauri_plugin_opener::OpenerExt; use crate::audio::import_audio; @@ -35,7 +36,10 @@ pub async fn audio_exist( app: tauri::AppHandle, session_id: String, ) -> Result { - let data_dir = dirs::data_dir().unwrap().join("hyprnote").join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| e.to_string())?; let session_dir = data_dir.join(session_id); ["audio.wav", "audio.ogg"] @@ -54,7 +58,10 @@ pub async fn audio_delete( app: tauri::AppHandle, session_id: String, ) -> Result<(), String> { - let data_dir = dirs::data_dir().unwrap().join("hyprnote").join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| e.to_string())?; let session_dir = data_dir.join(session_id); ["audio.wav", "audio.ogg"] @@ -87,10 +94,10 @@ fn audio_import_internal( session_id: &str, source_path: &str, ) -> Result { - let data_dir = dirs::data_dir() - .ok_or_else(|| AudioImportError::PathResolver("Failed to get data directory".to_string()))? - .join("hyprnote") - .join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| AudioImportError::PathResolver(e.to_string()))?; let session_dir = data_dir.join(session_id); std::fs::create_dir_all(&session_dir)?; @@ -120,7 +127,10 @@ pub async fn audio_path( app: tauri::AppHandle, session_id: String, ) -> Result { - let data_dir = dirs::data_dir().unwrap().join("hyprnote").join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| e.to_string())?; let session_dir = data_dir.join(session_id); let path = ["audio.ogg", "audio.wav"] @@ -138,7 +148,10 @@ pub async fn audio_open( app: tauri::AppHandle, session_id: String, ) -> Result<(), String> { - let data_dir = dirs::data_dir().unwrap().join("hyprnote").join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| e.to_string())?; let session_dir = data_dir.join(session_id); app.opener() @@ -154,7 +167,10 @@ pub async fn delete_session_folder( app: tauri::AppHandle, session_id: String, ) -> Result<(), String> { - let data_dir = dirs::data_dir().unwrap().join("hyprnote").join("sessions"); + let data_dir = app + .path() + .resolve("hyprnote/sessions", BaseDirectory::Data) + .map_err(|e| e.to_string())?; let session_dir = data_dir.join(session_id); if session_dir.exists() { diff --git a/plugins/tracing/src/errors.rs b/plugins/tracing/src/errors.rs index 9dbce37bb..0881f7652 100644 --- a/plugins/tracing/src/errors.rs +++ b/plugins/tracing/src/errors.rs @@ -1,7 +1,10 @@ use serde::{ser::Serializer, Serialize}; #[derive(Debug, thiserror::Error)] -pub enum Error {} +pub enum Error { + #[error("{0}")] + PathResolver(String), +} impl Serialize for Error { fn serialize(&self, serializer: S) -> std::result::Result diff --git a/plugins/tracing/src/ext.rs b/plugins/tracing/src/ext.rs index 23f9a92e2..a89af7814 100644 --- a/plugins/tracing/src/ext.rs +++ b/plugins/tracing/src/ext.rs @@ -7,7 +7,11 @@ pub trait TracingPluginExt { impl> TracingPluginExt for T { fn logs_dir(&self) -> Result { - let logs_dir = dirs::home_dir().unwrap().join("Library/Logs/hyprnote"); + use tauri::{path::BaseDirectory, Manager}; + let logs_dir = self + .path() + .resolve("hyprnote", BaseDirectory::Data) + .map_err(|e| crate::Error::PathResolver(e.to_string()))?; let _ = std::fs::create_dir_all(&logs_dir); Ok(logs_dir) }