Mayo commited on
refactor: split modules
Browse files- Cargo.lock +58 -115
- Cargo.toml +8 -2
- {koharu-core → koharu-http}/Cargo.toml +1 -1
- {koharu-core → koharu-http}/README.md +0 -0
- {koharu-core → koharu-http}/src/download.rs +0 -0
- {koharu-core → koharu-http}/src/hf_hub.rs +0 -0
- {koharu-core → koharu-http}/src/http.rs +0 -0
- {koharu-core → koharu-http}/src/lib.rs +0 -0
- {koharu-core → koharu-http}/src/progress.rs +0 -0
- koharu-ml/Cargo.toml +2 -1
- koharu/src/ml.rs → koharu-ml/src/facade.rs +8 -13
- koharu-ml/src/font_detector/mod.rs +2 -47
- koharu-ml/src/hf_hub.rs +3 -3
- koharu-ml/src/lib.rs +2 -1
- koharu/src/llm.rs → koharu-ml/src/llm/facade.rs +6 -6
- koharu-ml/src/llm/mod.rs +1 -0
- koharu-pipeline/Cargo.toml +23 -0
- koharu-pipeline/src/lib.rs +20 -0
- {koharu → koharu-pipeline}/src/operations.rs +11 -45
- {koharu → koharu-pipeline}/src/pipeline.rs +1 -1
- koharu-renderer/Cargo.toml +2 -0
- koharu/src/renderer.rs → koharu-renderer/src/facade.rs +5 -7
- koharu-renderer/src/lib.rs +1 -0
- koharu-renderer/src/renderer.rs +1 -23
- koharu-rpc/Cargo.toml +19 -0
- koharu-rpc/src/lib.rs +4 -0
- koharu-rpc/src/rpc.rs +226 -0
- {koharu → koharu-rpc}/src/server.rs +14 -6
- koharu-runtime/Cargo.toml +1 -1
- koharu-runtime/src/dylib.rs +2 -2
- koharu-runtime/src/zip.rs +1 -1
- koharu-types/Cargo.toml +21 -0
- koharu-types/src/effect.rs +25 -0
- koharu-types/src/font.rs +45 -0
- {koharu → koharu-types}/src/image.rs +2 -2
- koharu/src/state.rs → koharu-types/src/lib.rs +11 -17
- koharu/Cargo.toml +10 -26
- koharu/src/app.rs +18 -23
- koharu/src/khr.rs +0 -144
- koharu/src/lib.rs +0 -10
- koharu/src/rpc.rs +0 -569
Cargo.lock
CHANGED
|
@@ -423,15 +423,6 @@ dependencies = [
|
|
| 423 |
"system-deps",
|
| 424 |
]
|
| 425 |
|
| 426 |
-
[[package]]
|
| 427 |
-
name = "atomic-polyfill"
|
| 428 |
-
version = "1.0.3"
|
| 429 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 430 |
-
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
| 431 |
-
dependencies = [
|
| 432 |
-
"critical-section",
|
| 433 |
-
]
|
| 434 |
-
|
| 435 |
[[package]]
|
| 436 |
name = "atomic-waker"
|
| 437 |
version = "1.1.2"
|
|
@@ -1067,15 +1058,6 @@ dependencies = [
|
|
| 1067 |
"cc",
|
| 1068 |
]
|
| 1069 |
|
| 1070 |
-
[[package]]
|
| 1071 |
-
name = "cobs"
|
| 1072 |
-
version = "0.3.0"
|
| 1073 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1074 |
-
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
| 1075 |
-
dependencies = [
|
| 1076 |
-
"thiserror 2.0.18",
|
| 1077 |
-
]
|
| 1078 |
-
|
| 1079 |
[[package]]
|
| 1080 |
name = "codespan-reporting"
|
| 1081 |
version = "0.12.0"
|
|
@@ -1300,12 +1282,6 @@ dependencies = [
|
|
| 1300 |
"itertools 0.13.0",
|
| 1301 |
]
|
| 1302 |
|
| 1303 |
-
[[package]]
|
| 1304 |
-
name = "critical-section"
|
| 1305 |
-
version = "1.2.0"
|
| 1306 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1307 |
-
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
| 1308 |
-
|
| 1309 |
[[package]]
|
| 1310 |
name = "crossbeam-channel"
|
| 1311 |
version = "0.5.15"
|
|
@@ -1770,18 +1746,6 @@ version = "1.2.2"
|
|
| 1770 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1771 |
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
| 1772 |
|
| 1773 |
-
[[package]]
|
| 1774 |
-
name = "embedded-io"
|
| 1775 |
-
version = "0.4.0"
|
| 1776 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1777 |
-
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
| 1778 |
-
|
| 1779 |
-
[[package]]
|
| 1780 |
-
name = "embedded-io"
|
| 1781 |
-
version = "0.6.1"
|
| 1782 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1783 |
-
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
| 1784 |
-
|
| 1785 |
[[package]]
|
| 1786 |
name = "encode_unicode"
|
| 1787 |
version = "1.0.0"
|
|
@@ -1815,18 +1779,6 @@ dependencies = [
|
|
| 1815 |
"syn 2.0.114",
|
| 1816 |
]
|
| 1817 |
|
| 1818 |
-
[[package]]
|
| 1819 |
-
name = "enum_dispatch"
|
| 1820 |
-
version = "0.3.13"
|
| 1821 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1822 |
-
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
|
| 1823 |
-
dependencies = [
|
| 1824 |
-
"once_cell",
|
| 1825 |
-
"proc-macro2",
|
| 1826 |
-
"quote",
|
| 1827 |
-
"syn 2.0.114",
|
| 1828 |
-
]
|
| 1829 |
-
|
| 1830 |
[[package]]
|
| 1831 |
name = "enumflags2"
|
| 1832 |
version = "0.7.12"
|
|
@@ -3001,15 +2953,6 @@ dependencies = [
|
|
| 3001 |
"smallvec",
|
| 3002 |
]
|
| 3003 |
|
| 3004 |
-
[[package]]
|
| 3005 |
-
name = "hash32"
|
| 3006 |
-
version = "0.2.1"
|
| 3007 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 3008 |
-
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
| 3009 |
-
dependencies = [
|
| 3010 |
-
"byteorder",
|
| 3011 |
-
]
|
| 3012 |
-
|
| 3013 |
[[package]]
|
| 3014 |
name = "hashbrown"
|
| 3015 |
version = "0.12.3"
|
|
@@ -3040,20 +2983,6 @@ dependencies = [
|
|
| 3040 |
"serde_core",
|
| 3041 |
]
|
| 3042 |
|
| 3043 |
-
[[package]]
|
| 3044 |
-
name = "heapless"
|
| 3045 |
-
version = "0.7.17"
|
| 3046 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 3047 |
-
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
| 3048 |
-
dependencies = [
|
| 3049 |
-
"atomic-polyfill",
|
| 3050 |
-
"hash32",
|
| 3051 |
-
"rustc_version",
|
| 3052 |
-
"serde",
|
| 3053 |
-
"spin",
|
| 3054 |
-
"stable_deref_trait",
|
| 3055 |
-
]
|
| 3056 |
-
|
| 3057 |
[[package]]
|
| 3058 |
name = "heck"
|
| 3059 |
version = "0.4.1"
|
|
@@ -4021,38 +3950,22 @@ name = "koharu"
|
|
| 4021 |
version = "0.32.8"
|
| 4022 |
dependencies = [
|
| 4023 |
"anyhow",
|
| 4024 |
-
"async-trait",
|
| 4025 |
-
"axum",
|
| 4026 |
-
"blake3",
|
| 4027 |
"clap",
|
| 4028 |
"dirs",
|
| 4029 |
-
"enum_dispatch",
|
| 4030 |
-
"futures",
|
| 4031 |
"git-version",
|
| 4032 |
-
"icu",
|
| 4033 |
-
"image",
|
| 4034 |
-
"koharu-core",
|
| 4035 |
"koharu-ml",
|
|
|
|
| 4036 |
"koharu-renderer",
|
|
|
|
| 4037 |
"koharu-runtime",
|
|
|
|
| 4038 |
"once_cell",
|
| 4039 |
-
"open",
|
| 4040 |
-
"postcard",
|
| 4041 |
-
"rayon",
|
| 4042 |
"rfd",
|
| 4043 |
-
"rmp-serde",
|
| 4044 |
-
"rmpv",
|
| 4045 |
-
"serde",
|
| 4046 |
-
"serde_bytes",
|
| 4047 |
"serde_json",
|
| 4048 |
-
"strum",
|
| 4049 |
-
"sys-locale",
|
| 4050 |
"tauri",
|
| 4051 |
"tauri-build",
|
| 4052 |
"tauri-plugin-updater",
|
| 4053 |
-
"thiserror 2.0.18",
|
| 4054 |
"tokio",
|
| 4055 |
-
"tower",
|
| 4056 |
"tracing",
|
| 4057 |
"tracing-subscriber",
|
| 4058 |
"windows 0.62.2",
|
|
@@ -4061,7 +3974,7 @@ dependencies = [
|
|
| 4061 |
]
|
| 4062 |
|
| 4063 |
[[package]]
|
| 4064 |
-
name = "koharu-
|
| 4065 |
version = "0.32.8"
|
| 4066 |
dependencies = [
|
| 4067 |
"anyhow",
|
|
@@ -4092,7 +4005,8 @@ dependencies = [
|
|
| 4092 |
"futures",
|
| 4093 |
"image",
|
| 4094 |
"imageproc",
|
| 4095 |
-
"koharu-
|
|
|
|
| 4096 |
"libloading 0.9.0",
|
| 4097 |
"minijinja",
|
| 4098 |
"num_cpus",
|
|
@@ -4113,6 +4027,26 @@ dependencies = [
|
|
| 4113 |
"tracing-subscriber",
|
| 4114 |
]
|
| 4115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4116 |
[[package]]
|
| 4117 |
name = "koharu-renderer"
|
| 4118 |
version = "0.32.8"
|
|
@@ -4127,7 +4061,9 @@ dependencies = [
|
|
| 4127 |
"harfrust",
|
| 4128 |
"icu",
|
| 4129 |
"image",
|
|
|
|
| 4130 |
"once_cell",
|
|
|
|
| 4131 |
"serde",
|
| 4132 |
"skrifa",
|
| 4133 |
"tokio",
|
|
@@ -4135,6 +4071,22 @@ dependencies = [
|
|
| 4135 |
"wgpu",
|
| 4136 |
]
|
| 4137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4138 |
[[package]]
|
| 4139 |
name = "koharu-runtime"
|
| 4140 |
version = "0.32.8"
|
|
@@ -4143,7 +4095,7 @@ dependencies = [
|
|
| 4143 |
"csv",
|
| 4144 |
"flate2",
|
| 4145 |
"futures",
|
| 4146 |
-
"koharu-
|
| 4147 |
"libloading 0.9.0",
|
| 4148 |
"num_cpus",
|
| 4149 |
"once_cell",
|
|
@@ -4156,6 +4108,19 @@ dependencies = [
|
|
| 4156 |
"zip 7.4.0",
|
| 4157 |
]
|
| 4158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4159 |
[[package]]
|
| 4160 |
name = "kuchikiki"
|
| 4161 |
version = "0.8.8-speedreader"
|
|
@@ -5630,19 +5595,6 @@ dependencies = [
|
|
| 5630 |
"portable-atomic",
|
| 5631 |
]
|
| 5632 |
|
| 5633 |
-
[[package]]
|
| 5634 |
-
name = "postcard"
|
| 5635 |
-
version = "1.1.3"
|
| 5636 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 5637 |
-
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
| 5638 |
-
dependencies = [
|
| 5639 |
-
"cobs",
|
| 5640 |
-
"embedded-io 0.4.0",
|
| 5641 |
-
"embedded-io 0.6.1",
|
| 5642 |
-
"heapless",
|
| 5643 |
-
"serde",
|
| 5644 |
-
]
|
| 5645 |
-
|
| 5646 |
[[package]]
|
| 5647 |
name = "potential_utf"
|
| 5648 |
version = "0.1.4"
|
|
@@ -7128,15 +7080,6 @@ dependencies = [
|
|
| 7128 |
"system-deps",
|
| 7129 |
]
|
| 7130 |
|
| 7131 |
-
[[package]]
|
| 7132 |
-
name = "spin"
|
| 7133 |
-
version = "0.9.8"
|
| 7134 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 7135 |
-
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
| 7136 |
-
dependencies = [
|
| 7137 |
-
"lock_api",
|
| 7138 |
-
]
|
| 7139 |
-
|
| 7140 |
[[package]]
|
| 7141 |
name = "spirv"
|
| 7142 |
version = "0.3.0+sdk-1.3.268.0"
|
|
|
|
| 423 |
"system-deps",
|
| 424 |
]
|
| 425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
[[package]]
|
| 427 |
name = "atomic-waker"
|
| 428 |
version = "1.1.2"
|
|
|
|
| 1058 |
"cc",
|
| 1059 |
]
|
| 1060 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1061 |
[[package]]
|
| 1062 |
name = "codespan-reporting"
|
| 1063 |
version = "0.12.0"
|
|
|
|
| 1282 |
"itertools 0.13.0",
|
| 1283 |
]
|
| 1284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
[[package]]
|
| 1286 |
name = "crossbeam-channel"
|
| 1287 |
version = "0.5.15"
|
|
|
|
| 1746 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1747 |
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
| 1748 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1749 |
[[package]]
|
| 1750 |
name = "encode_unicode"
|
| 1751 |
version = "1.0.0"
|
|
|
|
| 1779 |
"syn 2.0.114",
|
| 1780 |
]
|
| 1781 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1782 |
[[package]]
|
| 1783 |
name = "enumflags2"
|
| 1784 |
version = "0.7.12"
|
|
|
|
| 2953 |
"smallvec",
|
| 2954 |
]
|
| 2955 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2956 |
[[package]]
|
| 2957 |
name = "hashbrown"
|
| 2958 |
version = "0.12.3"
|
|
|
|
| 2983 |
"serde_core",
|
| 2984 |
]
|
| 2985 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2986 |
[[package]]
|
| 2987 |
name = "heck"
|
| 2988 |
version = "0.4.1"
|
|
|
|
| 3950 |
version = "0.32.8"
|
| 3951 |
dependencies = [
|
| 3952 |
"anyhow",
|
|
|
|
|
|
|
|
|
|
| 3953 |
"clap",
|
| 3954 |
"dirs",
|
|
|
|
|
|
|
| 3955 |
"git-version",
|
|
|
|
|
|
|
|
|
|
| 3956 |
"koharu-ml",
|
| 3957 |
+
"koharu-pipeline",
|
| 3958 |
"koharu-renderer",
|
| 3959 |
+
"koharu-rpc",
|
| 3960 |
"koharu-runtime",
|
| 3961 |
+
"koharu-types",
|
| 3962 |
"once_cell",
|
|
|
|
|
|
|
|
|
|
| 3963 |
"rfd",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3964 |
"serde_json",
|
|
|
|
|
|
|
| 3965 |
"tauri",
|
| 3966 |
"tauri-build",
|
| 3967 |
"tauri-plugin-updater",
|
|
|
|
| 3968 |
"tokio",
|
|
|
|
| 3969 |
"tracing",
|
| 3970 |
"tracing-subscriber",
|
| 3971 |
"windows 0.62.2",
|
|
|
|
| 3974 |
]
|
| 3975 |
|
| 3976 |
[[package]]
|
| 3977 |
+
name = "koharu-http"
|
| 3978 |
version = "0.32.8"
|
| 3979 |
dependencies = [
|
| 3980 |
"anyhow",
|
|
|
|
| 4005 |
"futures",
|
| 4006 |
"image",
|
| 4007 |
"imageproc",
|
| 4008 |
+
"koharu-http",
|
| 4009 |
+
"koharu-types",
|
| 4010 |
"libloading 0.9.0",
|
| 4011 |
"minijinja",
|
| 4012 |
"num_cpus",
|
|
|
|
| 4027 |
"tracing-subscriber",
|
| 4028 |
]
|
| 4029 |
|
| 4030 |
+
[[package]]
|
| 4031 |
+
name = "koharu-pipeline"
|
| 4032 |
+
version = "0.32.8"
|
| 4033 |
+
dependencies = [
|
| 4034 |
+
"anyhow",
|
| 4035 |
+
"image",
|
| 4036 |
+
"koharu-ml",
|
| 4037 |
+
"koharu-renderer",
|
| 4038 |
+
"koharu-types",
|
| 4039 |
+
"once_cell",
|
| 4040 |
+
"open",
|
| 4041 |
+
"rayon",
|
| 4042 |
+
"serde",
|
| 4043 |
+
"serde_bytes",
|
| 4044 |
+
"strum",
|
| 4045 |
+
"sys-locale",
|
| 4046 |
+
"tokio",
|
| 4047 |
+
"tracing",
|
| 4048 |
+
]
|
| 4049 |
+
|
| 4050 |
[[package]]
|
| 4051 |
name = "koharu-renderer"
|
| 4052 |
version = "0.32.8"
|
|
|
|
| 4061 |
"harfrust",
|
| 4062 |
"icu",
|
| 4063 |
"image",
|
| 4064 |
+
"koharu-types",
|
| 4065 |
"once_cell",
|
| 4066 |
+
"rayon",
|
| 4067 |
"serde",
|
| 4068 |
"skrifa",
|
| 4069 |
"tokio",
|
|
|
|
| 4071 |
"wgpu",
|
| 4072 |
]
|
| 4073 |
|
| 4074 |
+
[[package]]
|
| 4075 |
+
name = "koharu-rpc"
|
| 4076 |
+
version = "0.32.8"
|
| 4077 |
+
dependencies = [
|
| 4078 |
+
"anyhow",
|
| 4079 |
+
"axum",
|
| 4080 |
+
"futures",
|
| 4081 |
+
"koharu-http",
|
| 4082 |
+
"koharu-pipeline",
|
| 4083 |
+
"rmp-serde",
|
| 4084 |
+
"rmpv",
|
| 4085 |
+
"serde",
|
| 4086 |
+
"tokio",
|
| 4087 |
+
"tracing",
|
| 4088 |
+
]
|
| 4089 |
+
|
| 4090 |
[[package]]
|
| 4091 |
name = "koharu-runtime"
|
| 4092 |
version = "0.32.8"
|
|
|
|
| 4095 |
"csv",
|
| 4096 |
"flate2",
|
| 4097 |
"futures",
|
| 4098 |
+
"koharu-http",
|
| 4099 |
"libloading 0.9.0",
|
| 4100 |
"num_cpus",
|
| 4101 |
"once_cell",
|
|
|
|
| 4108 |
"zip 7.4.0",
|
| 4109 |
]
|
| 4110 |
|
| 4111 |
+
[[package]]
|
| 4112 |
+
name = "koharu-types"
|
| 4113 |
+
version = "0.32.8"
|
| 4114 |
+
dependencies = [
|
| 4115 |
+
"anyhow",
|
| 4116 |
+
"blake3",
|
| 4117 |
+
"image",
|
| 4118 |
+
"serde",
|
| 4119 |
+
"serde_bytes",
|
| 4120 |
+
"sys-locale",
|
| 4121 |
+
"tokio",
|
| 4122 |
+
]
|
| 4123 |
+
|
| 4124 |
[[package]]
|
| 4125 |
name = "kuchikiki"
|
| 4126 |
version = "0.8.8-speedreader"
|
|
|
|
| 5595 |
"portable-atomic",
|
| 5596 |
]
|
| 5597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5598 |
[[package]]
|
| 5599 |
name = "potential_utf"
|
| 5600 |
version = "0.1.4"
|
|
|
|
| 7080 |
"system-deps",
|
| 7081 |
]
|
| 7082 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7083 |
[[package]]
|
| 7084 |
name = "spirv"
|
| 7085 |
version = "0.3.0+sdk-1.3.268.0"
|
Cargo.toml
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
[workspace]
|
| 2 |
members = [
|
| 3 |
"koharu",
|
| 4 |
-
"koharu-
|
| 5 |
"koharu-ml",
|
| 6 |
"koharu-runtime",
|
| 7 |
"koharu-renderer",
|
|
|
|
|
|
|
|
|
|
| 8 |
]
|
| 9 |
default-members = ["koharu"]
|
| 10 |
resolver = "3"
|
|
@@ -25,10 +28,13 @@ keywords = ["manga", "ocr", "translation", "deep_learning", "computer_vision"]
|
|
| 25 |
publish = false
|
| 26 |
|
| 27 |
[workspace.dependencies]
|
| 28 |
-
koharu-
|
| 29 |
koharu-runtime = { path = "koharu-runtime", default-features = false }
|
| 30 |
koharu-ml = { path = "koharu-ml", default-features = false }
|
| 31 |
koharu-renderer = { path = "koharu-renderer", default-features = false }
|
|
|
|
|
|
|
|
|
|
| 32 |
candle-transformers = "=0.9.2"
|
| 33 |
candle-core = "=0.9.2"
|
| 34 |
candle-nn = "=0.9.2"
|
|
|
|
| 1 |
[workspace]
|
| 2 |
members = [
|
| 3 |
"koharu",
|
| 4 |
+
"koharu-http",
|
| 5 |
"koharu-ml",
|
| 6 |
"koharu-runtime",
|
| 7 |
"koharu-renderer",
|
| 8 |
+
"koharu-pipeline",
|
| 9 |
+
"koharu-rpc",
|
| 10 |
+
"koharu-types",
|
| 11 |
]
|
| 12 |
default-members = ["koharu"]
|
| 13 |
resolver = "3"
|
|
|
|
| 28 |
publish = false
|
| 29 |
|
| 30 |
[workspace.dependencies]
|
| 31 |
+
koharu-http = { path = "koharu-http", default-features = false }
|
| 32 |
koharu-runtime = { path = "koharu-runtime", default-features = false }
|
| 33 |
koharu-ml = { path = "koharu-ml", default-features = false }
|
| 34 |
koharu-renderer = { path = "koharu-renderer", default-features = false }
|
| 35 |
+
koharu-pipeline = { path = "koharu-pipeline", default-features = false }
|
| 36 |
+
koharu-rpc = { path = "koharu-rpc", default-features = false }
|
| 37 |
+
koharu-types = { path = "koharu-types", default-features = false }
|
| 38 |
candle-transformers = "=0.9.2"
|
| 39 |
candle-core = "=0.9.2"
|
| 40 |
candle-nn = "=0.9.2"
|
{koharu-core → koharu-http}/Cargo.toml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
[package]
|
| 2 |
-
name = "koharu-
|
| 3 |
version.workspace = true
|
| 4 |
edition.workspace = true
|
| 5 |
description.workspace = true
|
|
|
|
| 1 |
[package]
|
| 2 |
+
name = "koharu-http"
|
| 3 |
version.workspace = true
|
| 4 |
edition.workspace = true
|
| 5 |
description.workspace = true
|
{koharu-core → koharu-http}/README.md
RENAMED
|
File without changes
|
{koharu-core → koharu-http}/src/download.rs
RENAMED
|
File without changes
|
{koharu-core → koharu-http}/src/hf_hub.rs
RENAMED
|
File without changes
|
{koharu-core → koharu-http}/src/http.rs
RENAMED
|
File without changes
|
{koharu-core → koharu-http}/src/lib.rs
RENAMED
|
File without changes
|
{koharu-core → koharu-http}/src/progress.rs
RENAMED
|
File without changes
|
koharu-ml/Cargo.toml
CHANGED
|
@@ -12,7 +12,8 @@ keywords.workspace = true
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
| 15 |
-
koharu-
|
|
|
|
| 16 |
anyhow = { workspace = true }
|
| 17 |
clap = { workspace = true }
|
| 18 |
image = { workspace = true }
|
|
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
| 15 |
+
koharu-http = { workspace = true }
|
| 16 |
+
koharu-types = { workspace = true }
|
| 17 |
anyhow = { workspace = true }
|
| 18 |
clap = { workspace = true }
|
| 19 |
image = { workspace = true }
|
koharu/src/ml.rs → koharu-ml/src/facade.rs
RENAMED
|
@@ -1,12 +1,11 @@
|
|
| 1 |
use anyhow::Result;
|
| 2 |
use image::{DynamicImage, Rgba};
|
| 3 |
-
use
|
| 4 |
-
use koharu_ml::font_detector::{self, FontDetector};
|
| 5 |
-
use koharu_ml::lama::{self, Lama};
|
| 6 |
-
use koharu_ml::manga_ocr::{self, MangaOcr};
|
| 7 |
|
| 8 |
-
use crate::
|
| 9 |
-
use crate::
|
|
|
|
|
|
|
| 10 |
|
| 11 |
const NEAR_BLACK_THRESHOLD: u8 = 12;
|
| 12 |
const GRAY_NEAR_BLACK_THRESHOLD: u8 = 60;
|
|
@@ -56,7 +55,7 @@ fn colors_similar(a: [u8; 3], b: [u8; 3]) -> bool {
|
|
| 56 |
&& a[2].abs_diff(b[2]) <= SIMILAR_COLOR_MAX_DIFF
|
| 57 |
}
|
| 58 |
|
| 59 |
-
fn normalize_font_prediction(prediction: &mut
|
| 60 |
prediction.text_color = clamp_near_white(clamp_near_black(prediction.text_color));
|
| 61 |
prediction.stroke_color = clamp_near_white(clamp_near_black(prediction.stroke_color));
|
| 62 |
|
|
@@ -213,11 +212,7 @@ impl Model {
|
|
| 213 |
Ok(result.into())
|
| 214 |
}
|
| 215 |
|
| 216 |
-
pub async fn detect_font(
|
| 217 |
-
&self,
|
| 218 |
-
image: &DynamicImage,
|
| 219 |
-
top_k: usize,
|
| 220 |
-
) -> Result<font_detector::FontPrediction> {
|
| 221 |
let mut results = self
|
| 222 |
.detect_fonts(std::slice::from_ref(image), top_k)
|
| 223 |
.await?;
|
|
@@ -228,7 +223,7 @@ impl Model {
|
|
| 228 |
&self,
|
| 229 |
images: &[DynamicImage],
|
| 230 |
top_k: usize,
|
| 231 |
-
) -> Result<Vec<
|
| 232 |
if images.is_empty() {
|
| 233 |
return Ok(Vec::new());
|
| 234 |
}
|
|
|
|
| 1 |
use anyhow::Result;
|
| 2 |
use image::{DynamicImage, Rgba};
|
| 3 |
+
use koharu_types::{Document, FontPrediction, SerializableDynamicImage, TextBlock, TextStyle};
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
use crate::comic_text_detector::{self, ComicTextDetector};
|
| 6 |
+
use crate::font_detector::{self, FontDetector};
|
| 7 |
+
use crate::lama::{self, Lama};
|
| 8 |
+
use crate::manga_ocr::{self, MangaOcr};
|
| 9 |
|
| 10 |
const NEAR_BLACK_THRESHOLD: u8 = 12;
|
| 11 |
const GRAY_NEAR_BLACK_THRESHOLD: u8 = 60;
|
|
|
|
| 55 |
&& a[2].abs_diff(b[2]) <= SIMILAR_COLOR_MAX_DIFF
|
| 56 |
}
|
| 57 |
|
| 58 |
+
fn normalize_font_prediction(prediction: &mut FontPrediction) {
|
| 59 |
prediction.text_color = clamp_near_white(clamp_near_black(prediction.text_color));
|
| 60 |
prediction.stroke_color = clamp_near_white(clamp_near_black(prediction.stroke_color));
|
| 61 |
|
|
|
|
| 212 |
Ok(result.into())
|
| 213 |
}
|
| 214 |
|
| 215 |
+
pub async fn detect_font(&self, image: &DynamicImage, top_k: usize) -> Result<FontPrediction> {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
let mut results = self
|
| 217 |
.detect_fonts(std::slice::from_ref(image), top_k)
|
| 218 |
.await?;
|
|
|
|
| 223 |
&self,
|
| 224 |
images: &[DynamicImage],
|
| 225 |
top_k: usize,
|
| 226 |
+
) -> Result<Vec<FontPrediction>> {
|
| 227 |
if images.is_empty() {
|
| 228 |
return Ok(Vec::new());
|
| 229 |
}
|
koharu-ml/src/font_detector/mod.rs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
use std::{fs, path::PathBuf};
|
| 2 |
|
|
|
|
| 3 |
use anyhow::{Context, Result};
|
| 4 |
use candle_core::{DType, Device, IndexOp, Tensor};
|
| 5 |
use candle_nn::{
|
|
@@ -7,9 +8,6 @@ use candle_nn::{
|
|
| 7 |
ops::{sigmoid, softmax},
|
| 8 |
};
|
| 9 |
use image::{DynamicImage, GenericImageView, imageops::FilterType};
|
| 10 |
-
use serde::{Deserialize, Serialize};
|
| 11 |
-
|
| 12 |
-
use crate::{define_models, device};
|
| 13 |
|
| 14 |
mod models;
|
| 15 |
pub use models::ModelKind;
|
|
@@ -23,50 +21,7 @@ define_models! {
|
|
| 23 |
FontNames => ("fffonion/yuzumarker-font-detection", "font-labels-ex.json"),
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
pub enum TextDirection {
|
| 28 |
-
Horizontal,
|
| 29 |
-
Vertical,
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 33 |
-
pub struct NamedFontPrediction {
|
| 34 |
-
pub index: usize,
|
| 35 |
-
pub name: String,
|
| 36 |
-
pub language: Option<String>,
|
| 37 |
-
pub probability: f32,
|
| 38 |
-
pub serif: bool,
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 42 |
-
pub struct FontPrediction {
|
| 43 |
-
pub top_fonts: Vec<(usize, f32)>,
|
| 44 |
-
pub named_fonts: Vec<NamedFontPrediction>,
|
| 45 |
-
pub direction: TextDirection,
|
| 46 |
-
pub text_color: [u8; 3],
|
| 47 |
-
pub stroke_color: [u8; 3],
|
| 48 |
-
pub font_size_px: f32,
|
| 49 |
-
pub stroke_width_px: f32,
|
| 50 |
-
pub line_height: f32,
|
| 51 |
-
pub angle_deg: f32,
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
// implement Default for FontPrediction
|
| 55 |
-
impl Default for FontPrediction {
|
| 56 |
-
fn default() -> Self {
|
| 57 |
-
Self {
|
| 58 |
-
top_fonts: Vec::new(),
|
| 59 |
-
named_fonts: Vec::new(),
|
| 60 |
-
direction: TextDirection::Horizontal,
|
| 61 |
-
text_color: [0, 0, 0],
|
| 62 |
-
stroke_color: [0, 0, 0],
|
| 63 |
-
font_size_px: 0.0,
|
| 64 |
-
stroke_width_px: 0.0,
|
| 65 |
-
line_height: 1.0,
|
| 66 |
-
angle_deg: 0.0,
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
}
|
| 70 |
|
| 71 |
pub struct FontDetector {
|
| 72 |
model: models::Model,
|
|
|
|
| 1 |
use std::{fs, path::PathBuf};
|
| 2 |
|
| 3 |
+
use crate::{define_models, device};
|
| 4 |
use anyhow::{Context, Result};
|
| 5 |
use candle_core::{DType, Device, IndexOp, Tensor};
|
| 6 |
use candle_nn::{
|
|
|
|
| 8 |
ops::{sigmoid, softmax},
|
| 9 |
};
|
| 10 |
use image::{DynamicImage, GenericImageView, imageops::FilterType};
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
mod models;
|
| 13 |
pub use models::ModelKind;
|
|
|
|
| 21 |
FontNames => ("fffonion/yuzumarker-font-detection", "font-labels-ex.json"),
|
| 22 |
}
|
| 23 |
|
| 24 |
+
pub use koharu_types::{FontPrediction, NamedFontPrediction, TextDirection};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
pub struct FontDetector {
|
| 27 |
model: models::Model,
|
koharu-ml/src/hf_hub.rs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
#[allow(unused_imports)]
|
| 2 |
-
pub use
|
| 3 |
#[allow(unused_imports)]
|
| 4 |
-
pub use
|
| 5 |
|
| 6 |
#[macro_export]
|
| 7 |
macro_rules! define_models {
|
|
@@ -19,7 +19,7 @@ macro_rules! define_models {
|
|
| 19 |
use strum::EnumProperty;
|
| 20 |
let repo = self.get_str("repo").expect("repo property");
|
| 21 |
let filename = self.get_str("filename").expect("filename property");
|
| 22 |
-
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
|
|
|
| 1 |
#[allow(unused_imports)]
|
| 2 |
+
pub use koharu_http::download::model;
|
| 3 |
#[allow(unused_imports)]
|
| 4 |
+
pub use koharu_http::hf_hub::set_cache_dir;
|
| 5 |
|
| 6 |
#[macro_export]
|
| 7 |
macro_rules! define_models {
|
|
|
|
| 19 |
use strum::EnumProperty;
|
| 20 |
let repo = self.get_str("repo").expect("repo property");
|
| 21 |
let filename = self.get_str("filename").expect("filename property");
|
| 22 |
+
koharu_http::download::model(repo, filename).await
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
koharu-ml/src/lib.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
mod hf_hub;
|
| 2 |
|
| 3 |
pub mod comic_text_detector;
|
|
|
|
| 4 |
pub mod font_detector;
|
| 5 |
pub mod lama;
|
| 6 |
pub mod llm;
|
|
@@ -9,7 +10,7 @@ pub mod manga_ocr;
|
|
| 9 |
use anyhow::Result;
|
| 10 |
use candle_core::{Device, utils::metal_is_available};
|
| 11 |
|
| 12 |
-
pub use
|
| 13 |
pub use llm::{language_from_tag, set_default_locale, set_locale, supported_locales};
|
| 14 |
|
| 15 |
/// Name of the compute device being used.
|
|
|
|
| 1 |
mod hf_hub;
|
| 2 |
|
| 3 |
pub mod comic_text_detector;
|
| 4 |
+
pub mod facade;
|
| 5 |
pub mod font_detector;
|
| 6 |
pub mod lama;
|
| 7 |
pub mod llm;
|
|
|
|
| 10 |
use anyhow::Result;
|
| 11 |
use candle_core::{Device, utils::metal_is_available};
|
| 12 |
|
| 13 |
+
pub use koharu_http::hf_hub::set_cache_dir;
|
| 14 |
pub use llm::{language_from_tag, set_default_locale, set_locale, supported_locales};
|
| 15 |
|
| 16 |
/// Name of the compute device being used.
|
koharu/src/llm.rs → koharu-ml/src/llm/facade.rs
RENAMED
|
@@ -1,12 +1,14 @@
|
|
| 1 |
-
use koharu_ml::llm::{GenerateOptions, Llm, ModelId};
|
| 2 |
-
use serde::Serialize;
|
| 3 |
use std::sync::Arc;
|
|
|
|
|
|
|
| 4 |
use strum::Display;
|
| 5 |
use tokio::sync::RwLock;
|
| 6 |
|
| 7 |
-
use
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
pub use
|
| 10 |
|
| 11 |
#[derive(Debug, Clone, Serialize)]
|
| 12 |
#[serde(rename_all = "camelCase")]
|
|
@@ -128,13 +130,11 @@ impl Model {
|
|
| 128 |
}
|
| 129 |
|
| 130 |
/// Returns a read guard to the internal state.
|
| 131 |
-
/// Callers can inspect `State` directly while holding the guard.
|
| 132 |
pub async fn get(&self) -> tokio::sync::RwLockReadGuard<'_, State> {
|
| 133 |
self.state.read().await
|
| 134 |
}
|
| 135 |
|
| 136 |
/// Returns a write guard to the internal state.
|
| 137 |
-
/// Needed for operations that mutate the loaded LLM instance.
|
| 138 |
pub async fn get_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, State> {
|
| 139 |
self.state.write().await
|
| 140 |
}
|
|
|
|
|
|
|
|
|
|
| 1 |
use std::sync::Arc;
|
| 2 |
+
|
| 3 |
+
use serde::Serialize;
|
| 4 |
use strum::Display;
|
| 5 |
use tokio::sync::RwLock;
|
| 6 |
|
| 7 |
+
use koharu_types::{Document, TextBlock};
|
| 8 |
+
|
| 9 |
+
use super::{GenerateOptions, Llm, ModelId};
|
| 10 |
|
| 11 |
+
pub use super::prefetch;
|
| 12 |
|
| 13 |
#[derive(Debug, Clone, Serialize)]
|
| 14 |
#[serde(rename_all = "camelCase")]
|
|
|
|
| 130 |
}
|
| 131 |
|
| 132 |
/// Returns a read guard to the internal state.
|
|
|
|
| 133 |
pub async fn get(&self) -> tokio::sync::RwLockReadGuard<'_, State> {
|
| 134 |
self.state.read().await
|
| 135 |
}
|
| 136 |
|
| 137 |
/// Returns a write guard to the internal state.
|
|
|
|
| 138 |
pub async fn get_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, State> {
|
| 139 |
self.state.write().await
|
| 140 |
}
|
koharu-ml/src/llm/mod.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
mod model;
|
| 2 |
pub mod prompt;
|
| 3 |
mod quantized_hunyuan_dense;
|
|
|
|
| 1 |
+
pub mod facade;
|
| 2 |
mod model;
|
| 3 |
pub mod prompt;
|
| 4 |
mod quantized_hunyuan_dense;
|
koharu-pipeline/Cargo.toml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "koharu-pipeline"
|
| 3 |
+
version.workspace = true
|
| 4 |
+
edition.workspace = true
|
| 5 |
+
description.workspace = true
|
| 6 |
+
authors.workspace = true
|
| 7 |
+
publish.workspace = true
|
| 8 |
+
|
| 9 |
+
[dependencies]
|
| 10 |
+
koharu-types = { workspace = true }
|
| 11 |
+
koharu-ml = { workspace = true }
|
| 12 |
+
koharu-renderer = { workspace = true }
|
| 13 |
+
anyhow = { workspace = true }
|
| 14 |
+
image = { workspace = true }
|
| 15 |
+
once_cell = { workspace = true }
|
| 16 |
+
open = { workspace = true }
|
| 17 |
+
rayon = { workspace = true }
|
| 18 |
+
serde = { workspace = true }
|
| 19 |
+
serde_bytes = { workspace = true }
|
| 20 |
+
strum = { workspace = true }
|
| 21 |
+
sys-locale = { workspace = true }
|
| 22 |
+
tokio = { workspace = true }
|
| 23 |
+
tracing = { workspace = true }
|
koharu-pipeline/src/lib.rs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pub mod operations;
|
| 2 |
+
pub mod pipeline;
|
| 3 |
+
|
| 4 |
+
use std::sync::Arc;
|
| 5 |
+
|
| 6 |
+
use koharu_ml::DeviceName;
|
| 7 |
+
use koharu_renderer::facade::Renderer;
|
| 8 |
+
use koharu_types::AppState;
|
| 9 |
+
use tokio::sync::RwLock;
|
| 10 |
+
|
| 11 |
+
#[derive(Clone)]
|
| 12 |
+
pub struct AppResources {
|
| 13 |
+
pub state: AppState,
|
| 14 |
+
pub ml: Arc<koharu_ml::facade::Model>,
|
| 15 |
+
pub llm: Arc<koharu_ml::llm::facade::Model>,
|
| 16 |
+
pub renderer: Arc<Renderer>,
|
| 17 |
+
pub ml_device: DeviceName,
|
| 18 |
+
pub pipeline: Arc<RwLock<Option<pipeline::PipelineHandle>>>,
|
| 19 |
+
pub version: &'static str,
|
| 20 |
+
}
|
{koharu → koharu-pipeline}/src/operations.rs
RENAMED
|
@@ -9,17 +9,14 @@ use strum::IntoEnumIterator;
|
|
| 9 |
use sys_locale::get_locale;
|
| 10 |
use tracing::instrument;
|
| 11 |
|
| 12 |
-
use
|
| 13 |
-
app::AppResources,
|
| 14 |
-
image::SerializableDynamicImage,
|
| 15 |
-
khr::{deserialize_khr, has_khr_magic, serialize_khr},
|
| 16 |
-
llm,
|
| 17 |
-
state::{Document, TextBlock},
|
| 18 |
-
version,
|
| 19 |
-
};
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -42,7 +39,10 @@ pub struct OpenExternalPayload {
|
|
| 42 |
pub url: String,
|
| 43 |
}
|
| 44 |
|
| 45 |
-
pub fn open_external(
|
|
|
|
|
|
|
|
|
|
| 46 |
open::that(&payload.url)?;
|
| 47 |
Ok(())
|
| 48 |
}
|
|
@@ -142,33 +142,6 @@ pub struct FileResult {
|
|
| 142 |
pub content_type: String,
|
| 143 |
}
|
| 144 |
|
| 145 |
-
pub async fn save_documents(state: AppResources) -> anyhow::Result<FileResult> {
|
| 146 |
-
let guard = state.state.read().await;
|
| 147 |
-
if guard.documents.is_empty() {
|
| 148 |
-
anyhow::bail!("No documents to save");
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
let filename = if guard.documents.len() == 1 {
|
| 152 |
-
let stem = guard.documents[0]
|
| 153 |
-
.path
|
| 154 |
-
.file_stem()
|
| 155 |
-
.and_then(|s| s.to_str())
|
| 156 |
-
.unwrap_or("project");
|
| 157 |
-
format!("{}.khr", stem)
|
| 158 |
-
} else {
|
| 159 |
-
"project.khr".to_string()
|
| 160 |
-
};
|
| 161 |
-
|
| 162 |
-
let bytes = serialize_khr(&guard.documents)?;
|
| 163 |
-
drop(guard);
|
| 164 |
-
|
| 165 |
-
Ok(FileResult {
|
| 166 |
-
filename,
|
| 167 |
-
data: bytes,
|
| 168 |
-
content_type: "application/octet-stream".to_string(),
|
| 169 |
-
})
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
pub async fn export_document(
|
| 173 |
state: AppResources,
|
| 174 |
payload: IndexPayload,
|
|
@@ -766,13 +739,6 @@ pub fn load_documents(inputs: Vec<(PathBuf, Vec<u8>)>) -> anyhow::Result<Vec<Doc
|
|
| 766 |
return Ok(vec![]);
|
| 767 |
}
|
| 768 |
|
| 769 |
-
if inputs.len() == 1 {
|
| 770 |
-
let (_, ref bytes) = inputs[0];
|
| 771 |
-
if has_khr_magic(bytes) {
|
| 772 |
-
return deserialize_khr(bytes);
|
| 773 |
-
}
|
| 774 |
-
}
|
| 775 |
-
|
| 776 |
let mut documents: Vec<_> = inputs
|
| 777 |
.into_par_iter()
|
| 778 |
.filter_map(|(path, bytes)| match Document::from_bytes(path, bytes) {
|
|
|
|
| 9 |
use sys_locale::get_locale;
|
| 10 |
use tracing::instrument;
|
| 11 |
|
| 12 |
+
use koharu_types::{Document, SerializableDynamicImage, TextBlock};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
use koharu_ml::llm::facade as llm;
|
| 15 |
+
|
| 16 |
+
use crate::AppResources;
|
| 17 |
+
|
| 18 |
+
pub async fn app_version(state: AppResources) -> anyhow::Result<String> {
|
| 19 |
+
Ok(state.version.to_string())
|
| 20 |
}
|
| 21 |
|
| 22 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 39 |
pub url: String,
|
| 40 |
}
|
| 41 |
|
| 42 |
+
pub async fn open_external(
|
| 43 |
+
_state: AppResources,
|
| 44 |
+
payload: OpenExternalPayload,
|
| 45 |
+
) -> anyhow::Result<()> {
|
| 46 |
open::that(&payload.url)?;
|
| 47 |
Ok(())
|
| 48 |
}
|
|
|
|
| 142 |
pub content_type: String,
|
| 143 |
}
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
pub async fn export_document(
|
| 146 |
state: AppResources,
|
| 147 |
payload: IndexPayload,
|
|
|
|
| 739 |
return Ok(vec![]);
|
| 740 |
}
|
| 741 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
let mut documents: Vec<_> = inputs
|
| 743 |
.into_par_iter()
|
| 744 |
.filter_map(|(path, bytes)| match Document::from_bytes(path, bytes) {
|
{koharu → koharu-pipeline}/src/pipeline.rs
RENAMED
|
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
|
| 11 |
use std::str::FromStr;
|
| 12 |
use tokio::sync::broadcast;
|
| 13 |
|
| 14 |
-
use crate::
|
| 15 |
|
| 16 |
/// Steps in the processing pipeline.
|
| 17 |
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
|
|
| 11 |
use std::str::FromStr;
|
| 12 |
use tokio::sync::broadcast;
|
| 13 |
|
| 14 |
+
use crate::AppResources;
|
| 15 |
|
| 16 |
/// Steps in the processing pipeline.
|
| 17 |
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
koharu-renderer/Cargo.toml
CHANGED
|
@@ -12,6 +12,7 @@ keywords.workspace = true
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
|
|
|
| 15 |
anyhow = { workspace = true }
|
| 16 |
image = { workspace = true }
|
| 17 |
once_cell = { workspace = true }
|
|
@@ -26,6 +27,7 @@ wgpu = { workspace = true }
|
|
| 26 |
etagere = { workspace = true }
|
| 27 |
bytemuck = { workspace = true }
|
| 28 |
tracing = { workspace = true }
|
|
|
|
| 29 |
|
| 30 |
[dev-dependencies]
|
| 31 |
tokio = { workspace = true }
|
|
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
| 15 |
+
koharu-types = { workspace = true }
|
| 16 |
anyhow = { workspace = true }
|
| 17 |
image = { workspace = true }
|
| 18 |
once_cell = { workspace = true }
|
|
|
|
| 27 |
etagere = { workspace = true }
|
| 28 |
bytemuck = { workspace = true }
|
| 29 |
tracing = { workspace = true }
|
| 30 |
+
rayon = { workspace = true }
|
| 31 |
|
| 32 |
[dev-dependencies]
|
| 33 |
tokio = { workspace = true }
|
koharu/src/renderer.rs → koharu-renderer/src/facade.rs
RENAMED
|
@@ -3,16 +3,14 @@ use std::sync::{Arc, Mutex};
|
|
| 3 |
use anyhow::Result;
|
| 4 |
use icu::properties::{CodePointMapData, props::Script};
|
| 5 |
use image::{DynamicImage, imageops};
|
| 6 |
-
use koharu_renderer::{
|
| 7 |
-
font::{FamilyName, Font, FontBook, Properties},
|
| 8 |
-
layout::{LayoutRun, TextLayout, WritingMode},
|
| 9 |
-
renderer::{RenderOptions, TextShaderEffect, WgpuDeviceInfo, WgpuRenderer},
|
| 10 |
-
};
|
| 11 |
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
| 12 |
|
|
|
|
|
|
|
| 13 |
use crate::{
|
| 14 |
-
|
| 15 |
-
|
|
|
|
| 16 |
};
|
| 17 |
|
| 18 |
pub struct Renderer {
|
|
|
|
| 3 |
use anyhow::Result;
|
| 4 |
use icu::properties::{CodePointMapData, props::Script};
|
| 5 |
use image::{DynamicImage, imageops};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
| 7 |
|
| 8 |
+
use koharu_types::{Document, SerializableDynamicImage, TextBlock, TextShaderEffect, TextStyle};
|
| 9 |
+
|
| 10 |
use crate::{
|
| 11 |
+
font::{FamilyName, Font, FontBook, Properties},
|
| 12 |
+
layout::{LayoutRun, TextLayout, WritingMode},
|
| 13 |
+
renderer::{RenderOptions, WgpuDeviceInfo, WgpuRenderer},
|
| 14 |
};
|
| 15 |
|
| 16 |
pub struct Renderer {
|
koharu-renderer/src/lib.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
pub mod font;
|
| 2 |
pub mod layout;
|
| 3 |
pub mod renderer;
|
|
|
|
| 1 |
+
pub mod facade;
|
| 2 |
pub mod font;
|
| 3 |
pub mod layout;
|
| 4 |
pub mod renderer;
|
koharu-renderer/src/renderer.rs
CHANGED
|
@@ -11,29 +11,7 @@ use wgpu::util::DeviceExt;
|
|
| 11 |
use crate::font::Font;
|
| 12 |
use crate::layout::{LayoutRun, PositionedGlyph, WritingMode};
|
| 13 |
|
| 14 |
-
|
| 15 |
-
#[serde(rename_all = "camelCase")]
|
| 16 |
-
#[derive(Default)]
|
| 17 |
-
pub enum TextShaderEffect {
|
| 18 |
-
#[default]
|
| 19 |
-
Normal,
|
| 20 |
-
Antique,
|
| 21 |
-
Metal,
|
| 22 |
-
Manga,
|
| 23 |
-
MotionBlur,
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
impl TextShaderEffect {
|
| 27 |
-
fn id(self) -> f32 {
|
| 28 |
-
match self {
|
| 29 |
-
Self::Normal => 0.0,
|
| 30 |
-
Self::Antique => 1.0,
|
| 31 |
-
Self::Metal => 2.0,
|
| 32 |
-
Self::Manga => 3.0,
|
| 33 |
-
Self::MotionBlur => 4.0,
|
| 34 |
-
}
|
| 35 |
-
}
|
| 36 |
-
}
|
| 37 |
|
| 38 |
/// Options for rendering text.
|
| 39 |
#[derive(Debug, Clone)]
|
|
|
|
| 11 |
use crate::font::Font;
|
| 12 |
use crate::layout::{LayoutRun, PositionedGlyph, WritingMode};
|
| 13 |
|
| 14 |
+
pub use koharu_types::TextShaderEffect;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
/// Options for rendering text.
|
| 17 |
#[derive(Debug, Clone)]
|
koharu-rpc/Cargo.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "koharu-rpc"
|
| 3 |
+
version.workspace = true
|
| 4 |
+
edition.workspace = true
|
| 5 |
+
description.workspace = true
|
| 6 |
+
authors.workspace = true
|
| 7 |
+
publish.workspace = true
|
| 8 |
+
|
| 9 |
+
[dependencies]
|
| 10 |
+
koharu-http = { workspace = true }
|
| 11 |
+
koharu-pipeline = { workspace = true }
|
| 12 |
+
anyhow = { workspace = true }
|
| 13 |
+
axum = { workspace = true }
|
| 14 |
+
futures = { workspace = true }
|
| 15 |
+
rmp-serde = { workspace = true }
|
| 16 |
+
rmpv = { workspace = true }
|
| 17 |
+
serde = { workspace = true }
|
| 18 |
+
tokio = { workspace = true }
|
| 19 |
+
tracing = { workspace = true }
|
koharu-rpc/src/lib.rs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pub mod rpc;
|
| 2 |
+
pub mod server;
|
| 3 |
+
|
| 4 |
+
pub use rpc::{SharedResources, WsState};
|
koharu-rpc/src/rpc.rs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::future::Future;
|
| 2 |
+
use std::sync::Arc;
|
| 3 |
+
use std::time::Duration;
|
| 4 |
+
|
| 5 |
+
use anyhow::Result;
|
| 6 |
+
use axum::{
|
| 7 |
+
extract::{
|
| 8 |
+
State,
|
| 9 |
+
ws::{Message, WebSocket, WebSocketUpgrade},
|
| 10 |
+
},
|
| 11 |
+
response::IntoResponse,
|
| 12 |
+
};
|
| 13 |
+
use futures::{SinkExt, StreamExt};
|
| 14 |
+
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
| 15 |
+
use tokio::sync::{broadcast, mpsc};
|
| 16 |
+
|
| 17 |
+
use koharu_pipeline::AppResources;
|
| 18 |
+
use koharu_pipeline::operations;
|
| 19 |
+
|
| 20 |
+
pub type SharedResources = Arc<tokio::sync::OnceCell<AppResources>>;
|
| 21 |
+
|
| 22 |
+
#[derive(Debug, Deserialize)]
|
| 23 |
+
struct RawIncoming {
|
| 24 |
+
id: u32,
|
| 25 |
+
method: String,
|
| 26 |
+
params: Option<rmpv::Value>,
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
#[derive(Debug, Serialize)]
|
| 30 |
+
#[serde(tag = "type")]
|
| 31 |
+
pub enum OutgoingMessage {
|
| 32 |
+
#[serde(rename = "res")]
|
| 33 |
+
Response {
|
| 34 |
+
id: u32,
|
| 35 |
+
#[serde(skip_serializing_if = "Option::is_none")]
|
| 36 |
+
result: Option<rmpv::Value>,
|
| 37 |
+
#[serde(skip_serializing_if = "Option::is_none")]
|
| 38 |
+
error: Option<String>,
|
| 39 |
+
},
|
| 40 |
+
#[serde(rename = "ntf")]
|
| 41 |
+
Notification { method: String, params: rmpv::Value },
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
fn ok_response(id: u32, result: rmpv::Value) -> OutgoingMessage {
|
| 45 |
+
OutgoingMessage::Response {
|
| 46 |
+
id,
|
| 47 |
+
result: Some(result),
|
| 48 |
+
error: None,
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
fn err_response(id: u32, msg: &str) -> OutgoingMessage {
|
| 53 |
+
OutgoingMessage::Response {
|
| 54 |
+
id,
|
| 55 |
+
result: None,
|
| 56 |
+
error: Some(msg.to_string()),
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
fn to_value<T: Serialize>(val: &T) -> Result<rmpv::Value> {
|
| 61 |
+
let bytes = rmp_serde::to_vec_named(val)?;
|
| 62 |
+
Ok(rmp_serde::from_slice(&bytes)?)
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
fn from_value<T: DeserializeOwned>(val: rmpv::Value) -> Result<T> {
|
| 66 |
+
let bytes = rmp_serde::to_vec_named(&val)?;
|
| 67 |
+
Ok(rmp_serde::from_slice(&bytes)?)
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
async fn call<F, Fut, P, T>(f: F, state: AppResources, params: rmpv::Value) -> Result<rmpv::Value>
|
| 71 |
+
where
|
| 72 |
+
F: FnOnce(AppResources, P) -> Fut,
|
| 73 |
+
Fut: Future<Output = Result<T>>,
|
| 74 |
+
P: DeserializeOwned,
|
| 75 |
+
T: Serialize,
|
| 76 |
+
{
|
| 77 |
+
to_value(&f(state, from_value(params)?).await?)
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
async fn call0<F, Fut, T>(f: F, state: AppResources) -> Result<rmpv::Value>
|
| 81 |
+
where
|
| 82 |
+
F: FnOnce(AppResources) -> Fut,
|
| 83 |
+
Fut: Future<Output = Result<T>>,
|
| 84 |
+
T: Serialize,
|
| 85 |
+
{
|
| 86 |
+
to_value(&f(state).await?)
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
async fn dispatch(method: &str, params: rmpv::Value, state: AppResources) -> Result<rmpv::Value> {
|
| 90 |
+
match method {
|
| 91 |
+
"app_version" => call0(operations::app_version, state).await,
|
| 92 |
+
"device" => call0(operations::device, state).await,
|
| 93 |
+
"get_documents" => call0(operations::get_documents, state).await,
|
| 94 |
+
"list_font_families" => call0(operations::list_font_families, state).await,
|
| 95 |
+
"llm_list" => call0(operations::llm_list, state).await,
|
| 96 |
+
"llm_ready" => call0(operations::llm_ready, state).await,
|
| 97 |
+
"llm_offload" => call0(operations::llm_offload, state).await,
|
| 98 |
+
"process_cancel" => call0(operations::process_cancel, state).await,
|
| 99 |
+
"get_document" => call(operations::get_document, state, params).await,
|
| 100 |
+
"get_thumbnail" => call(operations::get_thumbnail, state, params).await,
|
| 101 |
+
"export_document" => call(operations::export_document, state, params).await,
|
| 102 |
+
"open_documents" => call(operations::open_documents, state, params).await,
|
| 103 |
+
"open_external" => call(operations::open_external, state, params).await,
|
| 104 |
+
"detect" => call(operations::detect, state, params).await,
|
| 105 |
+
"ocr" => call(operations::ocr, state, params).await,
|
| 106 |
+
"inpaint" => call(operations::inpaint, state, params).await,
|
| 107 |
+
"update_inpaint_mask" => call(operations::update_inpaint_mask, state, params).await,
|
| 108 |
+
"update_brush_layer" => call(operations::update_brush_layer, state, params).await,
|
| 109 |
+
"inpaint_partial" => call(operations::inpaint_partial, state, params).await,
|
| 110 |
+
"render" => call(operations::render, state, params).await,
|
| 111 |
+
"update_text_blocks" => call(operations::update_text_blocks, state, params).await,
|
| 112 |
+
"llm_load" => call(operations::llm_load, state, params).await,
|
| 113 |
+
"llm_generate" => call(operations::llm_generate, state, params).await,
|
| 114 |
+
"process" => call(operations::process, state, params).await,
|
| 115 |
+
_ => Err(anyhow::anyhow!("Unknown method: {}", method)),
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
#[derive(Clone)]
|
| 120 |
+
pub struct WsState {
|
| 121 |
+
pub resources: SharedResources,
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
pub async fn ws_handler(ws: WebSocketUpgrade, State(state): State<WsState>) -> impl IntoResponse {
|
| 125 |
+
ws.max_message_size(1024 * 1024 * 1024)
|
| 126 |
+
.on_upgrade(|socket| handle_socket(socket, state))
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async fn handle_socket(socket: WebSocket, state: WsState) {
|
| 130 |
+
let (mut ws_sender, mut ws_receiver) = socket.split();
|
| 131 |
+
let (tx, mut send_rx) = mpsc::channel::<OutgoingMessage>(256);
|
| 132 |
+
|
| 133 |
+
// Notification forwarders
|
| 134 |
+
spawn_notification_forwarder(
|
| 135 |
+
"download_progress",
|
| 136 |
+
koharu_http::download::subscribe(),
|
| 137 |
+
tx.clone(),
|
| 138 |
+
);
|
| 139 |
+
spawn_notification_forwarder(
|
| 140 |
+
"process_progress",
|
| 141 |
+
koharu_pipeline::pipeline::subscribe(),
|
| 142 |
+
tx.clone(),
|
| 143 |
+
);
|
| 144 |
+
|
| 145 |
+
// Sender task: drain mpsc → WebSocket binary frames
|
| 146 |
+
let send_task = tokio::spawn(async move {
|
| 147 |
+
while let Some(msg) = send_rx.recv().await {
|
| 148 |
+
let Ok(bytes) = rmp_serde::to_vec_named(&msg) else {
|
| 149 |
+
continue;
|
| 150 |
+
};
|
| 151 |
+
if ws_sender.send(Message::Binary(bytes.into())).await.is_err() {
|
| 152 |
+
break;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
// Receive loop
|
| 158 |
+
while let Some(Ok(msg)) = ws_receiver.next().await {
|
| 159 |
+
let data = match msg {
|
| 160 |
+
Message::Binary(data) => data,
|
| 161 |
+
Message::Close(_) => break,
|
| 162 |
+
_ => continue,
|
| 163 |
+
};
|
| 164 |
+
|
| 165 |
+
let raw: RawIncoming = match rmp_serde::from_slice(&data) {
|
| 166 |
+
Ok(r) => r,
|
| 167 |
+
Err(e) => {
|
| 168 |
+
let _ = tx
|
| 169 |
+
.send(err_response(0, &format!("Decode error: {e}")))
|
| 170 |
+
.await;
|
| 171 |
+
continue;
|
| 172 |
+
}
|
| 173 |
+
};
|
| 174 |
+
|
| 175 |
+
let id = raw.id;
|
| 176 |
+
let tx = tx.clone();
|
| 177 |
+
let resources = state.resources.clone();
|
| 178 |
+
|
| 179 |
+
tokio::spawn(async move {
|
| 180 |
+
let response = match resources.get() {
|
| 181 |
+
Some(res) => {
|
| 182 |
+
let params = raw.params.unwrap_or(rmpv::Value::Nil);
|
| 183 |
+
match tokio::time::timeout(
|
| 184 |
+
Duration::from_secs(300),
|
| 185 |
+
dispatch(&raw.method, params, res.clone()),
|
| 186 |
+
)
|
| 187 |
+
.await
|
| 188 |
+
{
|
| 189 |
+
Ok(Ok(result)) => ok_response(id, result),
|
| 190 |
+
Ok(Err(e)) => err_response(id, &format!("{e:#}")),
|
| 191 |
+
Err(_) => err_response(id, "Request timed out"),
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
None => err_response(id, "Resources not initialized"),
|
| 195 |
+
};
|
| 196 |
+
let _ = tx.send(response).await;
|
| 197 |
+
});
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
send_task.abort();
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
fn spawn_notification_forwarder<T: Serialize + Clone + Send + 'static>(
|
| 204 |
+
method: &'static str,
|
| 205 |
+
mut rx: broadcast::Receiver<T>,
|
| 206 |
+
tx: mpsc::Sender<OutgoingMessage>,
|
| 207 |
+
) {
|
| 208 |
+
tokio::spawn(async move {
|
| 209 |
+
loop {
|
| 210 |
+
match rx.recv().await {
|
| 211 |
+
Ok(payload) => {
|
| 212 |
+
let params = to_value(&payload).unwrap_or(rmpv::Value::Nil);
|
| 213 |
+
let msg = OutgoingMessage::Notification {
|
| 214 |
+
method: method.to_string(),
|
| 215 |
+
params,
|
| 216 |
+
};
|
| 217 |
+
if tx.send(msg).await.is_err() {
|
| 218 |
+
break;
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
| 222 |
+
Err(broadcast::error::RecvError::Closed) => break,
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
});
|
| 226 |
+
}
|
{koharu → koharu-rpc}/src/server.rs
RENAMED
|
@@ -8,12 +8,20 @@ use axum::{
|
|
| 8 |
response::{IntoResponse, Response},
|
| 9 |
routing::get,
|
| 10 |
};
|
| 11 |
-
use tauri::AssetResolver;
|
| 12 |
use tokio::net::TcpListener;
|
| 13 |
|
| 14 |
use crate::rpc::{self, SharedResources, WsState};
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
let ws_state = WsState { resources: shared };
|
| 18 |
|
| 19 |
Router::new()
|
|
@@ -25,7 +33,7 @@ fn build_router(shared: SharedResources, resolver: Arc<AssetResolver<tauri::Wry>
|
|
| 25 |
})
|
| 26 |
}
|
| 27 |
|
| 28 |
-
fn serve_asset(resolver: &
|
| 29 |
let path = uri.path();
|
| 30 |
let target = if path == "/" {
|
| 31 |
"index.html"
|
|
@@ -38,8 +46,8 @@ fn serve_asset(resolver: &AssetResolver<tauri::Wry>, uri: Uri) -> Response {
|
|
| 38 |
.unwrap_or_else(|| (StatusCode::NOT_FOUND, "Not Found").into_response())
|
| 39 |
}
|
| 40 |
|
| 41 |
-
fn resolve_asset(resolver: &
|
| 42 |
-
let asset = resolver
|
| 43 |
let mut response = Response::new(Body::from(asset.bytes));
|
| 44 |
if let Ok(ct) = HeaderValue::from_str(&asset.mime_type) {
|
| 45 |
response.headers_mut().insert(header::CONTENT_TYPE, ct);
|
|
@@ -50,7 +58,7 @@ fn resolve_asset(resolver: &AssetResolver<tauri::Wry>, path: &str) -> Option<Res
|
|
| 50 |
pub async fn serve_with_listener(
|
| 51 |
listener: TcpListener,
|
| 52 |
shared: SharedResources,
|
| 53 |
-
resolver:
|
| 54 |
) -> Result<()> {
|
| 55 |
let router = build_router(shared, resolver);
|
| 56 |
tracing::info!("HTTP server listening on http://{}", listener.local_addr()?);
|
|
|
|
| 8 |
response::{IntoResponse, Response},
|
| 9 |
routing::get,
|
| 10 |
};
|
|
|
|
| 11 |
use tokio::net::TcpListener;
|
| 12 |
|
| 13 |
use crate::rpc::{self, SharedResources, WsState};
|
| 14 |
|
| 15 |
+
/// An asset returned by the resolver: raw bytes + MIME type.
|
| 16 |
+
pub struct Asset {
|
| 17 |
+
pub bytes: Vec<u8>,
|
| 18 |
+
pub mime_type: String,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/// A function that resolves a path to an asset.
|
| 22 |
+
pub type SharedAssetResolver = Arc<dyn Fn(&str) -> Option<Asset> + Send + Sync>;
|
| 23 |
+
|
| 24 |
+
fn build_router(shared: SharedResources, resolver: SharedAssetResolver) -> Router {
|
| 25 |
let ws_state = WsState { resources: shared };
|
| 26 |
|
| 27 |
Router::new()
|
|
|
|
| 33 |
})
|
| 34 |
}
|
| 35 |
|
| 36 |
+
fn serve_asset(resolver: &SharedAssetResolver, uri: Uri) -> Response {
|
| 37 |
let path = uri.path();
|
| 38 |
let target = if path == "/" {
|
| 39 |
"index.html"
|
|
|
|
| 46 |
.unwrap_or_else(|| (StatusCode::NOT_FOUND, "Not Found").into_response())
|
| 47 |
}
|
| 48 |
|
| 49 |
+
fn resolve_asset(resolver: &SharedAssetResolver, path: &str) -> Option<Response> {
|
| 50 |
+
let asset = resolver(path)?;
|
| 51 |
let mut response = Response::new(Body::from(asset.bytes));
|
| 52 |
if let Ok(ct) = HeaderValue::from_str(&asset.mime_type) {
|
| 53 |
response.headers_mut().insert(header::CONTENT_TYPE, ct);
|
|
|
|
| 58 |
pub async fn serve_with_listener(
|
| 59 |
listener: TcpListener,
|
| 60 |
shared: SharedResources,
|
| 61 |
+
resolver: SharedAssetResolver,
|
| 62 |
) -> Result<()> {
|
| 63 |
let router = build_router(shared, resolver);
|
| 64 |
tracing::info!("HTTP server listening on http://{}", listener.local_addr()?);
|
koharu-runtime/Cargo.toml
CHANGED
|
@@ -12,7 +12,7 @@ keywords.workspace = true
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
| 15 |
-
koharu-
|
| 16 |
anyhow = { workspace = true }
|
| 17 |
serde_json = { workspace = true }
|
| 18 |
zip = { workspace = true }
|
|
|
|
| 12 |
publish.workspace = true
|
| 13 |
|
| 14 |
[dependencies]
|
| 15 |
+
koharu-http = { workspace = true }
|
| 16 |
anyhow = { workspace = true }
|
| 17 |
serde_json = { workspace = true }
|
| 18 |
zip = { workspace = true }
|
koharu-runtime/src/dylib.rs
CHANGED
|
@@ -7,8 +7,8 @@ use std::{
|
|
| 7 |
|
| 8 |
use anyhow::{Context, Result};
|
| 9 |
use futures::stream::{self, StreamExt, TryStreamExt};
|
| 10 |
-
use
|
| 11 |
-
use
|
| 12 |
use once_cell::sync::OnceCell;
|
| 13 |
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
| 14 |
use tokio::task;
|
|
|
|
| 7 |
|
| 8 |
use anyhow::{Context, Result};
|
| 9 |
use futures::stream::{self, StreamExt, TryStreamExt};
|
| 10 |
+
use koharu_http::download;
|
| 11 |
+
use koharu_http::http::http_client;
|
| 12 |
use once_cell::sync::OnceCell;
|
| 13 |
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
| 14 |
use tokio::task;
|
koharu-runtime/src/zip.rs
CHANGED
|
@@ -2,7 +2,7 @@ use std::io;
|
|
| 2 |
|
| 3 |
use anyhow::Result;
|
| 4 |
use flate2::read::DeflateDecoder;
|
| 5 |
-
use
|
| 6 |
|
| 7 |
// ZIP signatures and constants
|
| 8 |
const SIG_EOCD: [u8; 4] = [0x50, 0x4b, 0x05, 0x06]; // End Of Central Directory
|
|
|
|
| 2 |
|
| 3 |
use anyhow::Result;
|
| 4 |
use flate2::read::DeflateDecoder;
|
| 5 |
+
use koharu_http::http::http_client;
|
| 6 |
|
| 7 |
// ZIP signatures and constants
|
| 8 |
const SIG_EOCD: [u8; 4] = [0x50, 0x4b, 0x05, 0x06]; // End Of Central Directory
|
koharu-types/Cargo.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "koharu-types"
|
| 3 |
+
version.workspace = true
|
| 4 |
+
edition.workspace = true
|
| 5 |
+
description.workspace = true
|
| 6 |
+
license.workspace = true
|
| 7 |
+
authors.workspace = true
|
| 8 |
+
readme.workspace = true
|
| 9 |
+
homepage.workspace = true
|
| 10 |
+
repository.workspace = true
|
| 11 |
+
keywords.workspace = true
|
| 12 |
+
publish.workspace = true
|
| 13 |
+
|
| 14 |
+
[dependencies]
|
| 15 |
+
anyhow = { workspace = true }
|
| 16 |
+
blake3 = { workspace = true }
|
| 17 |
+
image = { workspace = true }
|
| 18 |
+
serde = { workspace = true }
|
| 19 |
+
serde_bytes = { workspace = true }
|
| 20 |
+
sys-locale = { workspace = true }
|
| 21 |
+
tokio = { workspace = true }
|
koharu-types/src/effect.rs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use serde::{Deserialize, Serialize};
|
| 2 |
+
|
| 3 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
| 4 |
+
#[serde(rename_all = "camelCase")]
|
| 5 |
+
#[derive(Default)]
|
| 6 |
+
pub enum TextShaderEffect {
|
| 7 |
+
#[default]
|
| 8 |
+
Normal,
|
| 9 |
+
Antique,
|
| 10 |
+
Metal,
|
| 11 |
+
Manga,
|
| 12 |
+
MotionBlur,
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
impl TextShaderEffect {
|
| 16 |
+
pub fn id(self) -> f32 {
|
| 17 |
+
match self {
|
| 18 |
+
Self::Normal => 0.0,
|
| 19 |
+
Self::Antique => 1.0,
|
| 20 |
+
Self::Metal => 2.0,
|
| 21 |
+
Self::Manga => 3.0,
|
| 22 |
+
Self::MotionBlur => 4.0,
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
koharu-types/src/font.rs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use serde::{Deserialize, Serialize};
|
| 2 |
+
|
| 3 |
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
| 4 |
+
pub enum TextDirection {
|
| 5 |
+
Horizontal,
|
| 6 |
+
Vertical,
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 10 |
+
pub struct NamedFontPrediction {
|
| 11 |
+
pub index: usize,
|
| 12 |
+
pub name: String,
|
| 13 |
+
pub language: Option<String>,
|
| 14 |
+
pub probability: f32,
|
| 15 |
+
pub serif: bool,
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 19 |
+
pub struct FontPrediction {
|
| 20 |
+
pub top_fonts: Vec<(usize, f32)>,
|
| 21 |
+
pub named_fonts: Vec<NamedFontPrediction>,
|
| 22 |
+
pub direction: TextDirection,
|
| 23 |
+
pub text_color: [u8; 3],
|
| 24 |
+
pub stroke_color: [u8; 3],
|
| 25 |
+
pub font_size_px: f32,
|
| 26 |
+
pub stroke_width_px: f32,
|
| 27 |
+
pub line_height: f32,
|
| 28 |
+
pub angle_deg: f32,
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
impl Default for FontPrediction {
|
| 32 |
+
fn default() -> Self {
|
| 33 |
+
Self {
|
| 34 |
+
top_fonts: Vec::new(),
|
| 35 |
+
named_fonts: Vec::new(),
|
| 36 |
+
direction: TextDirection::Horizontal,
|
| 37 |
+
text_color: [0, 0, 0],
|
| 38 |
+
stroke_color: [0, 0, 0],
|
| 39 |
+
font_size_px: 0.0,
|
| 40 |
+
stroke_width_px: 0.0,
|
| 41 |
+
line_height: 1.0,
|
| 42 |
+
angle_deg: 0.0,
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
{koharu → koharu-types}/src/image.rs
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
use std::ops::Deref;
|
| 2 |
|
| 3 |
-
use image::{ColorType, DynamicImage, codecs::webp::WebPEncoder};
|
| 4 |
use serde::{Deserialize, Serialize, Serializer};
|
| 5 |
|
| 6 |
#[derive(Debug, Default, Clone)]
|
|
@@ -30,7 +30,7 @@ impl<'de> Deserialize<'de> for SerializableDynamicImage {
|
|
| 30 |
D: serde::Deserializer<'de>,
|
| 31 |
{
|
| 32 |
let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
|
| 33 |
-
let img = image::load_from_memory(&bytes).map_err(serde::de::Error::custom)?;
|
| 34 |
Ok(SerializableDynamicImage(img))
|
| 35 |
}
|
| 36 |
}
|
|
|
|
| 1 |
use std::ops::Deref;
|
| 2 |
|
| 3 |
+
use ::image::{ColorType, DynamicImage, codecs::webp::WebPEncoder};
|
| 4 |
use serde::{Deserialize, Serialize, Serializer};
|
| 5 |
|
| 6 |
#[derive(Debug, Default, Clone)]
|
|
|
|
| 30 |
D: serde::Deserializer<'de>,
|
| 31 |
{
|
| 32 |
let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
|
| 33 |
+
let img = ::image::load_from_memory(&bytes).map_err(serde::de::Error::custom)?;
|
| 34 |
Ok(SerializableDynamicImage(img))
|
| 35 |
}
|
| 36 |
}
|
koharu/src/state.rs → koharu-types/src/lib.rs
RENAMED
|
@@ -1,18 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
use std::{collections::HashSet, path::PathBuf, sync::Arc};
|
| 2 |
|
| 3 |
-
use
|
| 4 |
-
use image::GenericImageView;
|
| 5 |
-
use koharu_ml::font_detector::FontPrediction;
|
| 6 |
-
use koharu_renderer::renderer::TextShaderEffect;
|
| 7 |
use serde::{Deserialize, Serialize};
|
| 8 |
use sys_locale::get_locale;
|
| 9 |
use tokio::sync::RwLock;
|
| 10 |
|
| 11 |
-
use crate::{
|
| 12 |
-
image::SerializableDynamicImage,
|
| 13 |
-
khr::{deserialize_khr, has_khr_magic},
|
| 14 |
-
};
|
| 15 |
-
|
| 16 |
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
| 17 |
#[serde(rename_all = "camelCase")]
|
| 18 |
pub struct TextBlock {
|
|
@@ -188,22 +188,16 @@ impl Document {
|
|
| 188 |
documents
|
| 189 |
.into_iter()
|
| 190 |
.next()
|
| 191 |
-
.ok_or_else(|| anyhow!("No document found in file"))
|
| 192 |
}
|
| 193 |
|
| 194 |
pub fn from_bytes(path: impl Into<PathBuf>, bytes: Vec<u8>) -> anyhow::Result<Vec<Self>> {
|
| 195 |
let path = path.into();
|
| 196 |
-
|
| 197 |
-
if has_khr_magic(&bytes) {
|
| 198 |
-
return deserialize_khr(&bytes)
|
| 199 |
-
.map_err(|err| anyhow!("Failed to load documents: {err}"));
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
Ok(vec![Self::image(path, bytes)?])
|
| 203 |
}
|
| 204 |
|
| 205 |
fn image(path: PathBuf, bytes: Vec<u8>) -> anyhow::Result<Self> {
|
| 206 |
-
let img = image::load_from_memory(&bytes)?;
|
| 207 |
let (width, height) = img.dimensions();
|
| 208 |
let id = blake3::hash(&bytes).to_hex().to_string();
|
| 209 |
let name = path
|
|
|
|
| 1 |
+
mod effect;
|
| 2 |
+
mod font;
|
| 3 |
+
mod image;
|
| 4 |
+
|
| 5 |
+
pub use effect::TextShaderEffect;
|
| 6 |
+
pub use font::{FontPrediction, NamedFontPrediction, TextDirection};
|
| 7 |
+
pub use image::SerializableDynamicImage;
|
| 8 |
+
|
| 9 |
use std::{collections::HashSet, path::PathBuf, sync::Arc};
|
| 10 |
|
| 11 |
+
use ::image::GenericImageView;
|
|
|
|
|
|
|
|
|
|
| 12 |
use serde::{Deserialize, Serialize};
|
| 13 |
use sys_locale::get_locale;
|
| 14 |
use tokio::sync::RwLock;
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
| 17 |
#[serde(rename_all = "camelCase")]
|
| 18 |
pub struct TextBlock {
|
|
|
|
| 188 |
documents
|
| 189 |
.into_iter()
|
| 190 |
.next()
|
| 191 |
+
.ok_or_else(|| anyhow::anyhow!("No document found in file"))
|
| 192 |
}
|
| 193 |
|
| 194 |
pub fn from_bytes(path: impl Into<PathBuf>, bytes: Vec<u8>) -> anyhow::Result<Vec<Self>> {
|
| 195 |
let path = path.into();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
Ok(vec![Self::image(path, bytes)?])
|
| 197 |
}
|
| 198 |
|
| 199 |
fn image(path: PathBuf, bytes: Vec<u8>) -> anyhow::Result<Self> {
|
| 200 |
+
let img = ::image::load_from_memory(&bytes)?;
|
| 201 |
let (width, height) = img.dimensions();
|
| 202 |
let id = blake3::hash(&bytes).to_hex().to_string();
|
| 203 |
let name = path
|
koharu/Cargo.toml
CHANGED
|
@@ -22,40 +22,24 @@ include = [
|
|
| 22 |
]
|
| 23 |
|
| 24 |
[dependencies]
|
| 25 |
-
koharu-core = { workspace = true }
|
| 26 |
koharu-ml = { workspace = true }
|
|
|
|
| 27 |
koharu-renderer = { workspace = true }
|
|
|
|
| 28 |
koharu-runtime = { workspace = true }
|
|
|
|
| 29 |
anyhow = { workspace = true }
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
rfd = { workspace = true }
|
| 32 |
-
open = { workspace = true }
|
| 33 |
-
serde = { workspace = true }
|
| 34 |
-
serde_bytes = { workspace = true }
|
| 35 |
-
image = { workspace = true }
|
| 36 |
-
tracing = { workspace = true }
|
| 37 |
-
tracing-subscriber = { workspace = true }
|
| 38 |
-
postcard = { workspace = true }
|
| 39 |
-
thiserror = { workspace = true }
|
| 40 |
tauri = { workspace = true }
|
| 41 |
tauri-plugin-updater = { workspace = true }
|
| 42 |
-
blake3 = { workspace = true }
|
| 43 |
tokio = { workspace = true }
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
dirs = { workspace = true }
|
| 47 |
-
clap = { workspace = true }
|
| 48 |
-
once_cell = { workspace = true }
|
| 49 |
-
sys-locale = { workspace = true }
|
| 50 |
-
icu = { workspace = true }
|
| 51 |
-
axum = { workspace = true }
|
| 52 |
-
tower = { workspace = true }
|
| 53 |
-
rmp-serde = { workspace = true }
|
| 54 |
-
rmpv = { workspace = true }
|
| 55 |
-
enum_dispatch = { workspace = true }
|
| 56 |
-
async-trait = { workspace = true }
|
| 57 |
-
git-version = { workspace = true }
|
| 58 |
-
serde_json = { workspace = true }
|
| 59 |
|
| 60 |
[target.'cfg(target_os = "windows")'.dependencies]
|
| 61 |
windows = { workspace = true }
|
|
|
|
| 22 |
]
|
| 23 |
|
| 24 |
[dependencies]
|
|
|
|
| 25 |
koharu-ml = { workspace = true }
|
| 26 |
+
koharu-pipeline = { workspace = true }
|
| 27 |
koharu-renderer = { workspace = true }
|
| 28 |
+
koharu-rpc = { workspace = true }
|
| 29 |
koharu-runtime = { workspace = true }
|
| 30 |
+
koharu-types = { workspace = true }
|
| 31 |
anyhow = { workspace = true }
|
| 32 |
+
clap = { workspace = true }
|
| 33 |
+
dirs = { workspace = true }
|
| 34 |
+
git-version = { workspace = true }
|
| 35 |
+
serde_json = { workspace = true }
|
| 36 |
+
once_cell = { workspace = true }
|
| 37 |
rfd = { workspace = true }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
tauri = { workspace = true }
|
| 39 |
tauri-plugin-updater = { workspace = true }
|
|
|
|
| 40 |
tokio = { workspace = true }
|
| 41 |
+
tracing = { workspace = true }
|
| 42 |
+
tracing-subscriber = { workspace = true }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
[target.'cfg(target_os = "windows")'.dependencies]
|
| 45 |
windows = { workspace = true }
|
koharu/src/app.rs
CHANGED
|
@@ -2,21 +2,18 @@ use std::{path::PathBuf, sync::Arc};
|
|
| 2 |
|
| 3 |
use anyhow::{Context, Result};
|
| 4 |
use clap::Parser;
|
| 5 |
-
use koharu_ml::{DeviceName, cuda_is_available, device_name};
|
| 6 |
-
use koharu_runtime::{ensure_dylibs, preload_dylibs};
|
| 7 |
use once_cell::sync::Lazy;
|
| 8 |
use rfd::MessageDialog;
|
| 9 |
use tauri::Manager;
|
| 10 |
use tokio::{net::TcpListener, sync::RwLock};
|
| 11 |
use tracing_subscriber::fmt::format::FmtSpan;
|
| 12 |
|
| 13 |
-
use
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
};
|
| 20 |
|
| 21 |
static APP_ROOT: Lazy<PathBuf> = Lazy::new(|| {
|
| 22 |
dirs::data_local_dir()
|
|
@@ -26,16 +23,6 @@ static APP_ROOT: Lazy<PathBuf> = Lazy::new(|| {
|
|
| 26 |
static LIB_ROOT: Lazy<PathBuf> = Lazy::new(|| APP_ROOT.join("libs"));
|
| 27 |
static MODEL_ROOT: Lazy<PathBuf> = Lazy::new(|| APP_ROOT.join("models"));
|
| 28 |
|
| 29 |
-
#[derive(Clone)]
|
| 30 |
-
pub struct AppResources {
|
| 31 |
-
pub state: AppState,
|
| 32 |
-
pub ml: Arc<ml::Model>,
|
| 33 |
-
pub llm: Arc<llm::Model>,
|
| 34 |
-
pub renderer: Arc<Renderer>,
|
| 35 |
-
pub ml_device: DeviceName,
|
| 36 |
-
pub pipeline: Arc<RwLock<Option<crate::pipeline::PipelineHandle>>>,
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
#[derive(Parser)]
|
| 40 |
#[command(version = crate::version::APP_VERSION, about)]
|
| 41 |
struct Cli {
|
|
@@ -117,7 +104,7 @@ fn initialize(headless: bool, _debug: bool) -> Result<()> {
|
|
| 117 |
|
| 118 |
async fn prefetch() -> Result<()> {
|
| 119 |
ensure_dylibs(LIB_ROOT.to_path_buf()).await?;
|
| 120 |
-
|
| 121 |
|
| 122 |
Ok(())
|
| 123 |
}
|
|
@@ -146,11 +133,11 @@ async fn build_resources(cpu: bool) -> Result<AppResources> {
|
|
| 146 |
|
| 147 |
let ml_device = device_name(cpu);
|
| 148 |
let ml = Arc::new(
|
| 149 |
-
|
| 150 |
.await
|
| 151 |
.context("Failed to initialize ML model")?,
|
| 152 |
);
|
| 153 |
-
let llm = Arc::new(llm::Model::new(cpu));
|
| 154 |
let renderer = Arc::new(Renderer::new().context("Failed to initialize renderer")?);
|
| 155 |
let state = Arc::new(RwLock::new(State::default()));
|
| 156 |
|
|
@@ -161,6 +148,7 @@ async fn build_resources(cpu: bool) -> Result<AppResources> {
|
|
| 161 |
renderer,
|
| 162 |
ml_device,
|
| 163 |
pipeline: Arc::new(RwLock::new(None)),
|
|
|
|
| 164 |
})
|
| 165 |
}
|
| 166 |
|
|
@@ -188,7 +176,14 @@ pub async fn run() -> Result<()> {
|
|
| 188 |
.append_invoke_initialization_script(format!("window.__KOHARU_WS_PORT__ = {};", ws_port))
|
| 189 |
.build(tauri::generate_context!())?;
|
| 190 |
|
| 191 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
tokio::spawn({
|
| 193 |
let shared = shared.clone();
|
| 194 |
async move {
|
|
|
|
| 2 |
|
| 3 |
use anyhow::{Context, Result};
|
| 4 |
use clap::Parser;
|
|
|
|
|
|
|
| 5 |
use once_cell::sync::Lazy;
|
| 6 |
use rfd::MessageDialog;
|
| 7 |
use tauri::Manager;
|
| 8 |
use tokio::{net::TcpListener, sync::RwLock};
|
| 9 |
use tracing_subscriber::fmt::format::FmtSpan;
|
| 10 |
|
| 11 |
+
use koharu_ml::{cuda_is_available, device_name};
|
| 12 |
+
use koharu_pipeline::AppResources;
|
| 13 |
+
use koharu_renderer::facade::Renderer;
|
| 14 |
+
use koharu_rpc::{SharedResources, server};
|
| 15 |
+
use koharu_runtime::{ensure_dylibs, preload_dylibs};
|
| 16 |
+
use koharu_types::State;
|
|
|
|
| 17 |
|
| 18 |
static APP_ROOT: Lazy<PathBuf> = Lazy::new(|| {
|
| 19 |
dirs::data_local_dir()
|
|
|
|
| 23 |
static LIB_ROOT: Lazy<PathBuf> = Lazy::new(|| APP_ROOT.join("libs"));
|
| 24 |
static MODEL_ROOT: Lazy<PathBuf> = Lazy::new(|| APP_ROOT.join("models"));
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
#[derive(Parser)]
|
| 27 |
#[command(version = crate::version::APP_VERSION, about)]
|
| 28 |
struct Cli {
|
|
|
|
| 104 |
|
| 105 |
async fn prefetch() -> Result<()> {
|
| 106 |
ensure_dylibs(LIB_ROOT.to_path_buf()).await?;
|
| 107 |
+
koharu_ml::facade::prefetch().await?;
|
| 108 |
|
| 109 |
Ok(())
|
| 110 |
}
|
|
|
|
| 133 |
|
| 134 |
let ml_device = device_name(cpu);
|
| 135 |
let ml = Arc::new(
|
| 136 |
+
koharu_ml::facade::Model::new(cpu)
|
| 137 |
.await
|
| 138 |
.context("Failed to initialize ML model")?,
|
| 139 |
);
|
| 140 |
+
let llm = Arc::new(koharu_ml::llm::facade::Model::new(cpu));
|
| 141 |
let renderer = Arc::new(Renderer::new().context("Failed to initialize renderer")?);
|
| 142 |
let state = Arc::new(RwLock::new(State::default()));
|
| 143 |
|
|
|
|
| 148 |
renderer,
|
| 149 |
ml_device,
|
| 150 |
pipeline: Arc::new(RwLock::new(None)),
|
| 151 |
+
version: crate::version::current(),
|
| 152 |
})
|
| 153 |
}
|
| 154 |
|
|
|
|
| 176 |
.append_invoke_initialization_script(format!("window.__KOHARU_WS_PORT__ = {};", ws_port))
|
| 177 |
.build(tauri::generate_context!())?;
|
| 178 |
|
| 179 |
+
let tauri_resolver = Arc::new(app.asset_resolver());
|
| 180 |
+
let resolver: server::SharedAssetResolver = Arc::new(move |path: &str| {
|
| 181 |
+
let asset = tauri_resolver.get(path.to_string())?;
|
| 182 |
+
Some(server::Asset {
|
| 183 |
+
bytes: asset.bytes.to_vec(),
|
| 184 |
+
mime_type: asset.mime_type.clone(),
|
| 185 |
+
})
|
| 186 |
+
});
|
| 187 |
tokio::spawn({
|
| 188 |
let shared = shared.clone();
|
| 189 |
async move {
|
koharu/src/khr.rs
DELETED
|
@@ -1,144 +0,0 @@
|
|
| 1 |
-
use std::io::Cursor;
|
| 2 |
-
|
| 3 |
-
use anyhow::bail;
|
| 4 |
-
use image::{DynamicImage, GenericImageView, ImageFormat, RgbaImage, imageops};
|
| 5 |
-
use once_cell::sync::Lazy;
|
| 6 |
-
|
| 7 |
-
use crate::state::Document;
|
| 8 |
-
|
| 9 |
-
pub const KHR_MAGIC: &[u8; 4] = b"khr!";
|
| 10 |
-
const KHR_FOOTER_LEN: usize = KHR_MAGIC.len() + std::mem::size_of::<u64>();
|
| 11 |
-
const THUMBNAIL_HEIGHT: u32 = 300;
|
| 12 |
-
const THUMBNAIL_WIDTH: u32 = THUMBNAIL_HEIGHT * 4 / 3; // 4:3 aspect for contact sheet
|
| 13 |
-
const ICON_BYTES: &[u8] = include_bytes!("../icons/Square142x142Logo.png");
|
| 14 |
-
|
| 15 |
-
static ICON_IMAGE: Lazy<RgbaImage> = Lazy::new(|| {
|
| 16 |
-
image::load_from_memory(ICON_BYTES)
|
| 17 |
-
.expect("failed to decode embedded icon")
|
| 18 |
-
.to_rgba8()
|
| 19 |
-
});
|
| 20 |
-
|
| 21 |
-
pub fn has_khr_magic(bytes: &[u8]) -> bool {
|
| 22 |
-
bytes.len() >= KHR_MAGIC.len() && &bytes[bytes.len() - KHR_MAGIC.len()..] == KHR_MAGIC
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
pub fn serialize_khr(documents: &[Document]) -> anyhow::Result<Vec<u8>> {
|
| 26 |
-
let thumbnail = thumbnail_contact_sheet(documents);
|
| 27 |
-
let mut thumbnail_bytes = Vec::new();
|
| 28 |
-
thumbnail.write_to(&mut Cursor::new(&mut thumbnail_bytes), ImageFormat::Jpeg)?;
|
| 29 |
-
|
| 30 |
-
let khr_bytes = postcard::to_allocvec(documents)?;
|
| 31 |
-
let khr_offset = thumbnail_bytes.len() as u64;
|
| 32 |
-
|
| 33 |
-
let mut output = thumbnail_bytes;
|
| 34 |
-
output.extend_from_slice(&khr_bytes);
|
| 35 |
-
output.extend_from_slice(&khr_offset.to_le_bytes());
|
| 36 |
-
output.extend_from_slice(KHR_MAGIC);
|
| 37 |
-
|
| 38 |
-
Ok(output)
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
pub fn deserialize_khr(bytes: &[u8]) -> anyhow::Result<Vec<Document>> {
|
| 42 |
-
if bytes.len() >= KHR_FOOTER_LEN && has_khr_magic(bytes) {
|
| 43 |
-
let offset_start = bytes.len() - KHR_FOOTER_LEN;
|
| 44 |
-
let offset_bytes: [u8; 8] = bytes[offset_start..offset_start + 8]
|
| 45 |
-
.try_into()
|
| 46 |
-
.expect("slice with exact length");
|
| 47 |
-
let khr_offset = u64::from_le_bytes(offset_bytes) as usize;
|
| 48 |
-
let khr_end = bytes.len() - KHR_FOOTER_LEN;
|
| 49 |
-
|
| 50 |
-
if khr_offset > khr_end {
|
| 51 |
-
bail!("Invalid KHR offset in file");
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
let khr_bytes = &bytes[khr_offset..khr_end];
|
| 55 |
-
return decode_postcard(khr_bytes);
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
// fallback to legacy format without footer/signature
|
| 59 |
-
decode_postcard(bytes)
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
fn decode_postcard(bytes: &[u8]) -> anyhow::Result<Vec<Document>> {
|
| 63 |
-
if let Ok(documents) = postcard::from_bytes(bytes) {
|
| 64 |
-
return Ok(documents);
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
let document: Document = postcard::from_bytes(bytes)?;
|
| 68 |
-
Ok(vec![document])
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
fn thumbnail_contact_sheet(documents: &[Document]) -> DynamicImage {
|
| 72 |
-
if documents.is_empty() {
|
| 73 |
-
return DynamicImage::new_rgba8(1, 1);
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
let mut canvas = RgbaImage::from_pixel(
|
| 77 |
-
THUMBNAIL_WIDTH,
|
| 78 |
-
THUMBNAIL_HEIGHT,
|
| 79 |
-
image::Rgba([255, 255, 255, 255]),
|
| 80 |
-
);
|
| 81 |
-
|
| 82 |
-
// If there's only one document, fill the entire canvas with it.
|
| 83 |
-
if documents.len() == 1 {
|
| 84 |
-
let thumb = documents[0]
|
| 85 |
-
.image
|
| 86 |
-
.thumbnail(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
|
| 87 |
-
let (thumb_w, thumb_h) = thumb.dimensions();
|
| 88 |
-
let x = ((THUMBNAIL_WIDTH - thumb_w) / 2) as i64;
|
| 89 |
-
let y = ((THUMBNAIL_HEIGHT - thumb_h) / 2) as i64;
|
| 90 |
-
imageops::overlay(&mut canvas, &thumb.to_rgba8(), x, y);
|
| 91 |
-
} else {
|
| 92 |
-
// First image takes the left 1/3 of the canvas.
|
| 93 |
-
let left_width = THUMBNAIL_WIDTH / 3;
|
| 94 |
-
let first_thumb = documents[0].image.thumbnail(left_width, THUMBNAIL_HEIGHT);
|
| 95 |
-
let (first_w, first_h) = first_thumb.dimensions();
|
| 96 |
-
let first_x = ((left_width - first_w) / 2) as i64;
|
| 97 |
-
let first_y = ((THUMBNAIL_HEIGHT - first_h) / 2) as i64;
|
| 98 |
-
imageops::overlay(&mut canvas, &first_thumb.to_rgba8(), first_x, first_y);
|
| 99 |
-
|
| 100 |
-
// Remaining images are packed into the right 2/3 area.
|
| 101 |
-
let remaining = &documents[1..];
|
| 102 |
-
if !remaining.is_empty() {
|
| 103 |
-
let area_width = THUMBNAIL_WIDTH - left_width;
|
| 104 |
-
let area_height = THUMBNAIL_HEIGHT;
|
| 105 |
-
|
| 106 |
-
let cols = ((remaining.len() as f64).sqrt().ceil() as u32).max(1);
|
| 107 |
-
let rows = (remaining.len() as u32).div_ceil(cols);
|
| 108 |
-
|
| 109 |
-
let cell_w = (area_width / cols).max(1);
|
| 110 |
-
let cell_h = (area_height / rows).max(1);
|
| 111 |
-
|
| 112 |
-
for (idx, document) in remaining.iter().enumerate() {
|
| 113 |
-
let thumb = document.image.thumbnail(cell_w, cell_h);
|
| 114 |
-
let (thumb_w, thumb_h) = thumb.dimensions();
|
| 115 |
-
|
| 116 |
-
let col = (idx as u32) % cols;
|
| 117 |
-
let row = (idx as u32) / cols;
|
| 118 |
-
|
| 119 |
-
let base_x = left_width as i64 + (col * cell_w) as i64;
|
| 120 |
-
let base_y = (row * cell_h) as i64;
|
| 121 |
-
let x = base_x + ((cell_w as i64 - thumb_w as i64) / 2);
|
| 122 |
-
let y = base_y + ((cell_h as i64 - thumb_h as i64) / 2);
|
| 123 |
-
|
| 124 |
-
imageops::overlay(&mut canvas, &thumb.to_rgba8(), x, y);
|
| 125 |
-
}
|
| 126 |
-
}
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
// overlay small app icon on the bottom-left of the composite thumbnail
|
| 130 |
-
let icon = &*ICON_IMAGE;
|
| 131 |
-
let icon_w = icon.width() as i64;
|
| 132 |
-
let icon_h = icon.height() as i64;
|
| 133 |
-
let canvas_w = canvas.width() as i64;
|
| 134 |
-
let canvas_h = canvas.height() as i64;
|
| 135 |
-
|
| 136 |
-
if canvas_w >= icon_w && canvas_h >= icon_h {
|
| 137 |
-
let padding = 8i64;
|
| 138 |
-
let x = padding;
|
| 139 |
-
let y = (canvas_h - icon_h).max(0);
|
| 140 |
-
imageops::overlay(&mut canvas, icon, x, y);
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
DynamicImage::ImageRgba8(canvas)
|
| 144 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
koharu/src/lib.rs
CHANGED
|
@@ -1,14 +1,4 @@
|
|
| 1 |
pub mod app;
|
| 2 |
-
pub mod image;
|
| 3 |
-
pub mod khr;
|
| 4 |
-
pub mod llm;
|
| 5 |
-
pub mod ml;
|
| 6 |
-
pub mod operations;
|
| 7 |
-
pub mod pipeline;
|
| 8 |
-
pub mod renderer;
|
| 9 |
-
pub mod rpc;
|
| 10 |
-
pub mod server;
|
| 11 |
-
pub mod state;
|
| 12 |
pub mod version;
|
| 13 |
#[cfg(target_os = "windows")]
|
| 14 |
pub mod windows;
|
|
|
|
| 1 |
pub mod app;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
pub mod version;
|
| 3 |
#[cfg(target_os = "windows")]
|
| 4 |
pub mod windows;
|
koharu/src/rpc.rs
DELETED
|
@@ -1,569 +0,0 @@
|
|
| 1 |
-
use std::sync::Arc;
|
| 2 |
-
use std::time::Duration;
|
| 3 |
-
|
| 4 |
-
use anyhow::Result;
|
| 5 |
-
use async_trait::async_trait;
|
| 6 |
-
use axum::{
|
| 7 |
-
extract::{
|
| 8 |
-
State,
|
| 9 |
-
ws::{Message, WebSocket, WebSocketUpgrade},
|
| 10 |
-
},
|
| 11 |
-
response::IntoResponse,
|
| 12 |
-
};
|
| 13 |
-
use futures::{SinkExt, StreamExt};
|
| 14 |
-
use serde::{Deserialize, Serialize};
|
| 15 |
-
use tokio::sync::{broadcast, mpsc};
|
| 16 |
-
use tower::{Service, ServiceBuilder, ServiceExt, service_fn, timeout::TimeoutLayer};
|
| 17 |
-
|
| 18 |
-
use crate::app::AppResources;
|
| 19 |
-
use crate::operations;
|
| 20 |
-
|
| 21 |
-
// --- Shared Resources (lazy init) ---
|
| 22 |
-
|
| 23 |
-
pub type SharedResources = Arc<tokio::sync::OnceCell<AppResources>>;
|
| 24 |
-
|
| 25 |
-
// --- Wire Protocol ---
|
| 26 |
-
|
| 27 |
-
#[derive(Debug, Deserialize)]
|
| 28 |
-
struct RawIncoming {
|
| 29 |
-
id: u32,
|
| 30 |
-
method: String,
|
| 31 |
-
params: Option<rmpv::Value>,
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
#[derive(Debug, Serialize)]
|
| 35 |
-
#[serde(tag = "type")]
|
| 36 |
-
pub enum OutgoingMessage {
|
| 37 |
-
#[serde(rename = "res")]
|
| 38 |
-
Response {
|
| 39 |
-
id: u32,
|
| 40 |
-
#[serde(skip_serializing_if = "Option::is_none")]
|
| 41 |
-
result: Option<rmpv::Value>,
|
| 42 |
-
#[serde(skip_serializing_if = "Option::is_none")]
|
| 43 |
-
error: Option<String>,
|
| 44 |
-
},
|
| 45 |
-
#[serde(rename = "ntf")]
|
| 46 |
-
Notification { method: String, params: rmpv::Value },
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
fn ok_response(id: u32, result: rmpv::Value) -> OutgoingMessage {
|
| 50 |
-
OutgoingMessage::Response {
|
| 51 |
-
id,
|
| 52 |
-
result: Some(result),
|
| 53 |
-
error: None,
|
| 54 |
-
}
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
fn err_response(id: u32, msg: &str) -> OutgoingMessage {
|
| 58 |
-
OutgoingMessage::Response {
|
| 59 |
-
id,
|
| 60 |
-
result: None,
|
| 61 |
-
error: Some(msg.to_string()),
|
| 62 |
-
}
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
// --- Value conversion helpers ---
|
| 66 |
-
|
| 67 |
-
fn to_value<T: Serialize>(val: &T) -> Result<rmpv::Value> {
|
| 68 |
-
let bytes = rmp_serde::to_vec_named(val)?;
|
| 69 |
-
Ok(rmp_serde::from_slice(&bytes)?)
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
fn from_value<T: serde::de::DeserializeOwned>(val: rmpv::Value) -> Result<T> {
|
| 73 |
-
let bytes = rmp_serde::to_vec_named(&val)?;
|
| 74 |
-
Ok(rmp_serde::from_slice(&bytes)?)
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
// --- Handler Trait ---
|
| 78 |
-
|
| 79 |
-
#[async_trait]
|
| 80 |
-
#[enum_dispatch::enum_dispatch]
|
| 81 |
-
pub trait RpcCall {
|
| 82 |
-
async fn call(self, res: AppResources) -> Result<rmpv::Value>;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
// --- Handler Structs ---
|
| 86 |
-
|
| 87 |
-
// No-param handlers
|
| 88 |
-
|
| 89 |
-
#[derive(Deserialize)]
|
| 90 |
-
pub struct AppVersionHandler;
|
| 91 |
-
|
| 92 |
-
#[async_trait]
|
| 93 |
-
impl RpcCall for AppVersionHandler {
|
| 94 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 95 |
-
let v = operations::app_version(state).await?;
|
| 96 |
-
to_value(&v)
|
| 97 |
-
}
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
#[derive(Deserialize)]
|
| 101 |
-
pub struct DeviceHandler;
|
| 102 |
-
|
| 103 |
-
#[async_trait]
|
| 104 |
-
impl RpcCall for DeviceHandler {
|
| 105 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 106 |
-
let v = operations::device(state).await?;
|
| 107 |
-
to_value(&v)
|
| 108 |
-
}
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
#[derive(Deserialize)]
|
| 112 |
-
pub struct GetDocumentsHandler;
|
| 113 |
-
|
| 114 |
-
#[async_trait]
|
| 115 |
-
impl RpcCall for GetDocumentsHandler {
|
| 116 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 117 |
-
let v = operations::get_documents(state).await?;
|
| 118 |
-
to_value(&v)
|
| 119 |
-
}
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
#[derive(Deserialize)]
|
| 123 |
-
pub struct ListFontFamiliesHandler;
|
| 124 |
-
|
| 125 |
-
#[async_trait]
|
| 126 |
-
impl RpcCall for ListFontFamiliesHandler {
|
| 127 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 128 |
-
let v = operations::list_font_families(state).await?;
|
| 129 |
-
to_value(&v)
|
| 130 |
-
}
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
#[derive(Deserialize)]
|
| 134 |
-
pub struct LlmListHandler;
|
| 135 |
-
|
| 136 |
-
#[async_trait]
|
| 137 |
-
impl RpcCall for LlmListHandler {
|
| 138 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 139 |
-
let v = operations::llm_list(state).await?;
|
| 140 |
-
to_value(&v)
|
| 141 |
-
}
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
#[derive(Deserialize)]
|
| 145 |
-
pub struct LlmOffloadHandler;
|
| 146 |
-
|
| 147 |
-
#[async_trait]
|
| 148 |
-
impl RpcCall for LlmOffloadHandler {
|
| 149 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 150 |
-
operations::llm_offload(state).await?;
|
| 151 |
-
Ok(rmpv::Value::Nil)
|
| 152 |
-
}
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
#[derive(Deserialize)]
|
| 156 |
-
pub struct LlmReadyHandler;
|
| 157 |
-
|
| 158 |
-
#[async_trait]
|
| 159 |
-
impl RpcCall for LlmReadyHandler {
|
| 160 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 161 |
-
let v = operations::llm_ready(state).await?;
|
| 162 |
-
to_value(&v)
|
| 163 |
-
}
|
| 164 |
-
}
|
| 165 |
-
|
| 166 |
-
#[derive(Deserialize)]
|
| 167 |
-
pub struct SaveDocumentsHandler;
|
| 168 |
-
|
| 169 |
-
#[async_trait]
|
| 170 |
-
impl RpcCall for SaveDocumentsHandler {
|
| 171 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 172 |
-
let v = operations::save_documents(state).await?;
|
| 173 |
-
to_value(&v)
|
| 174 |
-
}
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
#[derive(Deserialize)]
|
| 178 |
-
pub struct ProcessCancelHandler;
|
| 179 |
-
|
| 180 |
-
#[async_trait]
|
| 181 |
-
impl RpcCall for ProcessCancelHandler {
|
| 182 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 183 |
-
operations::process_cancel(state).await?;
|
| 184 |
-
Ok(rmpv::Value::Nil)
|
| 185 |
-
}
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
// Index-based handlers (wrapping IndexPayload)
|
| 189 |
-
|
| 190 |
-
#[derive(Deserialize)]
|
| 191 |
-
#[serde(transparent)]
|
| 192 |
-
pub struct GetDocumentHandler(operations::IndexPayload);
|
| 193 |
-
|
| 194 |
-
#[async_trait]
|
| 195 |
-
impl RpcCall for GetDocumentHandler {
|
| 196 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 197 |
-
let v = operations::get_document(state, self.0).await?;
|
| 198 |
-
to_value(&v)
|
| 199 |
-
}
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
#[derive(Deserialize)]
|
| 203 |
-
#[serde(transparent)]
|
| 204 |
-
pub struct GetThumbnailHandler(operations::IndexPayload);
|
| 205 |
-
|
| 206 |
-
#[async_trait]
|
| 207 |
-
impl RpcCall for GetThumbnailHandler {
|
| 208 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 209 |
-
let v = operations::get_thumbnail(state, self.0).await?;
|
| 210 |
-
to_value(&v)
|
| 211 |
-
}
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
#[derive(Deserialize)]
|
| 215 |
-
#[serde(transparent)]
|
| 216 |
-
pub struct ExportDocumentHandler(operations::IndexPayload);
|
| 217 |
-
|
| 218 |
-
#[async_trait]
|
| 219 |
-
impl RpcCall for ExportDocumentHandler {
|
| 220 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 221 |
-
let v = operations::export_document(state, self.0).await?;
|
| 222 |
-
to_value(&v)
|
| 223 |
-
}
|
| 224 |
-
}
|
| 225 |
-
|
| 226 |
-
#[derive(Deserialize)]
|
| 227 |
-
#[serde(transparent)]
|
| 228 |
-
pub struct DetectHandler(operations::IndexPayload);
|
| 229 |
-
|
| 230 |
-
#[async_trait]
|
| 231 |
-
impl RpcCall for DetectHandler {
|
| 232 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 233 |
-
operations::detect(state, self.0).await?;
|
| 234 |
-
Ok(rmpv::Value::Nil)
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
#[derive(Deserialize)]
|
| 239 |
-
#[serde(transparent)]
|
| 240 |
-
pub struct OcrHandler(operations::IndexPayload);
|
| 241 |
-
|
| 242 |
-
#[async_trait]
|
| 243 |
-
impl RpcCall for OcrHandler {
|
| 244 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 245 |
-
operations::ocr(state, self.0).await?;
|
| 246 |
-
Ok(rmpv::Value::Nil)
|
| 247 |
-
}
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
#[derive(Deserialize)]
|
| 251 |
-
#[serde(transparent)]
|
| 252 |
-
pub struct InpaintHandler(operations::IndexPayload);
|
| 253 |
-
|
| 254 |
-
#[async_trait]
|
| 255 |
-
impl RpcCall for InpaintHandler {
|
| 256 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 257 |
-
operations::inpaint(state, self.0).await?;
|
| 258 |
-
Ok(rmpv::Value::Nil)
|
| 259 |
-
}
|
| 260 |
-
}
|
| 261 |
-
|
| 262 |
-
// Complex payload handlers
|
| 263 |
-
|
| 264 |
-
#[derive(Deserialize)]
|
| 265 |
-
#[serde(transparent)]
|
| 266 |
-
pub struct OpenExternalHandler(operations::OpenExternalPayload);
|
| 267 |
-
|
| 268 |
-
#[async_trait]
|
| 269 |
-
impl RpcCall for OpenExternalHandler {
|
| 270 |
-
async fn call(self, _state: AppResources) -> Result<rmpv::Value> {
|
| 271 |
-
operations::open_external(self.0)?;
|
| 272 |
-
Ok(rmpv::Value::Nil)
|
| 273 |
-
}
|
| 274 |
-
}
|
| 275 |
-
|
| 276 |
-
#[derive(Deserialize)]
|
| 277 |
-
#[serde(transparent)]
|
| 278 |
-
pub struct OpenDocumentsHandler(operations::OpenDocumentsPayload);
|
| 279 |
-
|
| 280 |
-
#[async_trait]
|
| 281 |
-
impl RpcCall for OpenDocumentsHandler {
|
| 282 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 283 |
-
let v = operations::open_documents(state, self.0).await?;
|
| 284 |
-
to_value(&v)
|
| 285 |
-
}
|
| 286 |
-
}
|
| 287 |
-
|
| 288 |
-
#[derive(Deserialize)]
|
| 289 |
-
#[serde(transparent)]
|
| 290 |
-
pub struct UpdateInpaintMaskHandler(operations::UpdateInpaintMaskPayload);
|
| 291 |
-
|
| 292 |
-
#[async_trait]
|
| 293 |
-
impl RpcCall for UpdateInpaintMaskHandler {
|
| 294 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 295 |
-
operations::update_inpaint_mask(state, self.0).await?;
|
| 296 |
-
Ok(rmpv::Value::Nil)
|
| 297 |
-
}
|
| 298 |
-
}
|
| 299 |
-
|
| 300 |
-
#[derive(Deserialize)]
|
| 301 |
-
#[serde(transparent)]
|
| 302 |
-
pub struct UpdateBrushLayerHandler(operations::UpdateBrushLayerPayload);
|
| 303 |
-
|
| 304 |
-
#[async_trait]
|
| 305 |
-
impl RpcCall for UpdateBrushLayerHandler {
|
| 306 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 307 |
-
operations::update_brush_layer(state, self.0).await?;
|
| 308 |
-
Ok(rmpv::Value::Nil)
|
| 309 |
-
}
|
| 310 |
-
}
|
| 311 |
-
|
| 312 |
-
#[derive(Deserialize)]
|
| 313 |
-
#[serde(transparent)]
|
| 314 |
-
pub struct InpaintPartialHandler(operations::InpaintPartialPayload);
|
| 315 |
-
|
| 316 |
-
#[async_trait]
|
| 317 |
-
impl RpcCall for InpaintPartialHandler {
|
| 318 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 319 |
-
operations::inpaint_partial(state, self.0).await?;
|
| 320 |
-
Ok(rmpv::Value::Nil)
|
| 321 |
-
}
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
#[derive(Deserialize)]
|
| 325 |
-
#[serde(transparent)]
|
| 326 |
-
pub struct RenderHandler(operations::RenderPayload);
|
| 327 |
-
|
| 328 |
-
#[async_trait]
|
| 329 |
-
impl RpcCall for RenderHandler {
|
| 330 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 331 |
-
operations::render(state, self.0).await?;
|
| 332 |
-
Ok(rmpv::Value::Nil)
|
| 333 |
-
}
|
| 334 |
-
}
|
| 335 |
-
|
| 336 |
-
#[derive(Deserialize)]
|
| 337 |
-
#[serde(transparent)]
|
| 338 |
-
pub struct UpdateTextBlocksHandler(operations::UpdateTextBlocksPayload);
|
| 339 |
-
|
| 340 |
-
#[async_trait]
|
| 341 |
-
impl RpcCall for UpdateTextBlocksHandler {
|
| 342 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 343 |
-
operations::update_text_blocks(state, self.0).await?;
|
| 344 |
-
Ok(rmpv::Value::Nil)
|
| 345 |
-
}
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
#[derive(Deserialize)]
|
| 349 |
-
#[serde(transparent)]
|
| 350 |
-
pub struct LlmLoadHandler(operations::LlmLoadPayload);
|
| 351 |
-
|
| 352 |
-
#[async_trait]
|
| 353 |
-
impl RpcCall for LlmLoadHandler {
|
| 354 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 355 |
-
operations::llm_load(state, self.0).await?;
|
| 356 |
-
Ok(rmpv::Value::Nil)
|
| 357 |
-
}
|
| 358 |
-
}
|
| 359 |
-
|
| 360 |
-
#[derive(Deserialize)]
|
| 361 |
-
#[serde(transparent)]
|
| 362 |
-
pub struct LlmGenerateHandler(operations::LlmGeneratePayload);
|
| 363 |
-
|
| 364 |
-
#[async_trait]
|
| 365 |
-
impl RpcCall for LlmGenerateHandler {
|
| 366 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 367 |
-
operations::llm_generate(state, self.0).await?;
|
| 368 |
-
Ok(rmpv::Value::Nil)
|
| 369 |
-
}
|
| 370 |
-
}
|
| 371 |
-
|
| 372 |
-
#[derive(Deserialize)]
|
| 373 |
-
#[serde(transparent)]
|
| 374 |
-
pub struct ProcessHandler(crate::pipeline::ProcessRequest);
|
| 375 |
-
|
| 376 |
-
#[async_trait]
|
| 377 |
-
impl RpcCall for ProcessHandler {
|
| 378 |
-
async fn call(self, state: AppResources) -> Result<rmpv::Value> {
|
| 379 |
-
operations::process(state, self.0).await?;
|
| 380 |
-
Ok(rmpv::Value::Nil)
|
| 381 |
-
}
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
-
// --- Method Dispatch Enum ---
|
| 385 |
-
|
| 386 |
-
#[derive(Deserialize)]
|
| 387 |
-
#[serde(tag = "method", content = "params")]
|
| 388 |
-
#[enum_dispatch::enum_dispatch(RpcCall)]
|
| 389 |
-
pub enum Method {
|
| 390 |
-
#[serde(rename = "app_version")]
|
| 391 |
-
AppVersion(AppVersionHandler),
|
| 392 |
-
#[serde(rename = "device")]
|
| 393 |
-
Device(DeviceHandler),
|
| 394 |
-
#[serde(rename = "open_external")]
|
| 395 |
-
OpenExternal(OpenExternalHandler),
|
| 396 |
-
#[serde(rename = "get_documents")]
|
| 397 |
-
GetDocuments(GetDocumentsHandler),
|
| 398 |
-
#[serde(rename = "get_document")]
|
| 399 |
-
GetDocument(GetDocumentHandler),
|
| 400 |
-
#[serde(rename = "get_thumbnail")]
|
| 401 |
-
GetThumbnail(GetThumbnailHandler),
|
| 402 |
-
#[serde(rename = "open_documents")]
|
| 403 |
-
OpenDocuments(OpenDocumentsHandler),
|
| 404 |
-
#[serde(rename = "save_documents")]
|
| 405 |
-
SaveDocuments(SaveDocumentsHandler),
|
| 406 |
-
#[serde(rename = "export_document")]
|
| 407 |
-
ExportDocument(ExportDocumentHandler),
|
| 408 |
-
#[serde(rename = "detect")]
|
| 409 |
-
Detect(DetectHandler),
|
| 410 |
-
#[serde(rename = "ocr")]
|
| 411 |
-
Ocr(OcrHandler),
|
| 412 |
-
#[serde(rename = "inpaint")]
|
| 413 |
-
Inpaint(InpaintHandler),
|
| 414 |
-
#[serde(rename = "update_inpaint_mask")]
|
| 415 |
-
UpdateInpaintMask(UpdateInpaintMaskHandler),
|
| 416 |
-
#[serde(rename = "update_brush_layer")]
|
| 417 |
-
UpdateBrushLayer(UpdateBrushLayerHandler),
|
| 418 |
-
#[serde(rename = "inpaint_partial")]
|
| 419 |
-
InpaintPartial(InpaintPartialHandler),
|
| 420 |
-
#[serde(rename = "render")]
|
| 421 |
-
Render(RenderHandler),
|
| 422 |
-
#[serde(rename = "update_text_blocks")]
|
| 423 |
-
UpdateTextBlocks(UpdateTextBlocksHandler),
|
| 424 |
-
#[serde(rename = "list_font_families")]
|
| 425 |
-
ListFontFamilies(ListFontFamiliesHandler),
|
| 426 |
-
#[serde(rename = "llm_list")]
|
| 427 |
-
LlmList(LlmListHandler),
|
| 428 |
-
#[serde(rename = "llm_load")]
|
| 429 |
-
LlmLoad(LlmLoadHandler),
|
| 430 |
-
#[serde(rename = "llm_offload")]
|
| 431 |
-
LlmOffload(LlmOffloadHandler),
|
| 432 |
-
#[serde(rename = "llm_ready")]
|
| 433 |
-
LlmReady(LlmReadyHandler),
|
| 434 |
-
#[serde(rename = "llm_generate")]
|
| 435 |
-
LlmGenerate(LlmGenerateHandler),
|
| 436 |
-
#[serde(rename = "process")]
|
| 437 |
-
Process(ProcessHandler),
|
| 438 |
-
#[serde(rename = "process_cancel")]
|
| 439 |
-
ProcessCancel(ProcessCancelHandler),
|
| 440 |
-
}
|
| 441 |
-
|
| 442 |
-
/// Parse method name + params into a Method enum using serde adjacently-tagged deserialization.
|
| 443 |
-
fn parse_call(method: &str, params: rmpv::Value) -> Result<Method> {
|
| 444 |
-
let tagged = rmpv::Value::Map(vec![
|
| 445 |
-
(
|
| 446 |
-
rmpv::Value::String("method".into()),
|
| 447 |
-
rmpv::Value::String(method.into()),
|
| 448 |
-
),
|
| 449 |
-
(rmpv::Value::String("params".into()), params),
|
| 450 |
-
]);
|
| 451 |
-
from_value(tagged)
|
| 452 |
-
}
|
| 453 |
-
|
| 454 |
-
// --- WebSocket State ---
|
| 455 |
-
|
| 456 |
-
#[derive(Clone)]
|
| 457 |
-
pub struct WsState {
|
| 458 |
-
pub resources: SharedResources,
|
| 459 |
-
}
|
| 460 |
-
|
| 461 |
-
// --- WebSocket Handler ---
|
| 462 |
-
|
| 463 |
-
pub async fn ws_handler(ws: WebSocketUpgrade, State(state): State<WsState>) -> impl IntoResponse {
|
| 464 |
-
ws.max_message_size(1024 * 1024 * 1024)
|
| 465 |
-
.on_upgrade(|socket| handle_socket(socket, state))
|
| 466 |
-
}
|
| 467 |
-
|
| 468 |
-
async fn handle_socket(socket: WebSocket, state: WsState) {
|
| 469 |
-
let (mut ws_sender, mut ws_receiver) = socket.split();
|
| 470 |
-
let (tx, mut send_rx) = mpsc::channel::<OutgoingMessage>(256);
|
| 471 |
-
|
| 472 |
-
// Notification forwarders
|
| 473 |
-
spawn_notification_forwarder(
|
| 474 |
-
"download_progress",
|
| 475 |
-
koharu_core::download::subscribe(),
|
| 476 |
-
tx.clone(),
|
| 477 |
-
);
|
| 478 |
-
spawn_notification_forwarder("process_progress", crate::pipeline::subscribe(), tx.clone());
|
| 479 |
-
|
| 480 |
-
// Sender task: drain mpsc → WebSocket binary frames
|
| 481 |
-
let send_task = tokio::spawn(async move {
|
| 482 |
-
while let Some(msg) = send_rx.recv().await {
|
| 483 |
-
let Ok(bytes) = rmp_serde::to_vec_named(&msg) else {
|
| 484 |
-
continue;
|
| 485 |
-
};
|
| 486 |
-
if ws_sender.send(Message::Binary(bytes.into())).await.is_err() {
|
| 487 |
-
break;
|
| 488 |
-
}
|
| 489 |
-
}
|
| 490 |
-
});
|
| 491 |
-
|
| 492 |
-
// Tower dispatch service (cloned per request)
|
| 493 |
-
let dispatch_svc = ServiceBuilder::new()
|
| 494 |
-
.layer(TimeoutLayer::new(Duration::from_secs(300)))
|
| 495 |
-
.service(service_fn(
|
| 496 |
-
|(method, res): (Method, AppResources)| async move { method.call(res).await },
|
| 497 |
-
));
|
| 498 |
-
|
| 499 |
-
// Receive loop
|
| 500 |
-
while let Some(Ok(msg)) = ws_receiver.next().await {
|
| 501 |
-
let data = match msg {
|
| 502 |
-
Message::Binary(data) => data,
|
| 503 |
-
Message::Close(_) => break,
|
| 504 |
-
_ => continue,
|
| 505 |
-
};
|
| 506 |
-
|
| 507 |
-
let raw: RawIncoming = match rmp_serde::from_slice(&data) {
|
| 508 |
-
Ok(r) => r,
|
| 509 |
-
Err(e) => {
|
| 510 |
-
let _ = tx
|
| 511 |
-
.send(err_response(0, &format!("Decode error: {e}")))
|
| 512 |
-
.await;
|
| 513 |
-
continue;
|
| 514 |
-
}
|
| 515 |
-
};
|
| 516 |
-
|
| 517 |
-
let id = raw.id;
|
| 518 |
-
let tx = tx.clone();
|
| 519 |
-
let resources = state.resources.clone();
|
| 520 |
-
let mut svc = dispatch_svc.clone();
|
| 521 |
-
|
| 522 |
-
tokio::spawn(async move {
|
| 523 |
-
let response = match resources.get() {
|
| 524 |
-
Some(res) => {
|
| 525 |
-
let params = raw.params.unwrap_or(rmpv::Value::Nil);
|
| 526 |
-
match parse_call(&raw.method, params) {
|
| 527 |
-
Ok(method) => match svc.ready().await {
|
| 528 |
-
Ok(ready) => match ready.call((method, res.clone())).await {
|
| 529 |
-
Ok(result) => ok_response(id, result),
|
| 530 |
-
Err(e) => err_response(id, &format!("{e:#}")),
|
| 531 |
-
},
|
| 532 |
-
Err(e) => err_response(id, &format!("{e}")),
|
| 533 |
-
},
|
| 534 |
-
Err(e) => err_response(id, &format!("{e:#}")),
|
| 535 |
-
}
|
| 536 |
-
}
|
| 537 |
-
None => err_response(id, "Resources not initialized"),
|
| 538 |
-
};
|
| 539 |
-
let _ = tx.send(response).await;
|
| 540 |
-
});
|
| 541 |
-
}
|
| 542 |
-
|
| 543 |
-
send_task.abort();
|
| 544 |
-
}
|
| 545 |
-
|
| 546 |
-
fn spawn_notification_forwarder<T: Serialize + Clone + Send + 'static>(
|
| 547 |
-
method: &'static str,
|
| 548 |
-
mut rx: broadcast::Receiver<T>,
|
| 549 |
-
tx: mpsc::Sender<OutgoingMessage>,
|
| 550 |
-
) {
|
| 551 |
-
tokio::spawn(async move {
|
| 552 |
-
loop {
|
| 553 |
-
match rx.recv().await {
|
| 554 |
-
Ok(payload) => {
|
| 555 |
-
let params = to_value(&payload).unwrap_or(rmpv::Value::Nil);
|
| 556 |
-
let msg = OutgoingMessage::Notification {
|
| 557 |
-
method: method.to_string(),
|
| 558 |
-
params,
|
| 559 |
-
};
|
| 560 |
-
if tx.send(msg).await.is_err() {
|
| 561 |
-
break;
|
| 562 |
-
}
|
| 563 |
-
}
|
| 564 |
-
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
| 565 |
-
Err(broadcast::error::RecvError::Closed) => break,
|
| 566 |
-
}
|
| 567 |
-
}
|
| 568 |
-
});
|
| 569 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|