diff --git a/bun.lockb b/bun.lockb index 87407f3..7490e65 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 1922a48..4108dbe 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "@tauri-apps/api": ">=2.0.0-beta.0", - "@tauri-apps/plugin-shell": ">=2.0.0-beta.0" + "@tauri-apps/plugin-shell": ">=2.0.0-beta.0", + "react-router-dom": "^6.23.1", + "usehooks-ts": "^3.1.0" }, "devDependencies": { "@tauri-apps/cli": ">=2.0.0-beta.0", @@ -27,4 +29,4 @@ "typescript": "^5.0.2", "vite": "^5.0.0" } -} +} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2b38129..f27820a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -111,6 +122,15 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "atk" version = "0.18.0" @@ -266,6 +286,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -338,6 +379,11 @@ name = "cc" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cesu8" @@ -391,6 +437,16 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cmake" version = "0.1.50" @@ -469,6 +525,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert_case" version = "0.4.0" @@ -524,6 +586,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -708,6 +785,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + [[package]] name = "deranged" version = "0.3.11" @@ -718,6 +801,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "derive_builder" version = "0.20.0" @@ -770,6 +864,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -820,6 +915,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1523,6 +1629,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -1755,6 +1870,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1879,6 +2003,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -2011,6 +2144,7 @@ dependencies = [ "tauri-build", "tauri-plugin-shell", "tokio", + "zip", ] [[package]] @@ -2038,6 +2172,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" @@ -2059,6 +2199,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "mac" version = "0.1.1" @@ -2650,6 +2800,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3583,6 +3743,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -5380,3 +5551,88 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zip" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap 2.2.6", + "lzma-rs", + "memchr", + "pbkdf2", + "rand 0.8.5", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.11+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ee6fe56..f815a3a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ reqwest = { version = "0.12.5", features = ["json"] } futures-util = "0.3.30" log = "0.4.21" env_logger = { version = "0.11.3", features = ["color"] } +zip = "2.1.3" [target.'cfg(target_os = "macos")'.dependencies] ct2rs = { version = "0.8.5", features = ["accelerate"] } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index e14ca1d..1a4fc23 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,7 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main"], + "windows": [ + "main" + ], "permissions": [ "path:default", "event:default", @@ -14,4 +16,4 @@ "tray:default", "shell:allow-open" ] -} +} \ No newline at end of file diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs index 1de5051..193731d 100644 --- a/src-tauri/src/cmd.rs +++ b/src-tauri/src/cmd.rs @@ -1,19 +1,79 @@ -use eyre::Result; +use crate::{downloader::Downloader, unzip}; +use ct2rs::Translator; +use eyre::{eyre, ContextCompat, Result}; +use std::{fs, path::PathBuf, time}; +use tauri::{AppHandle, Manager}; +use tauri_plugin_shell::ShellExt; #[tauri::command] -pub fn translate(language: String, model_path: String) -> Result { - log::debug!("translate with {} {}", language, model_path); - Ok(String::new()) +pub async fn translate(language: String, text: String, model_path: String) -> Result)>> { + log::debug!("translate with {} {} {}", language, model_path, text); + let cfg = ct2rs::config::Config::default(); + let t = Translator::new(&model_path, &cfg).map_err(|e| eyre!("{:?}", e))?; + let sources: Vec = text.lines().map(String::from).collect(); + let target_prefixes = vec![vec![language]; sources.len()]; + let now = time::Instant::now(); + let res = t + .translate_batch_with_target_prefix(&sources, &target_prefixes, &Default::default(), None) + .map_err(|e| eyre!("{:?}", e))?; + let elapsed = now.elapsed(); + log::info!("Time taken: {:?}", elapsed); + Ok(res) } #[tauri::command] -pub async fn download_model() -> Result { - log::debug!("download model"); - Ok(String::new()) +pub async fn download_model(app: AppHandle, url: String, filename: String) -> Result { + let local_data = app.path().app_local_data_dir()?; + std::fs::create_dir_all(local_data.clone())?; + log::debug!("download model from {}", url); + let mut downloader = Downloader::new(); + + let download_progress_callback = { + let app_handle = app.clone(); + + move |current: u64, total: u64| { + let app_handle = app_handle.clone(); + + // Update progress in background + tauri::async_runtime::spawn(async move { + let window = app_handle.get_webview_window("main").unwrap(); + let percentage = (current as f64 / total as f64) * 100.0; + log::debug!("percentage: {}", percentage); + if let Err(e) = window.emit("download_progress", (current, total)) { + log::error!("Failed to emit download progress: {}", e); + } + }); + // Return the abort signal immediately + false + } + }; + + let path = local_data.join(filename); + + log::debug!("download from {} to {}", url, path.display()); + downloader.download(&url, path.clone(), download_progress_callback).await?; + log::debug!("done download"); + unzip::unzip(&path, Some(&local_data))?; + fs::remove_file(path.clone())?; + let stem = path.file_stem().context("stem")?; + let folder = stem.to_str().context("tostr")?; + Ok(folder.to_string()) } #[tauri::command] -pub async fn get_model_path() -> Result> { +pub async fn get_model_path(app: AppHandle) -> Result> { log::debug!("get model path"); + let local_data = app.path().app_local_data_dir()?; + let path = local_data.join("nllb-200-distilled-600M"); + if path.exists() { + return Ok(Some(path)); + } Ok(None) } + +#[tauri::command] +pub async fn open_models_folder(app: AppHandle) -> Result<()> { + let local_data = app.path().app_local_data_dir()?; + app.shell().open(local_data.to_str().context("tostr")?, None)?; + Ok(()) +} diff --git a/src-tauri/src/downloader.rs b/src-tauri/src/downloader.rs index 6c39519..6ed4712 100644 --- a/src-tauri/src/downloader.rs +++ b/src-tauri/src/downloader.rs @@ -1,6 +1,5 @@ use eyre::{bail, Context, OptionExt, Result}; use futures_util::StreamExt; -use reqwest; use std::clone::Clone; use std::io::Write; use std::path::PathBuf; @@ -9,6 +8,7 @@ pub struct Downloader { client: reqwest::Client, } +#[allow(unused)] pub async fn get_filename(url: &str) -> Result { let client = reqwest::Client::new(); @@ -46,6 +46,7 @@ impl Downloader { let total_size = res .content_length() .ok_or_eyre(format!("Failed to get content length from '{}'", url))?; + log::debug!("total size is {}", total_size); let mut file = std::fs::File::create(path.clone()).context(format!("Failed to create file {}", path.display()))?; let mut downloaded: u64 = 0; let callback_limit = 1024 * 1024 * 2; // 1MB limit @@ -66,6 +67,7 @@ impl Downloader { } downloaded += chunk.len() as u64; } + log::debug!("finish download"); Ok(()) } } @@ -75,28 +77,3 @@ impl Default for Downloader { Self::new() } } - -#[cfg(test)] -mod tests { - use crate::{config, downloader}; - use eyre::{Context, Result}; - - fn init() { - let _ = env_logger::builder().is_test(true).try_init(); - } - - fn on_download_progress(_: u64, _: u64) -> bool { - false - } - - #[tokio::test] - async fn test_download() -> Result<()> { - init(); - let mut d = downloader::Downloader::new(); - let filepath = config::get_model_path()?; - d.download(config::URL, filepath, on_download_progress) - .await - .context("Cant download")?; - Ok(()) - } -} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a922670..6931b71 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,8 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod cmd; +mod downloader; +mod unzip; fn main() { env_logger::init(); @@ -10,7 +12,8 @@ fn main() { .invoke_handler(tauri::generate_handler![ cmd::translate, cmd::download_model, - cmd::get_model_path + cmd::get_model_path, + cmd::open_models_folder ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/unzip.rs b/src-tauri/src/unzip.rs new file mode 100644 index 0000000..288cd08 --- /dev/null +++ b/src-tauri/src/unzip.rs @@ -0,0 +1,62 @@ +use std::fs::{self, File}; +use std::io::{self}; +use std::path::{Path, PathBuf}; +use zip::ZipArchive; + +pub fn unzip(zip_path: &Path, parent_path: Option<&Path>) -> Result<(), std::io::Error> { + let file = File::open(zip_path)?; + let mut archive = ZipArchive::new(file)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i).unwrap(); + let outpath = match file.enclosed_name() { + Some(path) => { + if let Some(parent) = parent_path { + // Combine parent path with the extracted file's path + let mut full_path = PathBuf::new(); + full_path.push(parent); + full_path.push(path); + + full_path + } else { + // If no parent path is provided, extract using the original path + path + } + } + None => continue, + }; + + { + let comment = file.comment(); + if !comment.is_empty() { + println!("File {i} comment: {comment}"); + } + } + + if file.is_dir() { + println!("File {} extracted to \"{}\"", i, outpath.display()); + fs::create_dir_all(&outpath).unwrap(); + } else { + println!("File {} extracted to \"{}\" ({} bytes)", i, outpath.display(), file.size()); + if let Some(p) = outpath.parent() { + if !p.exists() { + fs::create_dir_all(p).unwrap(); + } + } + let mut outfile = fs::File::create(&outpath).unwrap(); + io::copy(&mut file, &mut outfile).unwrap(); + } + + // Get and Set permissions + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + if let Some(mode) = file.unix_mode() { + fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); + } + } + } + + Ok(()) +} diff --git a/src/App.tsx b/src/App.tsx index 10822b9..44aa5f1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,60 +1,16 @@ +import { Route, Routes } from "react-router-dom"; import "./globals.css"; -import languages from "./assets/languages.json"; -import { useState } from "react"; -import { invoke } from "@tauri-apps/api/core"; +import Home from "./pages/Home"; +import Setup from "./pages/Setup"; +import Settings from "./pages/Settings"; function App() { - const [srcText, setSrcText] = useState(""); - const [dstText, setDstText] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [language, setLanguage] = useState("eng"); - - async function translate() { - setIsLoading(true); - await invoke("translate", { language }); - await new Promise((resolve) => setTimeout(resolve, 3000)); - setIsLoading(false); - setDstText("result"); - } - return ( -
-

Lingo

- - - -
- -
- -
+ + } /> + } /> + } /> + ); } diff --git a/src/assets/languages.json b/src/assets/languages.json index fb2189c..d120bce 100644 --- a/src/assets/languages.json +++ b/src/assets/languages.json @@ -1,182 +1,182 @@ { - "acholi": "ach", - "adyghe": "ady", - "afrikaans": "afr", - "akan": "aka", - "albanian": "sq", - "amharic": "amh", - "arabic": "arb", - "armenian": "hye", - "assamese": "asm", - "asturian": "ast", - "avar": "ava", - "aymara": "aym", - "azerbaijani": "aze", - "bambara": "bam", - "bashkir": "bak", - "basque": "eus", - "belarusian": "bel", - "bengali": "ben", - "bihari": "bh", - "bislama": "bis", - "bosnian": "bos", - "breton": "bre", - "bulgarian": "bul", - "burmese": "mya", - "catalan": "cat", - "cebuano": "ceb", - "chamorro": "cha", - "chechen": "che", - "cherokee": "chr", - "chichewa": "nya", - "chuvash": "chv", - "corsican": "cos", - "cree": "cre", - "croatian": "hrv", - "czech": "ces", - "danish": "dan", - "divehi": "div", - "dutch": "nld", - "dzongkha": "dzo", - "english": "eng", - "esperanto": "epo", - "estonian": "est", - "ewe": "ewe", - "faroese": "fao", - "fijian": "fij", - "finnish": "fin", - "french": "fra", - "frisian": "fry", - "fulah": "ful", - "galician": "glg", - "ganda": "lug", - "georgian": "kat", - "german": "deu", - "greek": "ell", - "greenlandic": "kal", - "guarani": "grn", - "gujarati": "guj", - "haitian_creole": "hat", - "hausa": "hau", - "hawaiian": "haw", - "hebrew": "heb", - "herero": "her", - "hindi": "hin", - "hmong": "hmn", - "hungarian": "hun", - "icelandic": "isl", - "igbo": "ibo", - "indonesian": "ind", - "interlingua": "ina", - "interlingue": "ile", - "inuktitut": "iku", - "inupiaq": "ipk", - "irish": "gle", - "italian": "ita", - "japanese": "jpn", - "javanese": "jav", - "kabyle": "kab", - "kannada": "kan", - "kanuri": "kau", - "kashmiri": "kas", - "kazakh": "kaz", - "khmer": "khm", - "kikuyu": "kik", - "kinyarwanda": "kin", - "kirghiz": "kir", - "komi": "kom", - "kongo": "kon", - "korean": "kor", - "kurdish": "kur", - "kwanyama": "kua", - "lao": "lao", - "latin": "lat", - "latvian": "lav", - "limburgish": "lim", - "lingala": "lin", - "lithuanian": "lit", - "luba_katanga": "lub", - "luxembourgish": "ltz", - "macedonian": "mkd", - "malagasy": "mlg", - "malay": "msa", - "malayalam": "mal", - "maltese": "mlt", - "manx": "glv", - "maori": "mri", - "marathi": "mar", - "marshallese": "mah", - "mongolian": "mon", - "nepali": "nep", - "ndebele": "nbl", - "ndonga": "ndo", - "northern_sotho": "nso", - "norwegian": "nor", - "norwegian_nynorsk": "nno", - "nyanja": "nya", - "occitan": "oci", - "ojibwa": "oji", - "oriya": "ori", - "ossetian": "oss", - "pali": "pli", - "pashto": "pus", - "persian": "fas", - "polish": "pol", - "portuguese": "por", - "punjabi": "pan", - "quechua": "que", - "romanian": "ron", - "romansh": "roh", - "rundi": "run", - "russian": "rus", - "samoan": "smo", - "sango": "sag", - "sanskrit": "san", - "sardinian": "srd", - "scots_gaelic": "gla", - "serbian": "srp", - "shona": "sna", - "sichuan_yi": "iii", - "sindhi": "snd", - "sinhala": "sin", - "slovak": "slk", - "slovenian": "slv", - "somali": "som", - "sotho": "sot", - "spanish": "spa", - "sundanese": "sun", - "swahili": "swa", - "swati": "ssw", - "swedish": "swe", - "tagalog": "tgl", - "tahitian": "tah", - "tajik": "tgk", - "tamil": "tam", - "tatar": "tat", - "telugu": "tel", - "thai": "tha", - "tibetan": "bod", - "tigrinya": "tir", - "tonga": "ton", - "tsonga": "tsn", - "tswana": "tsn", - "turkish": "tur", - "turkmen": "tuk", - "twi": "twi", - "ukrainian": "ukr", - "urdu": "urd", - "uyghur": "uig", - "uzbek": "uzb", - "venda": "ven", - "vietnamese": "vie", - "volapuk": "vol", - "walloon": "wln", - "welsh": "cym", - "western_frisian": "fry", - "wolof": "wol", - "xhosa": "xho", - "yiddish": "yid", - "yoruba": "yor", - "zhuang": "zha", - "zulu": "zul" + "acholi": "ace_Arab", + "adyghe": "ady_Latn", + "afrikaans": "afr_Latn", + "akan": "aka_Latn", + "albanian": "sq_Latn", + "amharic": "amh_Ethi", + "arabic": "arb_Arab", + "armenian": "hye_Armn", + "assamese": "asm_Beng", + "asturian": "ast_Latn", + "avar": "ava_Latn", + "aymara": "aym_Latn", + "azerbaijani": "aze_Latn", + "bambara": "bam_Latn", + "bashkir": "bak_Cyrl", + "basque": "eus_Latn", + "belarusian": "bel_Cyrl", + "bengali": "ben_Beng", + "bihari": "bh_Latn", + "bislama": "bis_Latn", + "bosnian": "bos_Latn", + "breton": "bre_Latn", + "bulgarian": "bul_Cyrl", + "burmese": "mya_Mymr", + "catalan": "cat_Latn", + "cebuano": "ceb_Latn", + "chamorro": "cha_Latn", + "chechen": "che_Latn", + "cherokee": "chr_Latn", + "chichewa": "nya_Latn", + "chuvash": "chv_Latn", + "corsican": "cos_Latn", + "cree": "cre_Latn", + "croatian": "hrv_Latn", + "czech": "ces_Latn", + "danish": "dan_Latn", + "divehi": "div_Latn", + "dutch": "nld_Latn", + "dzongkha": "dzo_Tibt", + "english": "eng_Latn", + "esperanto": "epo_Latn", + "estonian": "est_Latn", + "ewe": "ewe_Latn", + "faroese": "fao_Latn", + "fijian": "fij_Latn", + "finnish": "fin_Latn", + "french": "fra_Latn", + "frisian": "fry_Latn", + "fulah": "ful_Latn", + "galician": "glg_Latn", + "ganda": "lug_Latn", + "georgian": "kat_Geor", + "german": "deu_Latn", + "greek": "ell_Grek", + "greenlandic": "kal_Latn", + "guarani": "grn_Latn", + "gujarati": "guj_Gujr", + "haitian_creole": "hat_Latn", + "hausa": "hau_Latn", + "hawaiian": "haw_Latn", + "hebrew": "heb_Hebr", + "herero": "her_Latn", + "hindi": "hin_Deva", + "hmong": "hmn_Latn", + "hungarian": "hun_Latn", + "icelandic": "isl_Latn", + "igbo": "ibo_Latn", + "indonesian": "ind_Latn", + "interlingua": "ina_Latn", + "interlingue": "ile_Latn", + "inuktitut": "iku_Latn", + "inupiaq": "ipk_Latn", + "irish": "gle_Latn", + "italian": "ita_Latn", + "japanese": "jpn_Jpan", + "javanese": "jav_Latn", + "kabyle": "kab_Latn", + "kannada": "kan_Knda", + "kanuri": "kau_Latn", + "kashmiri": "kas_Arab", + "kazakh": "kaz_Cyrl", + "khmer": "khm_Khmr", + "kikuyu": "kik_Latn", + "kinyarwanda": "kin_Latn", + "kirghiz": "kir_Cyrl", + "komi": "kom_Latn", + "kongo": "kon_Latn", + "korean": "kor_Hang", + "kurdish": "kur_Latn", + "kwanyama": "kua_Latn", + "lao": "lao_Laoo", + "latin": "lat_Latn", + "latvian": "lav_Latn", + "limburgish": "lim_Latn", + "lingala": "lin_Latn", + "lithuanian": "lit_Latn", + "luba_katanga": "lub_Latn", + "luxembourgish": "ltz_Latn", + "macedonian": "mkd_Cyrl", + "malagasy": "mlg_Latn", + "malay": "msa_Latn", + "malayalam": "mal_Mlym", + "maltese": "mlt_Latn", + "manx": "glv_Latn", + "maori": "mri_Latn", + "marathi": "mar_Deva", + "marshallese": "mah_Latn", + "mongolian": "mon_Latn", + "nepali": "nep_Deva", + "ndebele": "nbl_Latn", + "ndonga": "ndo_Latn", + "northern_sotho": "nso_Latn", + "norwegian": "nor_Latn", + "norwegian_nynorsk": "nno_Latn", + "nyanja": "nya_Latn", + "occitan": "oci_Latn", + "ojibwa": "oji_Latn", + "oriya": "ori_Orya", + "ossetian": "oss_Latn", + "pali": "pli_Latn", + "pashto": "pus_Arab", + "persian": "fas_Arab", + "polish": "pol_Latn", + "portuguese": "por_Latn", + "punjabi": "pan_Guru", + "quechua": "que_Latn", + "romanian": "ron_Latn", + "romansh": "roh_Latn", + "rundi": "run_Latn", + "russian": "rus_Cyrl", + "samoan": "smo_Latn", + "sango": "sag_Latn", + "sanskrit": "san_Deva", + "sardinian": "srd_Latn", + "scots_gaelic": "gla_Latn", + "serbian": "srp_Cyrl", + "shona": "sna_Latn", + "sichuan_yi": "iii_Latn", + "sindhi": "snd_Arab", + "sinhala": "sin_Sinh", + "slovak": "slk_Latn", + "slovenian": "slv_Latn", + "somali": "som_Latn", + "sotho": "sot_Latn", + "spanish": "spa_Latn", + "sundanese": "sun_Latn", + "swahili": "swa_Latn", + "swati": "ssw_Latn", + "swedish": "swe_Latn", + "tagalog": "tgl_Latn", + "tahitian": "tah_Latn", + "tajik": "tgk_Cyrl", + "tamil": "tam_Taml", + "tatar": "tat_Cyrl", + "telugu": "tel_Telu", + "thai": "tha_Thai", + "tibetan": "bod_Tibt", + "tigrinya": "tir_Ethi", + "tonga": "ton_Latn", + "tsonga": "tsn_Latn", + "tswana": "tsn_Latn", + "turkish": "tur_Latn", + "turkmen": "tuk_Latn", + "twi": "twi_Latn", + "ukrainian": "ukr_Cyrl", + "urdu": "urd_Arab", + "uyghur": "uig_Arab", + "uzbek": "uzb_Latn", + "venda": "ven_Latn", + "vietnamese": "vie_Latn", + "volapuk": "vol_Latn", + "walloon": "wln_Latn", + "welsh": "cym_Latn", + "western_frisian": "fry_Latn", + "wolof": "wol_Latn", + "xhosa": "xho_Latn", + "yiddish": "yid_Hebr", + "yoruba": "yor_Latn", + "zhuang": "zha_Latn", + "zulu": "zul_Latn" } \ No newline at end of file diff --git a/src/components/Download.tsx b/src/components/Download.tsx deleted file mode 100644 index 3567a2e..0000000 --- a/src/components/Download.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useEffect } from "react"; - -export default function Download() { - useEffect(() => {}, []); - return

download

; -} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..ed9dd59 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,3 @@ +export const modelURL = + "https://github.com/thewh1teagle/Lingo/releases/download/v0.0.1/nllb-200-distilled-600M.zip"; +export const modelFilename = "nllb-200-distilled-600M.zip"; diff --git a/src/main.tsx b/src/main.tsx index 2be325e..3fbaade 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,9 +1,10 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; +import { BrowserRouter } from "react-router-dom"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + - , + ); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..8ac3038 --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,95 @@ +import languages from "../assets/languages.json"; +import { useEffect, useState } from "react"; +import { invoke } from "@tauri-apps/api/core"; +import { useNavigate } from "react-router-dom"; +import { useLocalStorage } from "usehooks-ts"; + +type TranslateResponse = [string, number][]; // line, score + +export default function Home() { + const [srcText, setSrcText] = useState(""); + const [dstText, setDstText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [language, setLanguage] = useLocalStorage("prefs_language", "eng"); + const navigate = useNavigate(); + const [modelPath, setModelPath] = useState(null); + + async function translate() { + setIsLoading(true); + const resp = await invoke("translate", { + language, + modelPath, + text: srcText, + }); + console.log("resp => ", resp); + setIsLoading(false); + setDstText( + resp + .map(([line, _score]) => { + line = line.replace("", ""); + line = line.replace("", ""); + line = line.replace("", ""); + return line; + }) + .join("\n") + ); + } + + async function getModelPath() { + const modelPathResult = await invoke("get_model_path"); + if (!modelPathResult) { + navigate("/setup"); + } + setModelPath(modelPathResult); + } + + useEffect(() => { + getModelPath(); + }, []); + + return ( +
+

Lingo

+ +
+ +
+ + + +
+ +
+ +
+ ); +} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..c4563e9 --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,15 @@ +import { invoke } from "@tauri-apps/api/core"; + +export default function Settings() { + return ( +
+

settings

+ +
+ ); +} diff --git a/src/pages/Setup.tsx b/src/pages/Setup.tsx new file mode 100644 index 0000000..ea867ce --- /dev/null +++ b/src/pages/Setup.tsx @@ -0,0 +1,42 @@ +import { invoke } from "@tauri-apps/api/core"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import * as config from "../config"; +import { listen } from "@tauri-apps/api/event"; + +export default function Setup() { + const [progress, setProgress] = useState(null); + const navigate = useNavigate(); + + async function downloadModel() { + await new Promise((resolve) => setTimeout(resolve, 1000)); + await invoke("download_model", { + filename: config.modelFilename, + url: config.modelURL, + }); + navigate("/"); + } + + async function listenForProgress() { + await listen<[number, number]>("download_progress", (event) => { + const [part, total] = event.payload; + setProgress((part / total) * 100); + }); + } + + useEffect(() => { + listenForProgress(); + downloadModel(); + }, []); + + return ( +
+

Downloading model...

+ +
+ ); +}