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 ? (
-
- ) : (
-
- {(field) => (
-
- )}
-
- )}
- {config?.apiKey && (
-
- {(field) => (
-
- )}
-
- )}
- {config.baseUrl && (
-
-
- Advanced
-
-
-
- {(field) => (
-
- )}
-
-
-
- )}
-
- )}
+
+
+ {(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 ? (
-
- ) : (
-
- {(field) => (
-
- )}
-
- )}
-
+
+
{(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)
}