Compare commits

...
2 Commits
12 changed files with 279 additions and 184 deletions
+8 -6
View File
@@ -5,7 +5,7 @@ A local markdown note-taking app built with Tauri, SvelteKit, and Rust.
Your notes are stored as standard Markdown files on your local filesystem. Your notes are stored as standard Markdown files on your local filesystem.
No cloud, no lock-in. No cloud, no lock-in.
## Download (v1.1.5) ## Download (v1.1.6)
### Linux ### Linux
@@ -23,19 +23,19 @@ curl -fsSL https://repo.arkhost.com/gpg.key | sudo gpg --dearmor -o /usr/share/k
#### AppImage (Arch, Fedora 43+, openSUSE Tumbleweed) #### AppImage (Arch, Fedora 43+, openSUSE Tumbleweed)
[Download AppImage](https://download.helixnotes.com/releases/v1.1.5/HelixNotes_1.1.5_amd64.AppImage) [Download AppImage](https://download.helixnotes.com/releases/v1.1.6/HelixNotes_1.1.6_amd64.AppImage)
#### .deb (manual) #### .deb (manual)
[Download .deb](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.5/HelixNotes_1.1.5_amd64.deb) — Ubuntu 22.04+ [Download .deb](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.6/HelixNotes_1.1.6_amd64.deb) — Ubuntu 22.04+
### Windows ### Windows
[Download Installer](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.5/HelixNotes_1.1.5_x64-setup.exe) — Windows 10/11 [Download Installer](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.6/HelixNotes_1.1.6_x64-setup.exe) — Windows 10/11
### macOS ### macOS
[Download .dmg](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.5/HelixNotes_1.1.5_x64.dmg) — macOS (Intel, runs on Apple Silicon via Rosetta) [Download .dmg](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.1.6/HelixNotes_1.1.6_x64.dmg) — macOS (Intel, runs on Apple Silicon via Rosetta)
--- ---
@@ -57,7 +57,9 @@ All releases: [codeberg.org/ArkHost/HelixNotes/releases](https://codeberg.org/Ar
- **Tag management** — organize notes with tags, bulk edit from context menu - **Tag management** — organize notes with tags, bulk edit from context menu
- **Drag-and-drop** — move notes between notebooks, reorganize notebooks by dragging - **Drag-and-drop** — move notes between notebooks, reorganize notebooks by dragging
- **Obsidian import** — convert Obsidian wiki-links to standard markdown - **Obsidian import** — convert Obsidian wiki-links to standard markdown
- **Themes** — light/dark mode with customizable accent colors and fonts - **Multi-window** — open notes in separate windows (right-click → "Open in New Window")
- **File associations** — open .md files from your file manager directly in HelixNotes
- **Themes** — light/dark mode with customizable accent colors, fonts, and line height
- **Focus mode** — distraction-free writing - **Focus mode** — distraction-free writing
- **View mode** — read-only toggle for distraction-free reading - **View mode** — read-only toggle for distraction-free reading
- **Local** — everything stays on your machine - **Local** — everything stays on your machine
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "helixnotes", "name": "helixnotes",
"private": true, "private": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"version": "1.1.6", "version": "1.1.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
+106 -106
View File
@@ -89,9 +89,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.101" version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -174,7 +174,7 @@ dependencies = [
"futures-lite", "futures-lite",
"parking", "parking",
"polling", "polling",
"rustix 1.1.3", "rustix 1.1.4",
"slab", "slab",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -205,7 +205,7 @@ dependencies = [
"cfg-if", "cfg-if",
"event-listener", "event-listener",
"futures-lite", "futures-lite",
"rustix 1.1.3", "rustix 1.1.4",
] ]
[[package]] [[package]]
@@ -216,7 +216,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -231,7 +231,7 @@ dependencies = [
"cfg-if", "cfg-if",
"futures-core", "futures-core",
"futures-io", "futures-io",
"rustix 1.1.3", "rustix 1.1.4",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"windows-sys 0.61.2", "windows-sys 0.61.2",
@@ -251,7 +251,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -388,7 +388,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -414,9 +414,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.1" version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]] [[package]]
name = "byte-unit" name = "byte-unit"
@@ -605,9 +605,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.43" version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
@@ -784,7 +784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -794,7 +794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -818,7 +818,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -829,14 +829,14 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.6" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde_core", "serde_core",
@@ -850,7 +850,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -863,7 +863,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -923,7 +923,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -946,7 +946,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1050,7 +1050,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1208,7 +1208,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1330,7 +1330,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1597,7 +1597,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1688,7 +1688,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1750,7 +1750,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "helixnotes" name = "helixnotes"
version = "1.1.6" version = "1.1.7"
dependencies = [ dependencies = [
"chrono", "chrono",
"dirs", "dirs",
@@ -2248,9 +2248,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.85" version = "0.3.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@@ -2393,7 +2393,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"libc", "libc",
"redox_syscall 0.7.1", "redox_syscall 0.7.2",
] ]
[[package]] [[package]]
@@ -2404,9 +2404,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.11.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "litemap" name = "litemap"
@@ -2481,7 +2481,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -2725,7 +2725,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3200,7 +3200,7 @@ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3308,7 +3308,7 @@ dependencies = [
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix 1.1.3", "rustix 1.1.4",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -3349,7 +3349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3691,9 +3691,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.7.1" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
] ]
@@ -3726,7 +3726,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3754,9 +3754,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.9" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rend" name = "rend"
@@ -3976,22 +3976,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.3" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.11.0", "linux-raw-sys 0.12.1",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.36" version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -4140,7 +4140,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4157,9 +4157,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.6.0" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"core-foundation", "core-foundation",
@@ -4170,9 +4170,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.16.0" version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -4245,7 +4245,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4256,7 +4256,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4280,7 +4280,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4341,7 +4341,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4376,7 +4376,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4586,9 +4586,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.116" version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4612,7 +4612,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4817,7 +4817,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4936,7 +4936,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"syn 2.0.116", "syn 2.0.117",
"tauri-utils", "tauri-utils",
"thiserror 2.0.18", "thiserror 2.0.18",
"time", "time",
@@ -4954,7 +4954,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
@@ -5211,14 +5211,14 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.25.0" version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.4.1", "getrandom 0.4.1",
"once_cell", "once_cell",
"rustix 1.1.3", "rustix 1.1.4",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -5259,7 +5259,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5270,7 +5270,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5356,7 +5356,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5551,7 +5551,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5835,9 +5835,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.108" version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -5848,9 +5848,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.58" version = "0.4.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -5862,9 +5862,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.108" version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -5872,22 +5872,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.108" version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.108" version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -5954,9 +5954,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.85" version = "0.3.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -6056,7 +6056,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -6183,7 +6183,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -6194,7 +6194,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -6562,7 +6562,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"indexmap 2.13.0", "indexmap 2.13.0",
"prettyplease", "prettyplease",
"syn 2.0.116", "syn 2.0.117",
"wasm-metadata", "wasm-metadata",
"wit-bindgen-core", "wit-bindgen-core",
"wit-component", "wit-component",
@@ -6578,7 +6578,7 @@ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"wit-bindgen-core", "wit-bindgen-core",
"wit-bindgen-rust", "wit-bindgen-rust",
] ]
@@ -6708,7 +6708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [ dependencies = [
"libc", "libc",
"rustix 1.1.3", "rustix 1.1.4",
] ]
[[package]] [[package]]
@@ -6741,15 +6741,15 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"synstructure", "synstructure",
] ]
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.13.2" version = "5.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
@@ -6767,7 +6767,7 @@ dependencies = [
"hex", "hex",
"libc", "libc",
"ordered-stream", "ordered-stream",
"rustix 1.1.3", "rustix 1.1.4",
"serde", "serde",
"serde_repr", "serde_repr",
"tracing", "tracing",
@@ -6782,14 +6782,14 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.13.2" version = "5.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222"
dependencies = [ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"zbus_names", "zbus_names",
"zvariant", "zvariant",
"zvariant_utils", "zvariant_utils",
@@ -6823,7 +6823,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -6843,7 +6843,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"synstructure", "synstructure",
] ]
@@ -6883,7 +6883,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -6963,9 +6963,9 @@ dependencies = [
[[package]] [[package]]
name = "zvariant" name = "zvariant"
version = "5.9.2" version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b"
dependencies = [ dependencies = [
"endi", "endi",
"enumflags2", "enumflags2",
@@ -6977,14 +6977,14 @@ dependencies = [
[[package]] [[package]]
name = "zvariant_derive" name = "zvariant_derive"
version = "5.9.2" version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
dependencies = [ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"zvariant_utils", "zvariant_utils",
] ]
@@ -6997,6 +6997,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.116", "syn 2.0.117",
"winnow 0.7.14", "winnow 0.7.14",
] ]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "helixnotes" name = "helixnotes"
version = "1.1.6" version = "1.1.7"
description = "Local markdown note-taking app" description = "Local markdown note-taking app"
authors = ["HelixNotes"] authors = ["HelixNotes"]
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
+52 -21
View File
@@ -6,6 +6,7 @@ use crate::types::AiStreamEvent;
const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages"; const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages";
const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions"; const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions";
const OLLAMA_DEFAULT_URL: &str = "http://localhost:11434";
pub fn ai_request( pub fn ai_request(
app: AppHandle, app: AppHandle,
@@ -15,6 +16,7 @@ pub fn ai_request(
system_prompt: String, system_prompt: String,
user_message: String, user_message: String,
request_id: String, request_id: String,
base_url: Option<String>,
) { ) {
std::thread::spawn(move || { std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
@@ -23,7 +25,22 @@ pub fn ai_request(
"openai" => { "openai" => {
stream_openai( stream_openai(
&app, &app,
&api_key, OPENAI_API_URL,
Some(&api_key),
&model,
&system_prompt,
&user_message,
&request_id,
)
.await
}
"ollama" => {
let url = base_url.as_deref().unwrap_or(OLLAMA_DEFAULT_URL);
let url = format!("{}/v1/chat/completions", url.trim_end_matches('/'));
stream_openai(
&app,
&url,
None,
&model, &model,
&system_prompt, &system_prompt,
&user_message, &user_message,
@@ -188,7 +205,8 @@ async fn stream_anthropic(
async fn stream_openai( async fn stream_openai(
app: &AppHandle, app: &AppHandle,
api_key: &str, url: &str,
api_key: Option<&str>,
model: &str, model: &str,
system_prompt: &str, system_prompt: &str,
user_message: &str, user_message: &str,
@@ -224,10 +242,15 @@ async fn stream_openai(
body["temperature"] = json!(0.7); body["temperature"] = json!(0.7);
} }
let response = client let mut req = client
.post(OPENAI_API_URL) .post(url)
.header("Authorization", format!("Bearer {}", api_key)) .header("content-type", "application/json");
.header("content-type", "application/json")
if let Some(key) = api_key {
req = req.header("Authorization", format!("Bearer {}", key));
}
let response = req
.json(&body) .json(&body)
.send() .send()
.await .await
@@ -325,9 +348,19 @@ async fn stream_openai(
Ok(()) Ok(())
} }
pub async fn test_connection(provider: &str, api_key: &str, model: &str) -> Result<String, String> { pub async fn test_connection(
provider: &str,
api_key: &str,
model: &str,
base_url: Option<&str>,
) -> Result<String, String> {
match provider { match provider {
"openai" => test_openai(api_key, model).await, "openai" => test_openai(OPENAI_API_URL, Some(api_key), model).await,
"ollama" => {
let url = base_url.unwrap_or(OLLAMA_DEFAULT_URL);
let url = format!("{}/v1/chat/completions", url.trim_end_matches('/'));
test_openai(&url, None, model).await
}
_ => test_anthropic(api_key, model).await, _ => test_anthropic(api_key, model).await,
} }
} }
@@ -365,19 +398,12 @@ async fn test_anthropic(api_key: &str, model: &str) -> Result<String, String> {
} }
} }
async fn test_openai(api_key: &str, model: &str) -> Result<String, String> { async fn test_openai(url: &str, api_key: Option<&str>, model: &str) -> Result<String, String> {
let client = Client::new(); let client = Client::new();
let is_gpt5 = model.starts_with("gpt-5");
let token_key = if is_gpt5 {
"max_completion_tokens"
} else {
"max_tokens"
};
let body = json!({ let body = json!({
"model": model, "model": model,
token_key: 10, "max_tokens": 10,
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -386,10 +412,15 @@ async fn test_openai(api_key: &str, model: &str) -> Result<String, String> {
] ]
}); });
let response = client let mut req = client
.post(OPENAI_API_URL) .post(url)
.header("Authorization", format!("Bearer {}", api_key)) .header("content-type", "application/json");
.header("content-type", "application/json")
if let Some(key) = api_key {
req = req.header("Authorization", format!("Bearer {}", key));
}
let response = req
.json(&body) .json(&body)
.send() .send()
.await .await
+22 -7
View File
@@ -1334,11 +1334,13 @@ pub fn set_ai_settings(
api_key: Option<String>, api_key: Option<String>,
model: String, model: String,
writing_style: Option<String>, writing_style: Option<String>,
base_url: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut config = state.config.lock().map_err(|e| e.to_string())?; let mut config = state.config.lock().map_err(|e| e.to_string())?;
let key = api_key.filter(|k| !k.is_empty()); let key = api_key.filter(|k| !k.is_empty());
match provider.as_deref() { match provider.as_deref() {
Some("openai") => config.openai_api_key = key, Some("openai") => config.openai_api_key = key,
Some("ollama") => config.ollama_base_url = base_url.filter(|u| !u.trim().is_empty()),
_ => config.ai_api_key = key, _ => config.ai_api_key = key,
} }
config.ai_provider = provider; config.ai_provider = provider;
@@ -1350,27 +1352,36 @@ pub fn set_ai_settings(
#[tauri::command] #[tauri::command]
pub fn test_ai_connection(app: AppHandle) -> Result<(), String> { pub fn test_ai_connection(app: AppHandle) -> Result<(), String> {
let (provider, api_key, model) = { let (provider, api_key, model, base_url) = {
let state = app.state::<AppState>(); let state = app.state::<AppState>();
let config = state.config.lock().map_err(|e| e.to_string())?; let config = state.config.lock().map_err(|e| e.to_string())?;
let provider = config let provider = config
.ai_provider .ai_provider
.clone() .clone()
.unwrap_or_else(|| "anthropic".to_string()); .unwrap_or_else(|| "anthropic".to_string());
let key = if provider == "openai" { let key = if provider == "ollama" {
// Ollama doesn't require an API key
Some(String::new())
} else if provider == "openai" {
config.openai_api_key.clone() config.openai_api_key.clone()
} else { } else {
config.ai_api_key.clone() config.ai_api_key.clone()
} }
.ok_or("No API key configured")?; .ok_or("No API key configured")?;
let model = config.ai_model.clone(); let model = config.ai_model.clone();
(provider, key, model) let base_url = config.ollama_base_url.clone();
(provider, key, model, base_url)
}; };
std::thread::spawn(move || { std::thread::spawn(move || {
use tauri::Emitter; use tauri::Emitter;
let rt = tokio::runtime::Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(crate::ai::test_connection(&provider, &api_key, &model)); let result = rt.block_on(crate::ai::test_connection(
&provider,
&api_key,
&model,
base_url.as_deref(),
));
match result { match result {
Ok(msg) => { Ok(msg) => {
let _ = app.emit( let _ = app.emit(
@@ -1397,14 +1408,16 @@ pub fn ai_ask(
custom_prompt: Option<String>, custom_prompt: Option<String>,
request_id: String, request_id: String,
) -> Result<(), String> { ) -> Result<(), String> {
let (provider, api_key, model, writing_style) = { let (provider, api_key, model, writing_style, base_url) = {
let state = app.state::<AppState>(); let state = app.state::<AppState>();
let config = state.config.lock().map_err(|e| e.to_string())?; let config = state.config.lock().map_err(|e| e.to_string())?;
let provider = config let provider = config
.ai_provider .ai_provider
.clone() .clone()
.unwrap_or_else(|| "anthropic".to_string()); .unwrap_or_else(|| "anthropic".to_string());
let key = if provider == "openai" { let key = if provider == "ollama" {
Some(String::new())
} else if provider == "openai" {
config.openai_api_key.clone() config.openai_api_key.clone()
} else { } else {
config.ai_api_key.clone() config.ai_api_key.clone()
@@ -1412,7 +1425,8 @@ pub fn ai_ask(
.ok_or("No API key configured. Go to Settings > AI to set up your API key.")?; .ok_or("No API key configured. Go to Settings > AI to set up your API key.")?;
let model = config.ai_model.clone(); let model = config.ai_model.clone();
let style = config.ai_writing_style.clone(); let style = config.ai_writing_style.clone();
(provider, key, model, style) let base_url = config.ollama_base_url.clone();
(provider, key, model, style, base_url)
}; };
let mut system_prompt = "You are a helpful writing assistant inside a note-taking app called HelixNotes. \ let mut system_prompt = "You are a helpful writing assistant inside a note-taking app called HelixNotes. \
@@ -1458,6 +1472,7 @@ pub fn ai_ask(
system_prompt, system_prompt,
user_message, user_message,
request_id, request_id,
base_url,
); );
Ok(()) Ok(())
} }
+5 -2
View File
@@ -93,6 +93,8 @@ pub struct AppConfig {
pub ai_api_key: Option<String>, pub ai_api_key: Option<String>,
#[serde(default)] #[serde(default)]
pub openai_api_key: Option<String>, pub openai_api_key: Option<String>,
#[serde(default)]
pub ollama_base_url: Option<String>,
#[serde(default = "default_ai_model")] #[serde(default = "default_ai_model")]
pub ai_model: String, pub ai_model: String,
#[serde(default)] #[serde(default)]
@@ -132,7 +134,7 @@ fn default_max_versions() -> u32 {
} }
fn default_ai_model() -> String { fn default_ai_model() -> String {
"claude-sonnet-4-5-20250929".to_string() "claude-sonnet-4-6".to_string()
} }
impl Default for AppConfig { impl Default for AppConfig {
@@ -164,7 +166,8 @@ impl Default for AppConfig {
ai_provider: None, ai_provider: None,
ai_api_key: None, ai_api_key: None,
openai_api_key: None, openai_api_key: None,
ai_model: "claude-sonnet-4-5-20250929".to_string(), ollama_base_url: None,
ai_model: "claude-sonnet-4-6".to_string(),
ai_writing_style: None, ai_writing_style: None,
default_view_mode: false, default_view_mode: false,
show_tray_icon: false, show_tray_icon: false,
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "HelixNotes", "productName": "HelixNotes",
"version": "1.1.6", "version": "1.1.7",
"identifier": "com.helixnotes.app", "identifier": "com.helixnotes.app",
"build": { "build": {
"frontendDist": "../build", "frontendDist": "../build",
+2 -1
View File
@@ -314,8 +314,9 @@ export async function setAiSettings(
apiKey: string | null, apiKey: string | null,
model: string, model: string,
writingStyle: string | null, writingStyle: string | null,
baseUrl: string | null = null,
): Promise<void> { ): Promise<void> {
return invoke("set_ai_settings", { provider, apiKey, model, writingStyle }); return invoke("set_ai_settings", { provider, apiKey, model, writingStyle, baseUrl });
} }
export async function testAiConnection(): Promise<void> { export async function testAiConnection(): Promise<void> {
+5 -1
View File
@@ -2947,14 +2947,18 @@
if (!newTitle) return; if (!newTitle) return;
const oldPath = $activeNotePath; const oldPath = $activeNotePath;
$activeNote.meta.title = newTitle; $activeNote.meta.title = newTitle;
// Update stripped title so restoreTitleH1 uses the new title
if (titleWasStripped) strippedTitle = newTitle;
$editorDirty = true; $editorDirty = true;
autoSave(); // Force save current editor content before renaming so disk is up-to-date
await forceSave();
// Rename file on disk if filename doesn't match the new title // Rename file on disk if filename doesn't match the new title
const filename = oldPath.split('/').pop() ?? ''; const filename = oldPath.split('/').pop() ?? '';
const stem = filename.replace(/\.md$/, ''); const stem = filename.replace(/\.md$/, '');
if (stem !== newTitle) { if (stem !== newTitle) {
try { try {
const newPath = await renameNote(oldPath, newTitle); const newPath = await renameNote(oldPath, newTitle);
loadedPath = newPath;
$activeNotePath = newPath; $activeNotePath = newPath;
notes.update(list => list.map(n => notes.update(list => list.map(n =>
n.path === oldPath n.path === oldPath
+75 -37
View File
@@ -204,18 +204,20 @@
let aiProvider = $state<string | null>($appConfig?.ai_provider ?? null); let aiProvider = $state<string | null>($appConfig?.ai_provider ?? null);
let aiApiKey = $derived.by(() => { let aiApiKey = $derived.by(() => {
if (aiProvider === 'openai') return _openaiKey; if (aiProvider === 'openai') return _openaiKey;
if (aiProvider === 'ollama') return '';
return _anthropicKey; return _anthropicKey;
}); });
let _anthropicKey = $state($appConfig?.ai_api_key ?? ''); let _anthropicKey = $state($appConfig?.ai_api_key ?? '');
let _openaiKey = $state($appConfig?.openai_api_key ?? ''); let _openaiKey = $state($appConfig?.openai_api_key ?? '');
let aiModel = $state($appConfig?.ai_model ?? 'claude-sonnet-4-5-20250929'); let _ollamaBaseUrl = $state($appConfig?.ollama_base_url ?? 'http://localhost:11434');
let aiModel = $state($appConfig?.ai_model ?? 'claude-sonnet-4-6');
let aiWritingStyle = $state($appConfig?.ai_writing_style ?? ''); let aiWritingStyle = $state($appConfig?.ai_writing_style ?? '');
let aiShowKey = $state(false); let aiShowKey = $state(false);
let aiTestLoading = $state(false); let aiTestLoading = $state(false);
let aiTestMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null); let aiTestMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
const anthropicModels = [ const anthropicModels = [
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', desc: 'Balanced' }, { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', desc: 'Balanced' },
{ value: 'claude-opus-4-6', label: 'Claude Opus 4.6', desc: 'Most capable' }, { value: 'claude-opus-4-6', label: 'Claude Opus 4.6', desc: 'Most capable' },
{ value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', desc: 'Fast' }, { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', desc: 'Fast' },
]; ];
@@ -228,13 +230,15 @@
let aiModels = $derived(aiProvider === 'openai' ? openaiModels : anthropicModels); let aiModels = $derived(aiProvider === 'openai' ? openaiModels : anthropicModels);
async function saveAiSettings() { async function saveAiSettings() {
await setAiSettings(aiProvider, aiApiKey || null, aiModel, aiWritingStyle || null); const baseUrl = aiProvider === 'ollama' ? (_ollamaBaseUrl || null) : null;
await setAiSettings(aiProvider, aiApiKey || null, aiModel, aiWritingStyle || null, baseUrl);
if ($appConfig) { if ($appConfig) {
$appConfig = { $appConfig = {
...$appConfig, ...$appConfig,
ai_provider: aiProvider, ai_provider: aiProvider,
ai_api_key: _anthropicKey || null, ai_api_key: _anthropicKey || null,
openai_api_key: _openaiKey || null, openai_api_key: _openaiKey || null,
ollama_base_url: _ollamaBaseUrl || null,
ai_model: aiModel, ai_model: aiModel,
ai_writing_style: aiWritingStyle || null, ai_writing_style: aiWritingStyle || null,
}; };
@@ -981,52 +985,86 @@
<h3>Provider</h3> <h3>Provider</h3>
<div class="setting-options"> <div class="setting-options">
<button class="option-btn" class:active={!aiProvider} onclick={() => { if (!aiProvider) return; aiProvider = null; aiTestMessage = null; saveAiSettings(); }}>Disabled</button> <button class="option-btn" class:active={!aiProvider} onclick={() => { if (!aiProvider) return; aiProvider = null; aiTestMessage = null; saveAiSettings(); }}>Disabled</button>
<button class="option-btn" class:active={aiProvider === 'anthropic'} onclick={() => { if (aiProvider === 'anthropic') return; aiProvider = 'anthropic'; aiModel = 'claude-sonnet-4-5-20250929'; aiTestMessage = null; saveAiSettings(); }}>Anthropic</button> <button class="option-btn" class:active={aiProvider === 'ollama'} onclick={() => { if (aiProvider === 'ollama') return; aiProvider = 'ollama'; aiModel = 'gemma3:4b'; aiTestMessage = null; saveAiSettings(); }}>Ollama</button>
<button class="option-btn" class:active={aiProvider === 'anthropic'} onclick={() => { if (aiProvider === 'anthropic') return; aiProvider = 'anthropic'; aiModel = 'claude-sonnet-4-6'; aiTestMessage = null; saveAiSettings(); }}>Anthropic</button>
<button class="option-btn" class:active={aiProvider === 'openai'} onclick={() => { if (aiProvider === 'openai') return; aiProvider = 'openai'; aiModel = 'gpt-5.2'; aiTestMessage = null; saveAiSettings(); }}>OpenAI</button> <button class="option-btn" class:active={aiProvider === 'openai'} onclick={() => { if (aiProvider === 'openai') return; aiProvider = 'openai'; aiModel = 'gpt-5.2'; aiTestMessage = null; saveAiSettings(); }}>OpenAI</button>
</div> </div>
</div> </div>
{#if aiProvider} {#if aiProvider}
<div class="settings-section"> {#if aiProvider === 'ollama'}
<h3>API Key</h3> <p class="setting-hint" style="color: var(--text-success, #4ade80); margin-top: -4px; margin-bottom: 12px;">Your data stays on your device. No text is sent to any external server.</p>
<div class="ai-key-row"> {:else}
<p class="setting-hint" style="color: var(--text-warning, #f59e0b); margin-top: -4px; margin-bottom: 12px;">Your selected text and note content will be sent to {aiProvider === 'openai' ? 'OpenAI' : 'Anthropic'} servers for processing.</p>
{/if}
{#if aiProvider === 'ollama'}
<div class="settings-section">
<h3>Server URL</h3>
<input <input
type={aiShowKey ? 'text' : 'password'} type="text"
class="ai-key-input" class="ai-key-input"
placeholder={aiProvider === 'openai' ? 'sk-...' : 'sk-ant-...'} placeholder="http://localhost:11434"
value={aiApiKey} value={_ollamaBaseUrl}
oninput={(e) => { const v = (e.target as HTMLInputElement).value; if (aiProvider === 'openai') _openaiKey = v; else _anthropicKey = v; }} oninput={(e) => { _ollamaBaseUrl = (e.target as HTMLInputElement).value; }}
onblur={saveAiSettings} onblur={saveAiSettings}
/> />
<button class="ai-key-toggle" onclick={() => aiShowKey = !aiShowKey} title={aiShowKey ? 'Hide' : 'Show'}> <p class="setting-hint">Ollama server address. Install from <a href="https://ollama.com" target="_blank" class="ai-link">ollama.com</a></p>
{#if aiShowKey}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
{:else}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
{/if}
</button>
</div> </div>
{#if aiProvider === 'openai'} {:else}
<p class="setting-hint">Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank" class="ai-link">platform.openai.com</a></p> <div class="settings-section">
{:else} <h3>API Key</h3>
<p class="setting-hint">Get your API key from <a href="https://console.anthropic.com/settings/keys" target="_blank" class="ai-link">console.anthropic.com</a></p> <div class="ai-key-row">
{/if} <input
</div> type={aiShowKey ? 'text' : 'password'}
class="ai-key-input"
placeholder={aiProvider === 'openai' ? 'sk-...' : 'sk-ant-...'}
value={aiApiKey}
oninput={(e) => { const v = (e.target as HTMLInputElement).value; if (aiProvider === 'openai') _openaiKey = v; else _anthropicKey = v; }}
onblur={saveAiSettings}
/>
<button class="ai-key-toggle" onclick={() => aiShowKey = !aiShowKey} title={aiShowKey ? 'Hide' : 'Show'}>
{#if aiShowKey}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
{:else}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
{/if}
</button>
</div>
{#if aiProvider === 'openai'}
<p class="setting-hint">Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank" class="ai-link">platform.openai.com</a></p>
{:else}
<p class="setting-hint">Get your API key from <a href="https://console.anthropic.com/settings/keys" target="_blank" class="ai-link">console.anthropic.com</a></p>
{/if}
</div>
{/if}
<div class="settings-section"> <div class="settings-section">
<h3>Model</h3> <h3>Model</h3>
<div class="ai-model-options"> {#if aiProvider === 'ollama'}
{#each aiModels as m} <input
<button type="text"
class="ai-model-btn" class="ai-key-input"
class:active={aiModel === m.value} placeholder="gemma3:4b"
onclick={() => { aiModel = m.value; saveAiSettings(); }} value={aiModel}
> oninput={(e) => { aiModel = (e.target as HTMLInputElement).value; }}
<span class="ai-model-name">{m.label}</span> onblur={saveAiSettings}
<span class="ai-model-desc">{m.desc}</span> />
</button> <p class="setting-hint">Enter the model name as shown by <code>ollama list</code></p>
{/each} {:else}
</div> <div class="ai-model-options">
{#each aiModels as m}
<button
class="ai-model-btn"
class:active={aiModel === m.value}
onclick={() => { aiModel = m.value; saveAiSettings(); }}
>
<span class="ai-model-name">{m.label}</span>
<span class="ai-model-desc">{m.desc}</span>
</button>
{/each}
</div>
{/if}
</div> </div>
<div class="settings-section"> <div class="settings-section">
@@ -1044,7 +1082,7 @@
<div class="settings-section"> <div class="settings-section">
<h3>Connection</h3> <h3>Connection</h3>
<button class="import-btn" onclick={handleTestAi} disabled={aiTestLoading || !aiApiKey}> <button class="import-btn" onclick={handleTestAi} disabled={aiTestLoading || (aiProvider !== 'ollama' && !aiApiKey)}>
{#if aiTestLoading} {#if aiTestLoading}
<svg class="spinner-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" opacity="0.25" /><path d="M12 2a10 10 0 019.95 9" /></svg> <svg class="spinner-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" opacity="0.25" /><path d="M12 2a10 10 0 019.95 9" /></svg>
Testing... Testing...
+1
View File
@@ -60,6 +60,7 @@ export interface AppConfig {
ai_provider: string | null; ai_provider: string | null;
ai_api_key: string | null; ai_api_key: string | null;
openai_api_key: string | null; openai_api_key: string | null;
ollama_base_url: string | null;
ai_model: string; ai_model: string;
ai_writing_style: string | null; ai_writing_style: string | null;
default_view_mode: boolean; default_view_mode: boolean;