├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feature.yml ├── actions │ ├── build-for-linux │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ └── build.sh └── workflows │ └── package.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── .prettierrc.json ├── .scripts ├── popclip │ ├── Config.plist │ ├── Pot.png │ ├── Pot.sh │ └── build.sh └── snipdo │ ├── build.sh │ ├── pot.json │ ├── pot.png │ └── pot.ps1 ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── CHANGELOG ├── LICENSE ├── README.md ├── README_EN.md ├── README_KR.md ├── asset ├── 1.png ├── 2.png ├── 3.png ├── eg1.gif ├── eg2.gif ├── eg3.gif ├── eg4.gif ├── eg5.gif ├── eg6.gif └── header.png ├── com.pot_app.pot.metainfo.xml ├── daemon.html ├── index.html ├── package.json ├── patches └── hyprland.patch ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── icon.png ├── icon.svg ├── logo │ ├── Darwin.svg │ ├── Linux.svg │ ├── Windows_NT.svg │ ├── alibaba.svg │ ├── anki.svg │ ├── baidu.svg │ ├── bing.svg │ ├── caiyun.svg │ ├── cambridge_dict.svg │ ├── chatglm.png │ ├── deepl.svg │ ├── ecdict.svg │ ├── eudic.png │ ├── geminipro.webp │ ├── google.svg │ ├── iflytek.png │ ├── lingva.svg │ ├── niutrans.svg │ ├── ollama.png │ ├── openai.svg │ ├── paddle.png │ ├── qrcode.svg │ ├── simple_latex.png │ ├── tencent.svg │ ├── tencent_cloud.png │ ├── tesseract.png │ ├── transmart.svg │ ├── volcengine.svg │ ├── yandex.svg │ └── youdao.svg ├── tesseract-core-simd-lstm.wasm.js └── worker.min.js ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── icon_mac.ico ├── icons_mac │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── tray.ico ├── resources │ ├── ocr-aarch64-apple-darwin │ └── ocr-x86_64-apple-darwin ├── src │ ├── backup.rs │ ├── clipboard.rs │ ├── cmd.rs │ ├── config.rs │ ├── error.rs │ ├── hotkey.rs │ ├── lang_detect.rs │ ├── main.rs │ ├── screenshot.rs │ ├── server.rs │ ├── system_ocr.rs │ ├── tray.rs │ ├── updater.rs │ └── window.rs ├── tauri.conf.json ├── tauri.linux.conf.json ├── tauri.macos.conf.json ├── tauri.windows.conf.json ├── webview.arm64.json ├── webview.x64.json └── webview.x86.json ├── src ├── App.jsx ├── components │ └── WindowControl │ │ ├── index.jsx │ │ └── style.css ├── hooks │ ├── index.jsx │ ├── useConfig.jsx │ ├── useGetState.jsx │ ├── useSyncAtom.jsx │ ├── useToastStyle.jsx │ └── useVoice.jsx ├── i18n │ ├── index.jsx │ └── locales │ │ ├── ar_AE.json │ │ ├── de_DE.json │ │ ├── en_US.json │ │ ├── es_ES.json │ │ ├── fa_IR.json │ │ ├── fr_FR.json │ │ ├── he_IL.json │ │ ├── it_IT.json │ │ ├── ja_JP.json │ │ ├── ko_KR.json │ │ ├── nb_NO.json │ │ ├── nn_NO.json │ │ ├── pt_BR.json │ │ ├── pt_PT.json │ │ ├── ru_RU.json │ │ ├── ta_IN.json │ │ ├── tk_TM.json │ │ ├── tr_TR.json │ │ ├── uk_UA.json │ │ ├── zh_CN.json │ │ └── zh_TW.json ├── main.jsx ├── services │ ├── collection │ │ ├── anki │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── eudic │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ └── index.jsx │ ├── recognize │ │ ├── baidu │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── baidu_accurate │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── baidu_img │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── iflytek │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── iflytek_intsig │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── iflytek_latex │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── index.jsx │ │ ├── qrcode │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── simple_latex │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── system │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── tencent │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── tencent_accurate │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── tencent_img │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── tesseract │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── volcengine │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ └── volcengine_multi_lang │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ ├── translate │ │ ├── alibaba │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── baidu │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── baidu_field │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── bing │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── bing_dict │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── caiyun │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── cambridge_dict │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── chatglm │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── deepl │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── ecdict │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── geminipro │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── google │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── index.jsx │ │ ├── lingva │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── niutrans │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── ollama │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── openai │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── tencent │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── transmart │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── volcengine │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ ├── yandex │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ │ └── youdao │ │ │ ├── Config.jsx │ │ │ ├── index.jsx │ │ │ └── info.ts │ └── tts │ │ ├── index.jsx │ │ └── lingva │ │ ├── Config.jsx │ │ ├── index.jsx │ │ └── info.ts ├── style.css ├── utils │ ├── env.js │ ├── index.js │ ├── invoke_plugin.js │ ├── lang_detect.js │ ├── language.ts │ ├── service_instance.ts │ └── store.js └── window │ ├── Config │ ├── components │ │ └── SideBar │ │ │ └── index.jsx │ ├── index.jsx │ ├── pages │ │ ├── About │ │ │ └── index.jsx │ │ ├── Backup │ │ │ ├── AliyunModal │ │ │ │ └── index.jsx │ │ │ ├── WebDavModal │ │ │ │ └── index.jsx │ │ │ ├── index.jsx │ │ │ └── utils │ │ │ │ ├── aliyun.jsx │ │ │ │ ├── local.jsx │ │ │ │ └── webdav.jsx │ │ ├── General │ │ │ └── index.jsx │ │ ├── History │ │ │ └── index.jsx │ │ ├── Hotkey │ │ │ └── index.jsx │ │ ├── Recognize │ │ │ └── index.jsx │ │ ├── Service │ │ │ ├── Collection │ │ │ │ ├── ConfigModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── SelectModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── ServiceItem │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ ├── PluginConfig │ │ │ │ └── index.jsx │ │ │ ├── Recognize │ │ │ │ ├── ConfigModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── SelectModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── ServiceItem │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ ├── SelectPluginModal │ │ │ │ └── index.jsx │ │ │ ├── Translate │ │ │ │ ├── ConfigModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── SelectModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── ServiceItem │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ ├── Tts │ │ │ │ ├── ConfigModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── SelectModal │ │ │ │ │ └── index.jsx │ │ │ │ ├── ServiceItem │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ └── Translate │ │ │ └── index.jsx │ ├── routes │ │ └── index.jsx │ └── style.css │ ├── Recognize │ ├── ControlArea │ │ └── index.jsx │ ├── ImageArea │ │ └── index.jsx │ ├── TextArea │ │ └── index.jsx │ └── index.jsx │ ├── Screenshot │ └── index.jsx │ ├── Translate │ ├── components │ │ ├── LanguageArea │ │ │ └── index.jsx │ │ ├── SourceArea │ │ │ └── index.jsx │ │ └── TargetArea │ │ │ └── index.jsx │ └── index.jsx │ └── Updater │ └── index.jsx ├── tailwind.config.cjs ├── updater ├── updater-for-fix-runtime.mjs └── updater.mjs └── vite.config.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://afdian.com/a/pylogmon'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug Report 2 | description: Report a bug 3 | title: '[BUG]: title' 4 | labels: ['type: bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | # ⬆️ Please give a concise and clear title ⬆️ 10 | ## Thank you for your feedback, pot has become better because of it! 11 | ### Please confirm the following before submitting a new issue. 12 | - I have carefully reviewed the official website's usage documentation. 13 | - I have searched through the historical issues but could not find an answer. 14 | - type: textarea 15 | attributes: 16 | label: Description 17 | description: Simply describe the problem. 18 | validations: 19 | required: true 20 | - type: textarea 21 | attributes: 22 | label: Reproduction 23 | description: Steps to reproduce the behaviour. 24 | validations: 25 | required: true 26 | - type: dropdown 27 | attributes: 28 | label: Platform 29 | options: 30 | - Windows 31 | - Linux 32 | - MacOS 33 | validations: 34 | required: true 35 | - type: input 36 | attributes: 37 | label: System Version 38 | placeholder: 'Eg: Windows 11 Home Edition 22621.1702' 39 | validations: 40 | required: true 41 | - type: dropdown 42 | attributes: 43 | label: Window System (Linux Only) 44 | options: 45 | - X11 46 | - Wayland 47 | - type: input 48 | attributes: 49 | label: Software Version 50 | placeholder: 'Eg: 2.0.0' 51 | validations: 52 | required: true 53 | - type: textarea 54 | attributes: 55 | label: Log File 56 | description: 'pot.log file content' 57 | - type: textarea 58 | attributes: 59 | label: Additional Information 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 🌟 Feature Request 2 | description: Suggest an idea 3 | title: '[Feature]: title' 4 | labels: ['type: enhancement'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | # ⬆️ Please give a concise and clear title ⬆️ 10 | ## Thank you for your feedback, pot has become better because of it! 11 | ### Please confirm the following before submitting a new issue. 12 | - I have carefully reviewed the official website's usage documentation. 13 | - I have searched through the historical issues but could not find an answer. 14 | - type: textarea 15 | attributes: 16 | label: Description 17 | description: Simply describe your idea. 18 | validations: 19 | required: true 20 | - type: textarea 21 | attributes: 22 | label: Application Scenario 23 | description: Why is there such a demand? 24 | validations: 25 | required: true 26 | - type: textarea 27 | attributes: 28 | label: References 29 | description: Provide implementation ideas or reference documents when necessary. 30 | -------------------------------------------------------------------------------- /.github/actions/build-for-linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:bullseye 2 | COPY entrypoint.sh /entrypoint.sh 3 | RUN chmod a+x /entrypoint.sh 4 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /.github/actions/build-for-linux/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Build for Linux' 2 | branding: 3 | icon: user-check 4 | color: gray-dark 5 | inputs: 6 | target: 7 | required: true 8 | description: 'Rust Target' 9 | toolchain: 10 | required: true 11 | description: 'Rust Toolchain' 12 | runs: 13 | using: 'docker' 14 | image: 'Dockerfile' 15 | args: 16 | - ${{ inputs.target }} 17 | - ${{ inputs.toolchain }} 18 | -------------------------------------------------------------------------------- /.github/actions/build-for-linux/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://nodejs.org/dist/v19.8.1/node-v19.8.1-linux-x64.tar.xz 4 | tar -Jxvf ./node-v19.8.1-linux-x64.tar.xz 5 | export PATH=$(pwd)/node-v19.8.1-linux-x64/bin:$PATH 6 | npm install pnpm -g 7 | 8 | rustup target add "$INPUT_TARGET" 9 | rustup toolchain install --force-non-host "$INPUT_TOOLCHAIN" 10 | 11 | if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then 12 | apt-get update 13 | apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf libxdo-dev libxcb1 libxrandr2 libdbus-1-3 14 | elif [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then 15 | dpkg --add-architecture i386 16 | apt-get update 17 | apt-get install -y libstdc++6:i386 libgdk-pixbuf2.0-dev:i386 libatomic1:i386 gcc-multilib g++-multilib libwebkit2gtk-4.0-dev:i386 libssl-dev:i386 libgtk-3-dev:i386 librsvg2-dev:i386 patchelf:i386 libxdo-dev:i386 libxcb1:i386 libxrandr2:i386 libdbus-1-3:i386 libayatana-appindicator3-dev:i386 18 | export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH 19 | export PKG_CONFIG_SYSROOT_DIR=/ 20 | elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then 21 | dpkg --add-architecture arm64 22 | apt-get update 23 | apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libcups2:arm64 24 | apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libssl-dev:arm64 libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libxdo-dev:arm64 libxcb1:arm64 libxrandr2:arm64 libdbus-1-3:arm64 libayatana-appindicator3-dev:arm64 25 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 26 | export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc 27 | export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ 28 | export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig 29 | export PKG_CONFIG_ALLOW_CROSS=1 30 | elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then 31 | dpkg --add-architecture armhf 32 | apt-get update 33 | apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libcups2:armhf 34 | apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libssl-dev:armhf libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libxdo-dev:armhf libxcb1:armhf libxrandr2:armhf libdbus-1-3:armhf libayatana-appindicator3-dev:armhf 35 | export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc 36 | export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc 37 | export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ 38 | export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig 39 | export PKG_CONFIG_ALLOW_CROSS=1 40 | else 41 | echo "Unknown target: $INPUT_TARGET" && exit 1 42 | fi 43 | 44 | bash .github/actions/build.sh -------------------------------------------------------------------------------- /.github/actions/build.sh: -------------------------------------------------------------------------------- 1 | # pnpm install --resolution-only 2 | pnpm install 3 | sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml 4 | if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then 5 | pnpm tauri build --target $INPUT_TARGET 6 | else 7 | pnpm tauri build --target $INPUT_TARGET -b deb rpm 8 | fi 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 21 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | asset 2 | dist 3 | node_modules 4 | public 5 | target 6 | *-lock* 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": true, 9 | "printWidth": 120, 10 | "proseWrap": "preserve", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleAttributePerLine": true, 15 | "singleQuote": true, 16 | "tabWidth": 4, 17 | "trailingComma": "es5", 18 | "useTabs": false, 19 | "vueIndentScriptAndStyle": false, 20 | "endOfLine": "lf" 21 | } 22 | -------------------------------------------------------------------------------- /.scripts/popclip/Config.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Actions 6 | 7 | 8 | Shell Script File 9 | Pot.sh 10 | Image File 11 | Pot.png 12 | Title 13 | Pot Translate 14 | 15 | 16 | Credits 17 | 18 | 19 | Link 20 | https://pot-app.com/ 21 | Name 22 | pot-app 23 | 24 | 25 | Extension Description 26 | Translate text with Pot. 27 | Extension Identifier 28 | com.pot-app.popclip.extension.desktop 29 | Extension Image File 30 | Pot.png 31 | Extension Name 32 | Pot 33 | Required OS Version 34 | 10.13 35 | 36 | -------------------------------------------------------------------------------- /.scripts/popclip/Pot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/.scripts/popclip/Pot.png -------------------------------------------------------------------------------- /.scripts/popclip/Pot.sh: -------------------------------------------------------------------------------- 1 | curl -Lsd "$POPCLIP_TEXT" "127.0.0.1:60828" 2 | 3 | if [ $? -eq 0 ]; then 4 | exit 0 5 | else 6 | open -g -a pot 7 | sleep 2 8 | curl -Lsd "$POPCLIP_TEXT" "127.0.0.1:60828/translate" 9 | fi -------------------------------------------------------------------------------- /.scripts/popclip/build.sh: -------------------------------------------------------------------------------- 1 | rm Pot.popclipextz 2 | mkdir Pot.popclipext 3 | cp Config.plist Pot.popclipext 4 | cp Pot.png Pot.popclipext 5 | cp Pot.sh Pot.popclipext 6 | zip -r Pot.popclipextz Pot.popclipext 7 | rm -r Pot.popclipext 8 | -------------------------------------------------------------------------------- /.scripts/snipdo/build.sh: -------------------------------------------------------------------------------- 1 | zip pot.pbar pot.json pot.png pot.ps1 -------------------------------------------------------------------------------- /.scripts/snipdo/pot.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pot Translation", 3 | "identifier": "com.pot-app.snipdoext.desktop", 4 | "icon": "pot.png", 5 | "actions": [ 6 | { 7 | "title": "Pot Translation", 8 | "icon": "pot.png", 9 | "powershellFile": "pot.ps1" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.scripts/snipdo/pot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/.scripts/snipdo/pot.png -------------------------------------------------------------------------------- /.scripts/snipdo/pot.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$PLAIN_TEXT 3 | ) 4 | 5 | $encode_text = [System.Text.Encoding]::UTF8.GetBytes($PLAIN_TEXT) 6 | 7 | curl 127.0.0.1:60828/translate -Method POST -Body $encode_text -UseBasicParsing 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", "vadimcn.vscode-lldb"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Tauri Development Debug", 8 | "cargo": { 9 | "args": ["build", "--manifest-path=./src-tauri/Cargo.toml", "--no-default-features"] 10 | }, 11 | "preLaunchTask": "ui:dev" 12 | }, 13 | { 14 | "type": "lldb", 15 | "request": "launch", 16 | "name": "Tauri Production Debug", 17 | "cargo": { 18 | "args": ["build", "--release", "--manifest-path=./src-tauri/Cargo.toml"] 19 | }, 20 | "preLaunchTask": "ui:build" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "tauri:dev", 6 | "type": "shell", 7 | "command": "pnpm", 8 | "args": ["tauri", "dev"] 9 | }, 10 | { 11 | "label": "tauri:build", 12 | "type": "shell", 13 | "command": "pnpm", 14 | "args": ["tauri", "build"] 15 | }, 16 | { 17 | "label": "ui:dev", 18 | "type": "shell", 19 | "isBackground": true, 20 | "command": "pnpm", 21 | "args": ["dev"] 22 | }, 23 | { 24 | "label": "ui:build", 25 | "type": "shell", 26 | "command": "pnpm", 27 | "args": ["build"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | ## 3.0.7 (2025-5-10) 2 | 3 | ### New feature: 4 | 5 | - signed macOS app 6 | 7 | ### Bugs fixed: 8 | 9 | - fix screenshot on macOS 10 | - rm tray click event on macOS 11 | -------------------------------------------------------------------------------- /asset/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/1.png -------------------------------------------------------------------------------- /asset/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/2.png -------------------------------------------------------------------------------- /asset/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/3.png -------------------------------------------------------------------------------- /asset/eg1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg1.gif -------------------------------------------------------------------------------- /asset/eg2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg2.gif -------------------------------------------------------------------------------- /asset/eg3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg3.gif -------------------------------------------------------------------------------- /asset/eg4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg4.gif -------------------------------------------------------------------------------- /asset/eg5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg5.gif -------------------------------------------------------------------------------- /asset/eg6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/eg6.gif -------------------------------------------------------------------------------- /asset/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/asset/header.png -------------------------------------------------------------------------------- /daemon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | Pot 11 | 12 | 13 | 14 |
Daemon
15 | 16 | 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 14 | 17 | Pot 18 | 19 | 20 | 21 |
22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pot", 3 | "private": true, 4 | "version": "3.0.7", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri", 11 | "updater": "node updater/updater.mjs", 12 | "updater:fixRuntime": "node updater/updater-for-fix-runtime.mjs" 13 | }, 14 | "dependencies": { 15 | "@nextui-org/react": "^2.4.8", 16 | "@nextui-org/theme": "^2.2.11", 17 | "@react-spring/web": "^9.7.5", 18 | "@tauri-apps/api": "^1.6.0", 19 | "crypto-js": "^4.2.0", 20 | "flag-icons": "^7.2.3", 21 | "framer-motion": "^11.11.10", 22 | "i18next": "^23.16.4", 23 | "jose": "^5.9.6", 24 | "jotai": "^2.10.1", 25 | "jsqr": "^1.4.0", 26 | "md5": "^2.3.0", 27 | "nanoid": "^5.0.8", 28 | "next-themes": "^0.3.0", 29 | "ollama": "^0.5.9", 30 | "react": "^18.3.1", 31 | "react-beautiful-dnd": "^13.1.1", 32 | "react-dom": "^18.3.1", 33 | "react-hot-toast": "^2.4.1", 34 | "react-i18next": "^15.1.0", 35 | "react-icons": "^5.3.0", 36 | "react-markdown": "^9.0.1", 37 | "react-router-dom": "^6.27.0", 38 | "react-spinners": "^0.14.1", 39 | "react-use-measure": "^2.1.1", 40 | "tauri-plugin-autostart-api": "github:tauri-apps/tauri-plugin-autostart#v1", 41 | "tauri-plugin-fs-watch-api": "github:tauri-apps/tauri-plugin-fs-watch#v1", 42 | "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1", 43 | "tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1", 44 | "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1", 45 | "tesseract.js": "^5.1.1", 46 | "uuid": "^11.0.2" 47 | }, 48 | "devDependencies": { 49 | "@tauri-apps/cli": "^1.6.3", 50 | "@vitejs/plugin-react": "^4.3.3", 51 | "autoprefixer": "^10.4.20", 52 | "node-fetch": "^3.3.2", 53 | "postcss": "^8.4.47", 54 | "prettier": "3.3.2", 55 | "tailwindcss": "^3.4.14", 56 | "typescript": "^5.6.3", 57 | "vite": "^5.4.10" 58 | } 59 | } -------------------------------------------------------------------------------- /patches/hyprland.patch: -------------------------------------------------------------------------------- 1 | From ef74289f660bcb9d407056a42a045804f6b8ecbf Mon Sep 17 00:00:00 2001 2 | From: Pylogmon 3 | Date: Wed, 22 Nov 2023 10:31:59 +0800 4 | Subject: [PATCH] fix: Patch for Hyprland 5 | 6 | #596 7 | --- 8 | src/window/Translate/index.jsx | 14 +++++++------- 9 | 1 file changed, 7 insertions(+), 7 deletions(-) 10 | 11 | diff --git a/src/window/Translate/index.jsx b/src/window/Translate/index.jsx 12 | index ddbf885..04579a7 100644 13 | --- a/src/window/Translate/index.jsx 14 | +++ b/src/window/Translate/index.jsx 15 | @@ -54,13 +54,13 @@ void listen('tauri://focus', () => { 16 | } 17 | }); 18 | // 监听 move 事件取消 blurTimeout 时间之内的关闭窗口 19 | -void listen('tauri://move', () => { 20 | - info('Move'); 21 | - if (blurTimeout) { 22 | - info('Cancel Close'); 23 | - clearTimeout(blurTimeout); 24 | - } 25 | -}); 26 | +// void listen('tauri://move', () => { 27 | +// info('Move'); 28 | +// if (blurTimeout) { 29 | +// info('Cancel Close'); 30 | +// clearTimeout(blurTimeout); 31 | +// } 32 | +// }); 33 | 34 | export default function Translate() { 35 | const [closeOnBlur] = useConfig('translate_close_on_blur', true); 36 | -- 37 | 2.43.0 38 | 39 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/icon.png -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/logo/Darwin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/logo/Windows_NT.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/logo/alibaba.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34 | 35 | -------------------------------------------------------------------------------- /public/logo/anki.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/baidu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/bing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/chatglm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/chatglm.png -------------------------------------------------------------------------------- /public/logo/deepl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/logo/ecdict.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/eudic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/eudic.png -------------------------------------------------------------------------------- /public/logo/geminipro.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/geminipro.webp -------------------------------------------------------------------------------- /public/logo/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/iflytek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/iflytek.png -------------------------------------------------------------------------------- /public/logo/ollama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/ollama.png -------------------------------------------------------------------------------- /public/logo/openai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/paddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/paddle.png -------------------------------------------------------------------------------- /public/logo/qrcode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo/simple_latex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/simple_latex.png -------------------------------------------------------------------------------- /public/logo/tencent_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/tencent_cloud.png -------------------------------------------------------------------------------- /public/logo/tesseract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/public/logo/tesseract.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pot" 3 | version = "0.0.0" 4 | description = "Pot App" 5 | authors = ["pot-app"] 6 | license = "GPL-3.0-only" 7 | repository = "https://github.com/pot-app/pot-desktop" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [build-dependencies] 13 | tauri-build = { version = "1.5", features = [] } 14 | 15 | [dependencies] 16 | 17 | tauri = { version = "1.8", features = [ "dialog-save", "dialog-open", "fs-all", "protocol-asset", "shell-all", "clipboard-all", "os-all", "http-all", "http-multipart", "updater", "notification-all", "global-shortcut-all", "window-all", "path-all", "system-tray", "devtools"] } 18 | tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 19 | tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 20 | tauri-plugin-fs-watch = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 21 | tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 22 | tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 23 | tauri-plugin-sql = { git= "https://github.com/tauri-apps/plugins-workspace", branch = "v1",features = ["sqlite"] } 24 | serde = { version = "1.0", features = ["derive"] } 25 | selection = "1.2.0" 26 | serde_json = "1.0" 27 | dirs = "5.0.1" 28 | once_cell = "1.19.0" 29 | mouse_position = "0.1.4" 30 | log = "0.4" 31 | tiny_http = "0.12.0" 32 | screenshots = "=0.7.2" 33 | base64 = "0.22" 34 | arboard = "3.4" 35 | lingua = { version = "1.6.2", default-features = false, features = ["chinese", "japanese", "english", "korean", "french", "spanish", "german", "russian", "italian", "portuguese", "turkish", "arabic", "vietnamese", "thai", "indonesian", "malay", "hindi", "mongolian", "persian", "nynorsk", "bokmal", "ukrainian"] } 36 | reqwest = { version = "0.12", features = ["json"] } 37 | reqwest_dav = "=0.1.5" 38 | zip = "2.2.0" 39 | walkdir = "2.5" 40 | thiserror = "1.0" 41 | font-kit = "0.14.2" 42 | image = "0.25.4" 43 | 44 | [target.'cfg(target_os = "macos")'.dependencies] 45 | macos-accessibility-client = "0.0.1" 46 | window-shadows = "0.2" 47 | 48 | [target.'cfg(windows)'.dependencies] 49 | windows = {version="0.58.0",features= ["Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Graphics_Imaging", "Media_Ocr", "Foundation", "Globalization", "Storage", "Storage_Streams"] } 50 | window-shadows = "0.2" 51 | 52 | [target.'cfg(target_os = "linux")'.dependencies] 53 | #openssl 54 | 55 | [features] 56 | # this feature is used for production builds or when `devPath` points to the filesystem 57 | # DO NOT REMOVE!! 58 | custom-protocol = ["tauri/custom-protocol"] 59 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/icon_mac.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons/icon_mac.ico -------------------------------------------------------------------------------- /src-tauri/icons_mac/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons_mac/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons_mac/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/icon.png -------------------------------------------------------------------------------- /src-tauri/icons_mac/tray.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/icons_mac/tray.ico -------------------------------------------------------------------------------- /src-tauri/resources/ocr-aarch64-apple-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/resources/ocr-aarch64-apple-darwin -------------------------------------------------------------------------------- /src-tauri/resources/ocr-x86_64-apple-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pot-app/pot-desktop/d05a436c9f426a94c38e176f096aeca04c3bf06a/src-tauri/resources/ocr-x86_64-apple-darwin -------------------------------------------------------------------------------- /src-tauri/src/clipboard.rs: -------------------------------------------------------------------------------- 1 | use crate::window::text_translate; 2 | use std::sync::Mutex; 3 | use tauri::{ClipboardManager, Manager}; 4 | 5 | pub struct ClipboardMonitorEnableWrapper(pub Mutex); 6 | 7 | pub fn start_clipboard_monitor(app_handle: tauri::AppHandle) { 8 | tauri::async_runtime::spawn(async move { 9 | let mut pre_text = "".to_string(); 10 | loop { 11 | let handle = app_handle.app_handle(); 12 | let state = handle.state::(); 13 | if let Ok(clipboard_monitor) = state.0.try_lock() { 14 | if clipboard_monitor.contains("true") { 15 | if let Ok(result) = app_handle.clipboard_manager().read_text() { 16 | match result { 17 | Some(v) => { 18 | if v != pre_text { 19 | text_translate(v.clone()); 20 | pre_text = v; 21 | } 22 | } 23 | None => {} 24 | } 25 | } 26 | } else { 27 | break; 28 | } 29 | } 30 | std::thread::sleep(std::time::Duration::from_millis(500)); 31 | } 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src-tauri/src/error.rs: -------------------------------------------------------------------------------- 1 | // create the error type that represents all errors possible in our program 2 | #[derive(Debug, thiserror::Error)] 3 | pub enum Error { 4 | #[error(transparent)] 5 | Io(#[from] std::io::Error), 6 | #[error(transparent)] 7 | Error(#[from] Box), 8 | #[error(transparent)] 9 | Dav(#[from] reqwest_dav::Error), 10 | #[error(transparent)] 11 | DavRe(#[from] reqwest_dav::re_exports::reqwest::Error), 12 | #[error(transparent)] 13 | Serde(#[from] serde_json::Error), 14 | #[error(transparent)] 15 | Zip(#[from] zip::result::ZipError), 16 | #[error(transparent)] 17 | WalkDir(#[from] walkdir::Error), 18 | #[error(transparent)] 19 | Tauri(#[from] tauri::Error), 20 | #[error(transparent)] 21 | StripPrefix(#[from] std::path::StripPrefixError), 22 | #[error(transparent)] 23 | Arboard(#[from] arboard::Error), 24 | #[error(transparent)] 25 | Image(#[from] image::ImageError), 26 | #[error(transparent)] 27 | Selection(#[from] font_kit::error::SelectionError), 28 | #[error(transparent)] 29 | Reqwest(#[from] reqwest::Error), 30 | } 31 | 32 | // we must manually implement serde::Serialize 33 | impl serde::Serialize for Error { 34 | fn serialize(&self, serializer: S) -> Result 35 | where 36 | S: serde::ser::Serializer, 37 | { 38 | serializer.serialize_str(self.to_string().as_ref()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src-tauri/src/lang_detect.rs: -------------------------------------------------------------------------------- 1 | pub fn init_lang_detect() { 2 | // https://crates.io/crates/lingua 3 | use lingua::{Language, LanguageDetectorBuilder}; 4 | let languages = vec![ 5 | Language::Chinese, 6 | Language::Japanese, 7 | Language::English, 8 | Language::Korean, 9 | Language::French, 10 | Language::Spanish, 11 | Language::German, 12 | Language::Russian, 13 | Language::Italian, 14 | Language::Portuguese, 15 | Language::Turkish, 16 | Language::Arabic, 17 | Language::Vietnamese, 18 | Language::Thai, 19 | Language::Indonesian, 20 | Language::Malay, 21 | Language::Hindi, 22 | Language::Mongolian, 23 | Language::Bokmal, 24 | Language::Nynorsk, 25 | Language::Persian, 26 | Language::Ukrainian, 27 | ]; 28 | let detector = LanguageDetectorBuilder::from_languages(&languages).build(); 29 | let _ = detector.detect_language_of("Hello Language"); 30 | } 31 | #[tauri::command] 32 | pub fn lang_detect(text: &str) -> Result<&str, ()> { 33 | use lingua::{Language, LanguageDetectorBuilder}; 34 | let languages = vec![ 35 | Language::Chinese, 36 | Language::Japanese, 37 | Language::English, 38 | Language::Korean, 39 | Language::French, 40 | Language::Spanish, 41 | Language::German, 42 | Language::Russian, 43 | Language::Italian, 44 | Language::Portuguese, 45 | Language::Turkish, 46 | Language::Arabic, 47 | Language::Vietnamese, 48 | Language::Thai, 49 | Language::Indonesian, 50 | Language::Malay, 51 | Language::Hindi, 52 | Language::Mongolian, 53 | Language::Bokmal, 54 | Language::Nynorsk, 55 | Language::Persian, 56 | ]; 57 | let detector = LanguageDetectorBuilder::from_languages(&languages).build(); 58 | if let Some(lang) = detector.detect_language_of(text) { 59 | match lang { 60 | Language::Chinese => Ok("zh_cn"), 61 | Language::Japanese => Ok("ja"), 62 | Language::English => Ok("en"), 63 | Language::Korean => Ok("ko"), 64 | Language::French => Ok("fr"), 65 | Language::Spanish => Ok("es"), 66 | Language::German => Ok("de"), 67 | Language::Russian => Ok("ru"), 68 | Language::Italian => Ok("it"), 69 | Language::Portuguese => Ok("pt_pt"), 70 | Language::Turkish => Ok("tr"), 71 | Language::Arabic => Ok("ar"), 72 | Language::Vietnamese => Ok("vi"), 73 | Language::Thai => Ok("th"), 74 | Language::Indonesian => Ok("id"), 75 | Language::Malay => Ok("ms"), 76 | Language::Hindi => Ok("hi"), 77 | Language::Mongolian => Ok("mn_cy"), 78 | Language::Bokmal => Ok("nb_no"), 79 | Language::Nynorsk => Ok("nn_no"), 80 | Language::Persian => Ok("fa"), 81 | Language::Ukrainian => Ok("uk"), 82 | } 83 | } else { 84 | return Ok("en"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src-tauri/src/screenshot.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | 3 | #[tauri::command] 4 | pub fn screenshot(x: i32, y: i32) { 5 | use crate::APP; 6 | use dirs::cache_dir; 7 | use screenshots::{Compression, Screen}; 8 | use std::fs; 9 | info!("Screenshot screen with position: x={}, y={}", x, y); 10 | let screens = Screen::all().unwrap(); 11 | for screen in screens { 12 | let info = screen.display_info; 13 | info!("Screen: {:?}", info); 14 | if info.x == x && info.y == y { 15 | let handle = APP.get().unwrap(); 16 | let mut app_cache_dir_path = cache_dir().expect("Get Cache Dir Failed"); 17 | app_cache_dir_path.push(&handle.config().tauri.bundle.identifier); 18 | if !app_cache_dir_path.exists() { 19 | // 创建目录 20 | fs::create_dir_all(&app_cache_dir_path).expect("Create Cache Dir Failed"); 21 | } 22 | app_cache_dir_path.push("pot_screenshot.png"); 23 | 24 | let image = screen.capture().unwrap(); 25 | let buffer = image.to_png(Compression::Fast).unwrap(); 26 | fs::write(app_cache_dir_path, buffer).unwrap(); 27 | break; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src-tauri/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{get, set}; 2 | use crate::window::*; 3 | use log::{info, warn}; 4 | use std::thread; 5 | use tauri::api::notification; 6 | use tiny_http::{Request, Response, Server}; 7 | 8 | pub fn start_server() { 9 | let port = match get("server_port") { 10 | Some(v) => v.as_i64().unwrap(), 11 | None => { 12 | set("server_port", 60828); 13 | 60828 14 | } 15 | }; 16 | thread::spawn(move || { 17 | let server = match Server::http(format!("127.0.0.1:{port}")) { 18 | Ok(v) => v, 19 | Err(e) => { 20 | let _ = notification::Notification::new("com.pot-spp.com") 21 | .title("Server start failed") 22 | .body("Please Change Server Port and restart the application") 23 | .show(); 24 | warn!("Server start failed: {}", e); 25 | return; 26 | } 27 | }; 28 | for request in server.incoming_requests() { 29 | http_handle(request); 30 | } 31 | }); 32 | } 33 | 34 | fn http_handle(request: Request) { 35 | info!("Handle {} request", request.url()); 36 | match request.url() { 37 | "/" => handle_translate(request), 38 | "/config" => handle_config(request), 39 | "/translate" => handle_translate(request), 40 | "/selection_translate" => handle_selection_translate(request), 41 | "/input_translate" => handle_input_translate(request), 42 | "/ocr_recognize" => handle_ocr_recognize(request), 43 | "/ocr_translate" => handle_ocr_translate(request), 44 | "/ocr_recognize?screenshot=false" => handle_ocr_recognize(request), 45 | "/ocr_translate?screenshot=false" => handle_ocr_translate(request), 46 | "/ocr_recognize?screenshot=true" => handle_ocr_recognize(request), 47 | "/ocr_translate?screenshot=true" => handle_ocr_translate(request), 48 | _ => warn!("Unknown request url: {}", request.url()), 49 | } 50 | } 51 | 52 | fn handle_config(request: Request) { 53 | config_window(); 54 | response_ok(request); 55 | } 56 | 57 | fn handle_translate(mut request: Request) { 58 | let mut content = String::new(); 59 | request.as_reader().read_to_string(&mut content).unwrap(); 60 | text_translate(content); 61 | response_ok(request); 62 | } 63 | 64 | fn handle_selection_translate(request: Request) { 65 | selection_translate(); 66 | response_ok(request); 67 | } 68 | 69 | fn handle_input_translate(request: Request) { 70 | input_translate(); 71 | response_ok(request); 72 | } 73 | 74 | fn handle_ocr_recognize(request: Request) { 75 | if request.url().ends_with("false") { 76 | recognize_window(); 77 | } else { 78 | ocr_recognize(); 79 | } 80 | response_ok(request); 81 | } 82 | 83 | fn handle_ocr_translate(request: Request) { 84 | if request.url().ends_with("false") { 85 | image_translate(); 86 | } else { 87 | ocr_translate(); 88 | } 89 | response_ok(request); 90 | } 91 | 92 | fn response_ok(request: Request) { 93 | let response = Response::from_string("ok"); 94 | request.respond(response).unwrap(); 95 | } 96 | -------------------------------------------------------------------------------- /src-tauri/src/updater.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{get, set}; 2 | use crate::window::updater_window; 3 | use log::{info, warn}; 4 | 5 | pub fn check_update(app_handle: tauri::AppHandle) { 6 | let enable = match get("check_update") { 7 | Some(v) => v.as_bool().unwrap(), 8 | None => { 9 | set("check_update", true); 10 | true 11 | } 12 | }; 13 | if enable { 14 | tauri::async_runtime::spawn(async move { 15 | match tauri::updater::builder(app_handle).check().await { 16 | Ok(update) => { 17 | if update.is_update_available() { 18 | info!("New version available"); 19 | updater_window(); 20 | } 21 | } 22 | Err(e) => { 23 | warn!("Failed to check update: {}", e); 24 | } 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src-tauri/tauri.linux.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons/icon.ico", 5 | "iconAsTemplate": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src-tauri/tauri.macos.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons_mac/tray.ico", 5 | "iconAsTemplate": true 6 | }, 7 | "bundle": { 8 | "resources": [ 9 | "resources/*" 10 | ], 11 | "icon": [ 12 | "icons_mac/32x32.png", 13 | "icons_mac/128x128.png", 14 | "icons_mac/128x128@2x.png", 15 | "icons_mac/icon.icns", 16 | "icons_mac/icon.ico" 17 | ] 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src-tauri/tauri.windows.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons/icon.ico", 5 | "iconAsTemplate": true 6 | }, 7 | "bundle": { 8 | "windows": { 9 | "certificateThumbprint": null, 10 | "digestAlgorithm": "sha256", 11 | "timestampUrl": "", 12 | "webviewInstallMode": { 13 | "silent": true, 14 | "type": "embedBootstrapper" 15 | }, 16 | "nsis": { 17 | "displayLanguageSelector": true, 18 | "installerIcon": "icons/icon.ico", 19 | "license": "../LICENSE", 20 | "installMode": "perMachine", 21 | "languages": ["SimpChinese", "TradChinese", "English"] 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src-tauri/webview.arm64.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons/icon.ico", 5 | "iconAsTemplate": true 6 | }, 7 | "bundle": { 8 | "windows": { 9 | "certificateThumbprint": null, 10 | "digestAlgorithm": "sha256", 11 | "timestampUrl": "", 12 | "webviewInstallMode": { 13 | "type": "fixedRuntime", 14 | "path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.arm64/" 15 | }, 16 | "nsis": { 17 | "displayLanguageSelector": true, 18 | "installerIcon": "icons/icon.ico", 19 | "license": "../LICENSE", 20 | "installMode": "perMachine", 21 | "languages": [ 22 | "SimpChinese", 23 | "TradChinese", 24 | "English" 25 | ] 26 | } 27 | } 28 | }, 29 | "updater": { 30 | "active": true, 31 | "dialog": false, 32 | "endpoints": [ 33 | "https://dl.pot-app.com/https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json", 34 | "https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json" 35 | ], 36 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVBRTQxQTNDQjM5QzQzM0EKUldRNlE1eXpQQnJrV21mM1Bram5LRlF6UDA3K0Jab2FYL2lZSWhXTE5McWs2NUdJS0dtYkd5VGMK" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src-tauri/webview.x64.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons/icon.ico", 5 | "iconAsTemplate": true 6 | }, 7 | "bundle": { 8 | "windows": { 9 | "certificateThumbprint": null, 10 | "digestAlgorithm": "sha256", 11 | "timestampUrl": "", 12 | "webviewInstallMode": { 13 | "type": "fixedRuntime", 14 | "path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x64/" 15 | }, 16 | "nsis": { 17 | "displayLanguageSelector": true, 18 | "installerIcon": "icons/icon.ico", 19 | "license": "../LICENSE", 20 | "installMode": "perMachine", 21 | "languages": [ 22 | "SimpChinese", 23 | "TradChinese", 24 | "English" 25 | ] 26 | } 27 | } 28 | }, 29 | "updater": { 30 | "active": true, 31 | "dialog": false, 32 | "endpoints": [ 33 | "https://dl.pot-app.com/https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json", 34 | "https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json" 35 | ], 36 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVBRTQxQTNDQjM5QzQzM0EKUldRNlE1eXpQQnJrV21mM1Bram5LRlF6UDA3K0Jab2FYL2lZSWhXTE5McWs2NUdJS0dtYkd5VGMK" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src-tauri/webview.x86.json: -------------------------------------------------------------------------------- 1 | { 2 | "tauri": { 3 | "systemTray": { 4 | "iconPath": "icons/icon.ico", 5 | "iconAsTemplate": true 6 | }, 7 | "bundle": { 8 | "windows": { 9 | "certificateThumbprint": null, 10 | "digestAlgorithm": "sha256", 11 | "timestampUrl": "", 12 | "webviewInstallMode": { 13 | "type": "fixedRuntime", 14 | "path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x86/" 15 | }, 16 | "nsis": { 17 | "displayLanguageSelector": true, 18 | "installerIcon": "icons/icon.ico", 19 | "license": "../LICENSE", 20 | "installMode": "perMachine", 21 | "languages": [ 22 | "SimpChinese", 23 | "TradChinese", 24 | "English" 25 | ] 26 | } 27 | } 28 | }, 29 | "updater": { 30 | "active": true, 31 | "dialog": false, 32 | "endpoints": [ 33 | "https://dl.pot-app.com/https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json", 34 | "https://github.com/pot-app/pot-desktop/releases/download/updater/update-fix-runtime.json" 35 | ], 36 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVBRTQxQTNDQjM5QzQzM0EKUldRNlE1eXpQQnJrV21mM1Bram5LRlF6UDA3K0Jab2FYL2lZSWhXTE5McWs2NUdJS0dtYkd5VGMK" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/components/WindowControl/index.jsx: -------------------------------------------------------------------------------- 1 | import { VscChromeClose, VscChromeMinimize, VscChromeMaximize, VscChromeRestore } from 'react-icons/vsc'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { appWindow } from '@tauri-apps/api/window'; 4 | import { listen } from '@tauri-apps/api/event'; 5 | import { Button } from '@nextui-org/react'; 6 | 7 | import { osType } from '../../utils/env'; 8 | import './style.css'; 9 | 10 | export default function WindowControl() { 11 | const [isMax, setIsMax] = useState(false); 12 | 13 | useEffect(() => { 14 | listen('tauri://resize', async () => { 15 | if (await appWindow.isMaximized()) { 16 | setIsMax(true); 17 | } else { 18 | setIsMax(false); 19 | } 20 | }); 21 | }, []); 22 | 23 | return ( 24 |
25 | 33 | 47 | 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/WindowControl/style.css: -------------------------------------------------------------------------------- 1 | .close-button:hover { 2 | background-color: #c42b1c !important; 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/index.jsx: -------------------------------------------------------------------------------- 1 | export * from './useConfig'; 2 | export * from './useToastStyle'; 3 | export * from './useSyncAtom'; 4 | export * from './useVoice'; 5 | -------------------------------------------------------------------------------- /src/hooks/useConfig.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from 'react'; 2 | import { listen, emit } from '@tauri-apps/api/event'; 3 | import { useGetState } from './useGetState'; 4 | import { store } from '../utils/store'; 5 | import { debounce } from '../utils'; 6 | 7 | export const useConfig = (key, defaultValue, options = {}) => { 8 | const [property, setPropertyState, getProperty] = useGetState(null); 9 | const { sync = true } = options; 10 | 11 | // 同步到Store (State -> Store) 12 | const syncToStore = useCallback( 13 | debounce((v) => { 14 | store.set(key, v); 15 | store.save(); 16 | let eventKey = key.replaceAll('.', '_').replaceAll('@', ':'); 17 | emit(`${eventKey}_changed`, v); 18 | }), 19 | [] 20 | ); 21 | 22 | // 同步到State (Store -> State) 23 | const syncToState = useCallback((v) => { 24 | if (v !== null) { 25 | setPropertyState(v); 26 | } else { 27 | store.get(key).then((v) => { 28 | if (v === null) { 29 | setPropertyState(defaultValue); 30 | store.set(key, defaultValue); 31 | store.save(); 32 | } else { 33 | setPropertyState(v); 34 | } 35 | }); 36 | } 37 | }, []); 38 | 39 | const setProperty = useCallback((v, forceSync = false) => { 40 | setPropertyState(v); 41 | const isSync = forceSync || sync; 42 | isSync && syncToStore(v); 43 | }, []); 44 | 45 | // 初始化 46 | useEffect(() => { 47 | syncToState(null); 48 | const eventKey = key.replaceAll('.', '_').replaceAll('@', ':'); 49 | const unlisten = listen(`${eventKey}_changed`, (e) => { 50 | syncToState(e.payload); 51 | }); 52 | return () => { 53 | unlisten.then((f) => { 54 | f(); 55 | }); 56 | }; 57 | }, []); 58 | 59 | return [property, setProperty, getProperty]; 60 | }; 61 | 62 | export const deleteKey = (key) => { 63 | if (store.has(key)) { 64 | store.delete(key); 65 | store.save(); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/hooks/useGetState.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useCallback } from 'react'; 2 | 3 | export const useGetState = (initState) => { 4 | const [state, setState] = useState(initState); 5 | const stateRef = useRef(state); 6 | stateRef.current = state; 7 | const getState = useCallback(() => stateRef.current, []); 8 | return [state, setState, getState]; 9 | }; 10 | -------------------------------------------------------------------------------- /src/hooks/useSyncAtom.jsx: -------------------------------------------------------------------------------- 1 | import { useAtom } from 'jotai'; 2 | 3 | import { useGetState } from './useGetState'; 4 | 5 | export const useSyncAtom = (atom) => { 6 | const [atomValue, setAtomValue] = useAtom(atom); 7 | const [localValue, setLocalValue, getLocalValue] = useGetState(atomValue); 8 | 9 | const syncAtom = () => setAtomValue(getLocalValue()); 10 | 11 | return [localValue, setLocalValue, syncAtom]; 12 | }; 13 | -------------------------------------------------------------------------------- /src/hooks/useToastStyle.jsx: -------------------------------------------------------------------------------- 1 | import { semanticColors } from '@nextui-org/theme'; 2 | import { useTheme } from 'next-themes'; 3 | 4 | export const useToastStyle = () => { 5 | const { theme } = useTheme(); 6 | const toastStyle = { 7 | background: theme == 'dark' ? semanticColors.dark.content1.DEFAULT : semanticColors.light.content1.DEFAULT, 8 | color: theme == 'dark' ? semanticColors.dark.foreground.DEFAULT : semanticColors.light.foreground.DEFAULT, 9 | wordBreak: 'break-all', 10 | select: 'text', 11 | }; 12 | 13 | return toastStyle; 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/useVoice.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | let audioContext = new (window.AudioContext || window.webkitAudioContext)(); 3 | let source = null; 4 | 5 | export const useVoice = () => { 6 | const playOrStop = useCallback((data) => { 7 | if (source) { 8 | // 如果正在播放,停止播放 9 | source.stop(); 10 | source.disconnect(); 11 | source = null; 12 | } else { 13 | // 如果没在播放,开始播放 14 | audioContext.decodeAudioData(new Uint8Array(data).buffer, (buffer) => { 15 | source = audioContext.createBufferSource(); 16 | source.buffer = buffer; 17 | source.connect(audioContext.destination); 18 | source.start(); 19 | source.onended = () => { 20 | source.disconnect(); 21 | source = null; 22 | }; 23 | }); 24 | } 25 | }); 26 | 27 | return playOrStop; 28 | }; 29 | -------------------------------------------------------------------------------- /src/i18n/index.jsx: -------------------------------------------------------------------------------- 1 | import { initReactI18next } from 'react-i18next'; 2 | import i18n from 'i18next'; 3 | import zh_CN from './locales/zh_CN.json'; 4 | import zh_TW from './locales/zh_TW.json'; 5 | import en_US from './locales/en_US.json'; 6 | import ru_RU from './locales/ru_RU.json'; 7 | import pt_BR from './locales/pt_BR.json'; 8 | import de_DE from './locales/de_DE.json'; 9 | import es_ES from './locales/es_ES.json'; 10 | import fr_FR from './locales/fr_FR.json'; 11 | import it_IT from './locales/it_IT.json'; 12 | import ja_JP from './locales/ja_JP.json'; 13 | import ko_KR from './locales/ko_KR.json'; 14 | import pt_PT from './locales/pt_PT.json'; 15 | import tr_TR from './locales/tr_TR.json'; 16 | import nb_NO from './locales/nb_NO.json'; 17 | import nn_NO from './locales/nn_NO.json'; 18 | import fa_IR from './locales/fa_IR.json'; 19 | import uk_UA from './locales/uk_UA.json'; 20 | import ar_AE from './locales/ar_AE.json'; 21 | import he_IL from './locales/he_IL.json'; 22 | 23 | // http://www.lingoes.net/zh/translator/langcode.htm 24 | 25 | i18n.use(initReactI18next).init({ 26 | fallbackLng: { 27 | zh_tw: ['zh_cn'], 28 | zh_cn: ['zh_tw'], 29 | pt_pt: ['pt_br'], 30 | pt_br: ['pt_pt'], 31 | nb_no: ['nn_no'], 32 | nn_no: ['nb_no'], 33 | default: ['en'], 34 | }, 35 | debug: false, 36 | interpolation: { 37 | escapeValue: false, 38 | }, 39 | resources: { 40 | en: en_US, 41 | zh_cn: zh_CN, 42 | zh_tw: zh_TW, 43 | ja: ja_JP, 44 | ko: ko_KR, 45 | fr: fr_FR, 46 | es: es_ES, 47 | ru: ru_RU, 48 | de: de_DE, 49 | it: it_IT, 50 | tr: tr_TR, 51 | pt_pt: pt_PT, 52 | pt_br: pt_BR, 53 | nb_no: nb_NO, 54 | nn_no: nn_NO, 55 | fa: fa_IR, 56 | uk: uk_UA, 57 | ar: ar_AE, 58 | he: he_IL, 59 | }, 60 | }); 61 | 62 | export default i18n; 63 | -------------------------------------------------------------------------------- /src/i18n/locales/ko_KR.json: -------------------------------------------------------------------------------- 1 | { 2 | "translation": { 3 | "common": { 4 | "ok": "Ok", 5 | "cancel": "취소", 6 | "save": "저장", 7 | "write_clipboard": "클립보드에 작성", 8 | "plugin": "플러그인" 9 | }, 10 | "services": { 11 | "translate": { 12 | "lingva": { 13 | "title": "Lingva" 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/i18n/locales/nb_NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "translation": { 3 | "common": { 4 | "ok": "Ok" 5 | }, 6 | "services": { 7 | "translate": { 8 | "youdao": {}, 9 | "deepl": {}, 10 | "openai": {}, 11 | "alibaba": {}, 12 | "chatglm": {}, 13 | "geminipro_polish": {}, 14 | "transmart": {}, 15 | "caiyun": {}, 16 | "baidu_field": {}, 17 | "cambridge_dict": {}, 18 | "geminipro": {}, 19 | "openai_custom": {}, 20 | "baidu": {}, 21 | "niutrans": {}, 22 | "tencent": {}, 23 | "geminipro_custom": {}, 24 | "volcengine": {}, 25 | "google": {}, 26 | "yandex": {}, 27 | "bing_dict": {}, 28 | "chatglm_custom": {}, 29 | "chatglm_polish": {}, 30 | "chatglm_summary": {}, 31 | "openai_polish": {}, 32 | "geminipro_summary": {}, 33 | "lingva": { 34 | "title": "Lingva" 35 | }, 36 | "bing": {}, 37 | "openai_summary": {} 38 | }, 39 | "recognize": { 40 | "iflytek_latex_ocr": {}, 41 | "baidu_ocr": {}, 42 | "simple_latex_ocr": {}, 43 | "volcengine_ocr": {}, 44 | "iflytek_intsig_ocr": {}, 45 | "baidu_accurate_ocr": {}, 46 | "baidu_img_ocr": {}, 47 | "volcengine_multi_lang_ocr": {}, 48 | "tencent_ocr": {}, 49 | "tencent_accurate_ocr": {}, 50 | "iflytek_ocr": {}, 51 | "qrcode": {}, 52 | "tesseract": {}, 53 | "tencent_img_ocr": {}, 54 | "system": {} 55 | }, 56 | "collection": { 57 | "anki": {}, 58 | "eudic": {} 59 | }, 60 | "tts": { 61 | "lingva_tts": {} 62 | } 63 | }, 64 | "languages": {}, 65 | "config": { 66 | "general": { 67 | "proxy": {}, 68 | "event": {}, 69 | "theme": {}, 70 | "font_size": {} 71 | }, 72 | "translate": { 73 | "font_size": {} 74 | }, 75 | "backup": {}, 76 | "recognize": {}, 77 | "hotkey": {}, 78 | "service": {}, 79 | "about": {}, 80 | "history": {} 81 | }, 82 | "translate": {}, 83 | "updater": {}, 84 | "recognize": {} 85 | } 86 | } -------------------------------------------------------------------------------- /src/i18n/locales/nn_NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "translation": { 3 | "common": { 4 | "ok": "Ok" 5 | }, 6 | "services": { 7 | "translate": { 8 | "youdao": {}, 9 | "deepl": {}, 10 | "openai": {}, 11 | "alibaba": {}, 12 | "chatglm": {}, 13 | "geminipro_polish": {}, 14 | "transmart": {}, 15 | "caiyun": {}, 16 | "baidu_field": {}, 17 | "cambridge_dict": {}, 18 | "geminipro": {}, 19 | "openai_custom": {}, 20 | "baidu": {}, 21 | "niutrans": {}, 22 | "tencent": {}, 23 | "geminipro_custom": {}, 24 | "volcengine": {}, 25 | "google": {}, 26 | "yandex": {}, 27 | "bing_dict": {}, 28 | "chatglm_custom": {}, 29 | "chatglm_polish": {}, 30 | "chatglm_summary": {}, 31 | "openai_polish": {}, 32 | "geminipro_summary": {}, 33 | "lingva": { 34 | "title": "Lingva" 35 | }, 36 | "bing": {}, 37 | "openai_summary": {} 38 | }, 39 | "recognize": { 40 | "iflytek_latex_ocr": {}, 41 | "baidu_ocr": {}, 42 | "simple_latex_ocr": {}, 43 | "volcengine_ocr": {}, 44 | "iflytek_intsig_ocr": {}, 45 | "baidu_accurate_ocr": {}, 46 | "baidu_img_ocr": {}, 47 | "volcengine_multi_lang_ocr": {}, 48 | "tencent_ocr": {}, 49 | "tencent_accurate_ocr": {}, 50 | "iflytek_ocr": {}, 51 | "qrcode": {}, 52 | "tesseract": {}, 53 | "tencent_img_ocr": {}, 54 | "system": {} 55 | }, 56 | "collection": { 57 | "anki": {}, 58 | "eudic": {} 59 | }, 60 | "tts": { 61 | "lingva_tts": {} 62 | } 63 | }, 64 | "languages": {}, 65 | "config": { 66 | "general": { 67 | "proxy": {}, 68 | "event": {}, 69 | "theme": {}, 70 | "font_size": {} 71 | }, 72 | "translate": { 73 | "font_size": {} 74 | }, 75 | "backup": {}, 76 | "recognize": {}, 77 | "hotkey": {}, 78 | "service": {}, 79 | "about": {}, 80 | "history": {} 81 | }, 82 | "translate": {}, 83 | "updater": {}, 84 | "recognize": {} 85 | } 86 | } -------------------------------------------------------------------------------- /src/i18n/locales/tk_TM.json: -------------------------------------------------------------------------------- 1 | { 2 | "translation": { 3 | "config": { 4 | "general": { 5 | "default_font": "Bellenen", 6 | "theme": { 7 | "system": "Ulgam", 8 | "dark": "Garañky", 9 | "light": "Ýagty" 10 | }, 11 | "font_size": { 12 | "14": "Kiçi (14px)", 13 | "12": "Örän kiçi (12px)", 14 | "10": "Goşmaça kiçi (10px)", 15 | "title": "Şrift ölçegi", 16 | "16": "Standart (16px)", 17 | "18": "Uly (18px)", 18 | "20": "Örän uly (20px)", 19 | "24": "Goşmaça Uly (24px)" 20 | }, 21 | "transparent": "Transparent Täsiri", 22 | "proxy": { 23 | "password": "Parol", 24 | "title": "Proksy", 25 | "username": "Ulanyjy ady", 26 | "no_proxy": "Proksi ýok", 27 | "host": "Host", 28 | "port": "Port" 29 | }, 30 | "title": "Umumy Sazlamalar", 31 | "label": "Umumy", 32 | "auto_start": "Awto Başlangyç", 33 | "check_update": "Täzelenmäni Barlaň", 34 | "server_port": "Serwer porty", 35 | "app_language": "Dili", 36 | "app_theme": "Tema", 37 | "app_font": "Şrift", 38 | "app_fallback_font": "Yza gaýdýan şrift", 39 | "dev_mode": "Developer mody", 40 | "server_port_change": "Serwer porty üýtgedildi, üýtgeşmeleriň güýje girmegi üçin programmany täzeden açyň" 41 | } 42 | }, 43 | "languages": { 44 | "ar": "Arapça", 45 | "hi": "Hindi Dili", 46 | "mn_mo": "Mongol", 47 | "pt_br": "Portugaliýa (Braziliýa)", 48 | "auto": "Awto Gözläň", 49 | "zh_cn": "Ýönekeýleşdirilen Hytaý", 50 | "zh_tw": "Adaty Hytaý", 51 | "en": "Iñlis dili", 52 | "ja": "Ýapon dili", 53 | "ko": "Koreýs dili", 54 | "fr": "Fransuz", 55 | "es": "Ispan", 56 | "ru": "Rus", 57 | "de": "Nemes", 58 | "it": "Italýan", 59 | "tr": "Türk", 60 | "pt_pt": "Portugaliýa", 61 | "mn_cy": "Mongol (kiril)", 62 | "km": "Khmer", 63 | "nb_no": "Norwegiýaly Bokmål", 64 | "nn_no": "Norwegiýaly Nynorsk", 65 | "fa": "Pars", 66 | "sv": "Şwesiýa", 67 | "pl": "Polýak", 68 | "nl": "Gollandiýa", 69 | "uk": "Ukrainiýa", 70 | "he": "Hebrewýçe", 71 | "vi": "Wýetnam", 72 | "th": "Taý Dili", 73 | "id": "Indoneziýaly", 74 | "ms": "Malaý" 75 | }, 76 | "common": { 77 | "ok": "Bolýa", 78 | "cancel": "Ýatyr", 79 | "save": "Saklaň", 80 | "write_clipboard": "Ýat paneline ýazyň", 81 | "plugin": "Plugin", 82 | "coming": "Tiz ýakynda…", 83 | "clear": "Arassala" 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/i18n/locales/tr_TR.json: -------------------------------------------------------------------------------- 1 | { 2 | "translation": { 3 | "common": { 4 | "ok": "Ok" 5 | }, 6 | "services": { 7 | "translate": { 8 | "youdao": {}, 9 | "deepl": {}, 10 | "openai": {}, 11 | "alibaba": {}, 12 | "chatglm": {}, 13 | "geminipro_polish": {}, 14 | "transmart": {}, 15 | "caiyun": {}, 16 | "baidu_field": {}, 17 | "cambridge_dict": {}, 18 | "geminipro": {}, 19 | "openai_custom": {}, 20 | "baidu": {}, 21 | "niutrans": {}, 22 | "tencent": {}, 23 | "geminipro_custom": {}, 24 | "volcengine": {}, 25 | "google": {}, 26 | "yandex": {}, 27 | "bing_dict": {}, 28 | "chatglm_custom": {}, 29 | "chatglm_polish": {}, 30 | "chatglm_summary": {}, 31 | "openai_polish": {}, 32 | "geminipro_summary": {}, 33 | "lingva": { 34 | "title": "Lingva" 35 | }, 36 | "bing": {}, 37 | "openai_summary": {} 38 | }, 39 | "recognize": { 40 | "iflytek_latex_ocr": {}, 41 | "baidu_ocr": {}, 42 | "simple_latex_ocr": {}, 43 | "volcengine_ocr": {}, 44 | "iflytek_intsig_ocr": {}, 45 | "baidu_accurate_ocr": {}, 46 | "baidu_img_ocr": {}, 47 | "volcengine_multi_lang_ocr": {}, 48 | "tencent_ocr": {}, 49 | "tencent_accurate_ocr": {}, 50 | "iflytek_ocr": {}, 51 | "qrcode": {}, 52 | "tesseract": {}, 53 | "tencent_img_ocr": {}, 54 | "system": {} 55 | }, 56 | "collection": { 57 | "anki": {}, 58 | "eudic": {} 59 | }, 60 | "tts": { 61 | "lingva_tts": {} 62 | } 63 | }, 64 | "languages": {}, 65 | "config": { 66 | "general": { 67 | "proxy": {}, 68 | "event": {}, 69 | "theme": {}, 70 | "font_size": {} 71 | }, 72 | "translate": { 73 | "font_size": {} 74 | }, 75 | "backup": {}, 76 | "recognize": {}, 77 | "hotkey": {}, 78 | "service": {}, 79 | "about": {}, 80 | "history": {} 81 | }, 82 | "translate": {}, 83 | "updater": {}, 84 | "recognize": {} 85 | } 86 | } -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 2 | import { appWindow } from '@tauri-apps/api/window'; 3 | import { NextUIProvider } from '@nextui-org/react'; 4 | import ReactDOM from 'react-dom/client'; 5 | import React from 'react'; 6 | 7 | import { initStore } from './utils/store'; 8 | import { initEnv } from './utils/env'; 9 | import App from './App'; 10 | 11 | if (import.meta.env.PROD) { 12 | document.addEventListener('contextmenu', (e) => { 13 | e.preventDefault(); 14 | }); 15 | } 16 | 17 | initStore().then(async () => { 18 | await initEnv(); 19 | const rootElement = document.getElementById('root'); 20 | const root = ReactDOM.createRoot(rootElement); 21 | root.render( 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /src/services/collection/anki/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'anki', 3 | icon: 'logo/anki.svg', 4 | }; 5 | -------------------------------------------------------------------------------- /src/services/collection/eudic/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function collection(source, target, options = {}) { 4 | const { config } = options; 5 | const name = config['name'] ?? 'pot'; 6 | const token = config['token'] ?? ''; 7 | 8 | let categoryId = await checkCategory(name, token); 9 | return await addWordToCategory(categoryId, source, token); 10 | } 11 | 12 | async function checkCategory(name, token) { 13 | let res = await fetch('https://api.frdic.com/api/open/v1/studylist/category', { 14 | method: 'GET', 15 | query: { 16 | language: 'en', 17 | }, 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | Authorization: token, 21 | }, 22 | }); 23 | 24 | let result = res.data; 25 | if (result.data) { 26 | for (let i of result.data) { 27 | if (i.name === name) { 28 | return i.id; 29 | } 30 | } 31 | 32 | let res1 = await fetch('https://api.frdic.com/api/open/v1/studylist/category', { 33 | method: 'POST', 34 | headers: { 35 | 'Content-Type': 'application/json', 36 | Authorization: token, 37 | }, 38 | body: Body.json({ 39 | language: 'en', 40 | name: name, 41 | }), 42 | }); 43 | let result1 = res1.data; 44 | if (result1.data) { 45 | return result1.data.id; 46 | } else { 47 | throw 'Create Category Failed'; 48 | } 49 | } else { 50 | throw 'Get Category Failed'; 51 | } 52 | } 53 | 54 | async function addWordToCategory(id, word, token) { 55 | let res = await fetch('https://api.frdic.com/api/open/v1/studylist/words', { 56 | method: 'POST', 57 | headers: { 58 | 'Content-Type': 'application/json', 59 | Authorization: token, 60 | }, 61 | body: Body.json({ 62 | id: id, 63 | language: 'en', 64 | words: [word], 65 | }), 66 | }); 67 | let result = res.data; 68 | return result.message; 69 | } 70 | 71 | export * from './Config'; 72 | export * from './info'; 73 | -------------------------------------------------------------------------------- /src/services/collection/eudic/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'eudic', 3 | icon: 'logo/eudic.png', 4 | }; 5 | -------------------------------------------------------------------------------- /src/services/collection/index.jsx: -------------------------------------------------------------------------------- 1 | import * as _anki from './anki'; 2 | import * as _eudic from './eudic'; 3 | 4 | export const anki = _anki; 5 | export const eudic = _eudic; 6 | -------------------------------------------------------------------------------- /src/services/recognize/baidu/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function recognize(base64, language, options = {}) { 4 | const { config } = options; 5 | 6 | const { client_id, client_secret } = config; 7 | 8 | const url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic'; 9 | const token_url = 'https://aip.baidubce.com/oauth/2.0/token'; 10 | 11 | const token_res = await fetch(token_url, { 12 | method: 'POST', 13 | query: { 14 | grant_type: 'client_credentials', 15 | client_id, 16 | client_secret, 17 | }, 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | Accept: 'application/json', 21 | }, 22 | }); 23 | if (token_res.ok) { 24 | if (token_res.data.access_token) { 25 | let token = token_res.data.access_token; 26 | 27 | const res = await fetch(url, { 28 | method: 'POST', 29 | headers: { 30 | 'Content-Type': 'application/x-www-form-urlencoded', 31 | }, 32 | query: { 33 | access_token: token, 34 | }, 35 | body: Body.form({ 36 | language_type: language, 37 | detect_direction: 'false', 38 | image: base64, 39 | }), 40 | }); 41 | if (res.ok) { 42 | let result = res.data; 43 | if (result['words_result']) { 44 | let target = ''; 45 | for (let i of result['words_result']) { 46 | target += i['words'] + '\n'; 47 | } 48 | return target.trim(); 49 | } else { 50 | throw JSON.stringify(result); 51 | } 52 | } else { 53 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 54 | } 55 | } else { 56 | throw 'Get Access Token Failed!'; 57 | } 58 | } else { 59 | throw `Http Request Error\nHttp Status: ${token_res.status}\n${JSON.stringify(token_res.data)}`; 60 | } 61 | } 62 | 63 | export * from './Config'; 64 | export * from './info'; 65 | -------------------------------------------------------------------------------- /src/services/recognize/baidu/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'baidu_ocr', 3 | icon: 'logo/baidu.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'CHN_ENG', 8 | zh_cn = 'CHN_ENG', 9 | zh_tw = 'CHN_ENG', 10 | en = 'ENG', 11 | yue = 'CHN_ENG', 12 | ja = 'JAP', 13 | ko = 'KOR', 14 | fr = 'FRE', 15 | es = 'SPA', 16 | ru = 'RUS', 17 | de = 'GER', 18 | it = 'ITA', 19 | pt_pt = 'POR', 20 | pt_br = 'POR', 21 | } 22 | -------------------------------------------------------------------------------- /src/services/recognize/baidu_accurate/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function recognize(base64, language, options = {}) { 4 | const { config } = options; 5 | 6 | const { client_id, client_secret } = config; 7 | 8 | const url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic'; 9 | const token_url = 'https://aip.baidubce.com/oauth/2.0/token'; 10 | 11 | const token_res = await fetch(token_url, { 12 | method: 'POST', 13 | query: { 14 | grant_type: 'client_credentials', 15 | client_id, 16 | client_secret, 17 | }, 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | Accept: 'application/json', 21 | }, 22 | }); 23 | if (token_res.ok) { 24 | if (token_res.data.access_token) { 25 | let token = token_res.data.access_token; 26 | 27 | const res = await fetch(url, { 28 | method: 'POST', 29 | headers: { 30 | 'Content-Type': 'application/x-www-form-urlencoded', 31 | }, 32 | query: { 33 | access_token: token, 34 | }, 35 | body: Body.form({ 36 | language_type: language, 37 | detect_direction: 'false', 38 | image: base64, 39 | }), 40 | }); 41 | if (res.ok) { 42 | let result = res.data; 43 | if (result['words_result']) { 44 | let target = ''; 45 | for (let i of result['words_result']) { 46 | target += i['words'] + '\n'; 47 | } 48 | return target.trim(); 49 | } else { 50 | throw JSON.stringify(result); 51 | } 52 | } else { 53 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 54 | } 55 | } else { 56 | throw 'Get Access Token Failed!'; 57 | } 58 | } else { 59 | throw `Http Request Error\nHttp Status: ${token_res.status}\n${JSON.stringify(token_res.data)}`; 60 | } 61 | } 62 | 63 | export * from './Config'; 64 | export * from './info'; 65 | -------------------------------------------------------------------------------- /src/services/recognize/baidu_accurate/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'baidu_accurate_ocr', 3 | icon: 'logo/baidu.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto_detect', 8 | zh_cn = 'CHN_ENG', 9 | zh_tw = 'CHN_ENG', 10 | en = 'ENG', 11 | yue = 'CHN_ENG', 12 | ja = 'JAP', 13 | ko = 'KOR', 14 | fr = 'FRE', 15 | es = 'SPA', 16 | ru = 'RUS', 17 | de = 'GER', 18 | it = 'ITA', 19 | tr = 'TUR', 20 | pt_pt = 'POR', 21 | pt_br = 'POR', 22 | vi = 'VIE', 23 | id = 'IND', 24 | th = 'THA', 25 | ms = 'MAL', 26 | ar = 'ARA', 27 | hi = 'HIN', 28 | } 29 | -------------------------------------------------------------------------------- /src/services/recognize/baidu_img/index.jsx: -------------------------------------------------------------------------------- 1 | import { readBinaryFile, BaseDirectory } from '@tauri-apps/api/fs'; 2 | import { fetch, Body } from '@tauri-apps/api/http'; 3 | import { nanoid } from 'nanoid'; 4 | import md5 from 'md5'; 5 | 6 | export async function recognize(base64, language, options = {}) { 7 | const { config } = options; 8 | 9 | const { appid, secret } = config; 10 | 11 | const url = 'https://fanyi-api.baidu.com/api/trans/sdk/picture'; 12 | 13 | const salt = nanoid(); 14 | if (appid === '' || secret === '') { 15 | throw 'Please configure appid and secret'; 16 | } 17 | 18 | let file = await readBinaryFile('pot_screenshot_cut.png', { dir: BaseDirectory.AppCache }); 19 | const str = appid + md5(file) + salt + 'APICUIDmac' + secret; 20 | const sign = md5(str); 21 | 22 | let res = await fetch(url, { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'multipart/form-data', 26 | }, 27 | body: Body.form({ 28 | image: { 29 | file: file, 30 | mime: 'image/png', 31 | fileName: 'pot_screenshot_cut.png', 32 | }, 33 | from: 'auto', 34 | to: language === 'auto' ? 'zh' : language, 35 | appid: appid, 36 | salt: salt, 37 | cuid: 'APICUID', 38 | mac: 'mac', 39 | version: '3', 40 | sign: sign, 41 | }), 42 | }); 43 | 44 | if (res.ok) { 45 | let result = res.data; 46 | if (result['data'] && result['data']['sumSrc'] && result['data']['sumDst']) { 47 | if (language === 'auto') { 48 | return result['data']['sumSrc'].trim(); 49 | } else { 50 | return result['data']['sumDst'].trim(); 51 | } 52 | } else { 53 | throw JSON.stringify(result); 54 | } 55 | } else { 56 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 57 | } 58 | } 59 | 60 | export * from './Config'; 61 | export * from './info'; 62 | -------------------------------------------------------------------------------- /src/services/recognize/baidu_img/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'baidu_img_ocr', 3 | icon: 'logo/baidu.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'cht', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'jp', 13 | ko = 'kor', 14 | fr = 'fra', 15 | es = 'spa', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pot', 22 | vi = 'vie', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'may', 26 | ar = 'ar', 27 | hi = 'hi', 28 | } 29 | -------------------------------------------------------------------------------- /src/services/recognize/iflytek/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'iflytek_ocr', 3 | icon: 'logo/iflytek.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'zh_cn', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/recognize/iflytek_intsig/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | import CryptoJS from 'crypto-js'; 3 | import { iflytek_auth } from '../iflytek'; 4 | 5 | export async function recognize(base64, language, options = {}) { 6 | const { config } = options; 7 | 8 | const { appid, apisecret, apikey } = config; 9 | 10 | const host = 'api.xf-yun.com'; 11 | const today = new Date(); 12 | const date = today.toUTCString(); 13 | const request_line = 'POST /v1/private/hh_ocr_recognize_doc HTTP/1.1'; 14 | 15 | let auth = iflytek_auth(apikey, apisecret, host, date, request_line); 16 | 17 | let request_url = 18 | 'https://api.xf-yun.com/v1/private/hh_ocr_recognize_doc?' + 19 | 'authorization=' + 20 | auth + 21 | '&host=' + 22 | host + 23 | '&date=' + 24 | encodeURIComponent(date); 25 | 26 | let request_body = { 27 | header: { 28 | app_id: appid, // 在讯飞开放平台申请的appid信息 29 | status: 3, // 请求状态,取值为:3(一次传完) 30 | }, 31 | parameter: { 32 | hh_ocr_recognize_doc: { 33 | recognizeDocumentRes: { 34 | encoding: 'utf8', 35 | compress: 'raw', 36 | format: 'json', 37 | }, 38 | }, 39 | }, 40 | payload: { 41 | image: { 42 | image: base64, 43 | }, 44 | }, 45 | }; 46 | 47 | // 发送请求 48 | let res = await fetch(request_url, { 49 | method: 'POST', 50 | headers: { 'content-type': 'application/json' }, 51 | body: { type: 'Text', payload: JSON.stringify(request_body) }, 52 | }); 53 | 54 | // 处理结果 55 | if (!res.ok) { 56 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 57 | } 58 | let data = res['data']; 59 | if (!data) { 60 | throw `Result data not found\nResult:\n${JSON.stringify(res)}`; 61 | } 62 | let res_payload = data['payload']; 63 | if (!res_payload) { 64 | throw `Result payload not found\nResult:\n${JSON.stringify(res)}`; 65 | } 66 | 67 | let text = CryptoJS.enc.Base64.parse(res_payload['recognizeDocumentRes']['text']); // Base64解码 68 | let text_string = CryptoJS.enc.Utf8.stringify(text); 69 | let text_json = JSON.parse(text_string); 70 | let return_content = text_json['whole_text']; // 最终结果 71 | if (!return_content) { 72 | return_content = ''; 73 | } 74 | return return_content.trim(); 75 | } 76 | 77 | export * from './Config'; 78 | export * from './info'; 79 | -------------------------------------------------------------------------------- /src/services/recognize/iflytek_intsig/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'iflytek_intsig_ocr', 3 | icon: 'logo/iflytek.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | ja = 'ja', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt_pt', 20 | pt_br = 'pt_br', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | } 28 | -------------------------------------------------------------------------------- /src/services/recognize/iflytek_latex/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | import hmacSHA256 from 'crypto-js/hmac-sha256'; 3 | import hashSHA256 from 'crypto-js/sha256'; 4 | import Base64 from 'crypto-js/enc-base64'; 5 | 6 | export async function recognize(base64, language, options = {}) { 7 | const { config } = options; 8 | 9 | const { appid, apisecret, apikey } = config; 10 | 11 | const url = 'https://rest-api.xfyun.cn/v2/itr'; 12 | 13 | const body = { 14 | common: { 15 | app_id: appid, 16 | }, 17 | business: { 18 | ent: 'teach-photo-print', 19 | aue: 'raw', 20 | }, 21 | data: { 22 | image: base64, 23 | }, 24 | }; 25 | const host = 'rest-api.xfyun.cn'; 26 | const date = new Date().toUTCString(); 27 | const request_line = 'POST /v2/itr HTTP/1.1'; 28 | const digest = 'SHA-256=' + Base64.stringify(hashSHA256(JSON.stringify(body))); 29 | const signature_origin = `host: ${host}\ndate: ${date}\n${request_line}\ndigest: ${digest}`; 30 | const signature_sha = hmacSHA256(signature_origin, apisecret); 31 | const signature = Base64.stringify(signature_sha); 32 | const authorization = `api_key="${apikey}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"`; 33 | const headers = { 34 | 'Content-Type': 'application/json', 35 | Accept: 'application/json,version=1.0', 36 | Host: host, 37 | Date: date, 38 | Digest: digest, 39 | Authorization: authorization, 40 | }; 41 | const res = await fetch(url, { 42 | method: 'POST', 43 | headers, 44 | body: { 45 | type: 'Text', 46 | payload: JSON.stringify(body), 47 | }, 48 | }); 49 | if (res.ok) { 50 | let result = res.data; 51 | if (result.data['region']) { 52 | let target = ''; 53 | for (let i of result.data['region']) { 54 | target += i['recog']['content'] + '\n'; 55 | } 56 | target = target.replaceAll(' ifly-latex-begin ', ''); 57 | target = target.replaceAll(' ifly-latex-end ', ''); 58 | return target.trim(); 59 | } else { 60 | throw JSON.stringify(result); 61 | } 62 | } else { 63 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 64 | } 65 | } 66 | 67 | export * from './Config'; 68 | export * from './info'; 69 | -------------------------------------------------------------------------------- /src/services/recognize/iflytek_latex/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'iflytek_latex_ocr', 3 | icon: 'logo/iflytek.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'zh_cn', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/recognize/index.jsx: -------------------------------------------------------------------------------- 1 | import * as _system from './system'; 2 | import * as _tesseract from './tesseract'; 3 | import * as _baidu_ocr from './baidu'; 4 | import * as _baidu_accurate_ocr from './baidu_accurate'; 5 | import * as _baidu_img_ocr from './baidu_img'; 6 | import * as _iflytek_ocr from './iflytek'; 7 | import * as _iflytek_intsig_ocr from './iflytek_intsig'; 8 | import * as _iflytek_latex_ocr from './iflytek_latex'; 9 | import * as _simple_latex_ocr from './simple_latex'; 10 | import * as _tencent_ocr from './tencent'; 11 | import * as _tencent_accurate_ocr from './tencent_accurate'; 12 | import * as _tencent_img_ocr from './tencent_img'; 13 | import * as _volcengine_ocr from './volcengine'; 14 | import * as _volcengine_multi_lang_ocr from './volcengine_multi_lang'; 15 | import * as _qrcode from './qrcode'; 16 | 17 | export const system = _system; 18 | export const tesseract = _tesseract; 19 | export const baidu_ocr = _baidu_ocr; 20 | export const baidu_accurate_ocr = _baidu_accurate_ocr; 21 | export const baidu_img_ocr = _baidu_img_ocr; 22 | export const iflytek_ocr = _iflytek_ocr; 23 | export const iflytek_intsig_ocr = _iflytek_intsig_ocr; 24 | export const iflytek_latex_ocr = _iflytek_latex_ocr; 25 | export const simple_latex_ocr = _simple_latex_ocr; 26 | export const tencent_ocr = _tencent_ocr; 27 | export const tencent_accurate_ocr = _tencent_accurate_ocr; 28 | export const tencent_img_ocr = _tencent_img_ocr; 29 | export const volcengine_ocr = _volcengine_ocr; 30 | export const volcengine_multi_lang_ocr = _volcengine_multi_lang_ocr; 31 | export const qrcode = _qrcode; 32 | -------------------------------------------------------------------------------- /src/services/recognize/qrcode/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/services/recognize/qrcode/index.jsx: -------------------------------------------------------------------------------- 1 | import jsQR from 'jsqr'; 2 | 3 | export async function recognize(base64, language, options = {}) { 4 | let canvas = document.createElement('CANVAS'); 5 | let ctx = canvas.getContext('2d'); 6 | base64 = 'data:image/png;base64,' + base64; 7 | let img = new Image(); 8 | img.src = base64; 9 | let imgdata = await new Promise((resolve, reject) => { 10 | img.onload = () => { 11 | img.crossOrigin = 'anonymous'; 12 | canvas.height = img.height; 13 | canvas.width = img.width; 14 | ctx.drawImage(img, 0, 0); 15 | const height = img.height; 16 | const width = img.width; 17 | const data = ctx.getImageData(0, 0, width, height); 18 | if (height !== 0 && width !== 0) { 19 | resolve({ data, height, width }); 20 | } 21 | }; 22 | }); 23 | 24 | const code = jsQR(imgdata.data.data, imgdata.width, imgdata.height); 25 | if (code) { 26 | return code.data; 27 | } else { 28 | throw 'QR code not recognized or multiple QR codes exist'; 29 | } 30 | } 31 | 32 | export * from './Config'; 33 | export * from './info'; 34 | -------------------------------------------------------------------------------- /src/services/recognize/qrcode/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'qrcode', 3 | icon: 'logo/qrcode.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = '', 8 | zh_cn = '', 9 | zh_tw = '', 10 | en = '', 11 | ja = '', 12 | ko = '', 13 | fr = '', 14 | es = '', 15 | ru = '', 16 | de = '', 17 | it = '', 18 | tr = '', 19 | pt_pt = '', 20 | pt_br = '', 21 | vi = '', 22 | id = '', 23 | th = '', 24 | ms = '', 25 | ar = '', 26 | hi = '', 27 | } 28 | -------------------------------------------------------------------------------- /src/services/recognize/simple_latex/index.jsx: -------------------------------------------------------------------------------- 1 | import { readBinaryFile, BaseDirectory } from '@tauri-apps/api/fs'; 2 | import { fetch, Body } from '@tauri-apps/api/http'; 3 | 4 | export async function recognize(base64, language, options = {}) { 5 | const { config } = options; 6 | 7 | const { token } = config; 8 | 9 | const url = 'https://server.simpletex.cn/api/latex_ocr/v2'; 10 | 11 | let file = await readBinaryFile('pot_screenshot_cut.png', { dir: BaseDirectory.AppCache }); 12 | 13 | const res = await fetch(url, { 14 | method: 'POST', 15 | headers: { 16 | token, 17 | 'content-type': 'multipart/form-data', 18 | }, 19 | body: Body.form({ 20 | file: { 21 | file: file, 22 | fileName: 'pot_screenshot_cut.png', 23 | }, 24 | }), 25 | }); 26 | if (res.ok) { 27 | let result = res.data; 28 | if (result['res'] && result['res']['latex']) { 29 | return result['res']['latex'].trim(); 30 | } else { 31 | throw JSON.stringify(result); 32 | } 33 | } else { 34 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 35 | } 36 | } 37 | 38 | export * from './Config'; 39 | export * from './info'; 40 | -------------------------------------------------------------------------------- /src/services/recognize/simple_latex/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'simple_latex_ocr', 3 | icon: 'logo/simple_latex.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'zh_cn', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/recognize/system/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/services/recognize/system/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'system', 3 | icon: `system`, 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | ja = 'ja', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt_pt', 20 | pt_br = 'pt_br', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | uk = 'uk', 28 | he = 'he', 29 | } 30 | -------------------------------------------------------------------------------- /src/services/recognize/tencent/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'tencent_ocr', 3 | icon: 'logo/tencent_cloud.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh_rare', 10 | yue = 'zh_rare', 11 | en = 'auto', 12 | ja = 'jap', 13 | ko = 'kor', 14 | fr = 'fre', 15 | es = 'spa', 16 | ru = 'rus', 17 | de = 'ger', 18 | it = 'ita', 19 | pt_pt = 'por', 20 | pt_br = 'por', 21 | vi = 'vie', 22 | th = 'tha', 23 | ms = 'may', 24 | ar = 'ara', 25 | hi = 'hi', 26 | } 27 | -------------------------------------------------------------------------------- /src/services/recognize/tencent_accurate/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'tencent_accurate_ocr', 3 | icon: 'logo/tencent_cloud.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh_rare', 10 | yue = 'zh_rare', 11 | en = 'auto', 12 | } 13 | -------------------------------------------------------------------------------- /src/services/recognize/tencent_img/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'tencent_img_ocr', 3 | icon: 'logo/tencent_cloud.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh-TW', 10 | yue = 'zh-TW', 11 | en = 'en', 12 | ja = 'ja', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | th = 'th', 23 | ms = 'ms', 24 | } 25 | -------------------------------------------------------------------------------- /src/services/recognize/tesseract/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/services/recognize/tesseract/index.jsx: -------------------------------------------------------------------------------- 1 | import Tesseract from 'tesseract.js'; 2 | import { Language } from './info'; 3 | 4 | export async function recognize(base64, language) { 5 | const { 6 | data: { text }, 7 | } = await Tesseract.recognize('data:image/png;base64,' + base64, language, { 8 | workerPath: '/worker.min.js', 9 | corePath: '/tesseract-core-simd-lstm.wasm.js', 10 | langPath: 'https://pub-f6afb74f13c64cd89561b4714dca1c27.r2.dev', 11 | }); 12 | if (language === Language.zh_cn || language === Language.zh_tw) { 13 | return text.replaceAll(' ', '').trim(); 14 | } else { 15 | return text.trim(); 16 | } 17 | } 18 | 19 | export * from './Config'; 20 | export * from './info'; 21 | -------------------------------------------------------------------------------- /src/services/recognize/tesseract/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'tesseract', 3 | icon: 'logo/tesseract.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'eng', 8 | zh_cn = 'chi_sim', 9 | zh_tw = 'chi_tra', 10 | en = 'eng', 11 | yue = 'chi_sim', 12 | ja = 'jpn', 13 | ko = 'kor', 14 | fr = 'fra', 15 | es = 'spa', 16 | ru = 'rus', 17 | de = 'deu', 18 | it = 'ita', 19 | tr = 'tur', 20 | pt_pt = 'por', 21 | pt_br = 'por', 22 | vi = 'vie', 23 | id = 'ind', 24 | th = 'tha', 25 | ms = 'msa', 26 | ar = 'ara', 27 | hi = 'hin', 28 | uk = 'ukr', 29 | he = 'heb', 30 | } 31 | -------------------------------------------------------------------------------- /src/services/recognize/volcengine/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'volcengine_ocr', 3 | icon: 'logo/volcengine.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | yue = 'yue', 11 | en = 'en', 12 | } 13 | -------------------------------------------------------------------------------- /src/services/recognize/volcengine_multi_lang/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'volcengine_multi_lang_ocr', 3 | icon: 'logo/volcengine.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh_cn', 9 | zh_tw = 'zh_tw', 10 | en = 'en', 11 | ja = 'ja', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt_pt', 20 | pt_br = 'pt_br', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | } 28 | -------------------------------------------------------------------------------- /src/services/translate/alibaba/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | import HmacSHA1 from 'crypto-js/hmac-sha1'; 3 | import base64 from 'crypto-js/enc-base64'; 4 | 5 | export async function translate(text, from, to, options = {}) { 6 | const { config } = options; 7 | 8 | const { accesskey_id, accesskey_secret } = config; 9 | 10 | function getRandomNumber() { 11 | const rand = Math.floor(Math.random() * 99999) + 100000; 12 | return rand * 1000; 13 | } 14 | if (accesskey_id === '' || accesskey_secret === '') { 15 | throw 'Please configure AccessKey ID and AccessKey Secret'; 16 | } 17 | 18 | let today = new Date(); 19 | let timestamp = today.toISOString().replaceAll(/\.[0-9]*/g, ''); 20 | let endpoint = 'http://mt.cn-hangzhou.aliyuncs.com/'; 21 | let url_path = 'api/translate/web/general'; 22 | 23 | let query = `AccessKeyId=${accesskey_id}&Action=TranslateGeneral&Format=JSON&FormatType=text&Scene=general&SignatureMethod=HMAC-SHA1&SignatureNonce=${getRandomNumber()}&SignatureVersion=1.0&SourceLanguage=${from}&SourceText=${encodeURIComponent( 24 | text 25 | )}&TargetLanguage=${to}&Timestamp=${encodeURIComponent(timestamp)}&Version=2018-10-12`; 26 | 27 | let CanonicalizedQueryString = endpoint + url_path + '?' + query; 28 | 29 | let stringToSign = 'GET' + '&' + encodeURIComponent('/') + '&' + encodeURIComponent(query); 30 | 31 | stringToSign = stringToSign.replaceAll('!', '%2521'); 32 | stringToSign = stringToSign.replaceAll("'", '%2527'); 33 | stringToSign = stringToSign.replaceAll('(', '%2528'); 34 | stringToSign = stringToSign.replaceAll(')', '%2529'); 35 | stringToSign = stringToSign.replaceAll('*', '%252A'); 36 | stringToSign = stringToSign.replaceAll('+', '%252B'); 37 | stringToSign = stringToSign.replaceAll(',', '%252C'); 38 | 39 | let signature = base64.stringify(HmacSHA1(stringToSign, accesskey_secret + '&')); 40 | 41 | CanonicalizedQueryString = CanonicalizedQueryString + '&Signature=' + encodeURIComponent(signature); 42 | 43 | let res = await fetch(CanonicalizedQueryString, { 44 | method: 'GET', 45 | }); 46 | 47 | if (res.ok) { 48 | let result = res.data; 49 | if (result['Code'] === '200') { 50 | return result['Data']['Translated'].trim(); 51 | } else { 52 | throw JSON.stringify(result); 53 | } 54 | } else { 55 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 56 | } 57 | } 58 | 59 | export * from './Config'; 60 | export * from './info'; 61 | -------------------------------------------------------------------------------- /src/services/translate/alibaba/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'alibaba', 3 | icon: 'logo/alibaba.svg', 4 | }; 5 | // https://help.aliyun.com/document_detail/215387.html?spm=a2c4g.158269.0.0.ddfc4f62vEpa38 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh-tw', 10 | yue = 'yue', 11 | ja = 'ja', 12 | en = 'en', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pt', 22 | vi = 'vi', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'ms', 26 | ar = 'ar', 27 | hi = 'hi', 28 | mn_mo = 'mn', 29 | km = 'km', 30 | nb_no = 'no', 31 | nn_no = 'no', 32 | fa = 'fa', 33 | sv = 'sv', 34 | pl = 'pl', 35 | nl = 'nl', 36 | he = 'he', 37 | } 38 | -------------------------------------------------------------------------------- /src/services/translate/baidu/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | import { nanoid } from 'nanoid'; 3 | import md5 from 'md5'; 4 | 5 | export async function translate(text, from, to, options = {}) { 6 | const { config } = options; 7 | 8 | const { appid, secret } = config; 9 | 10 | const url = 'https://fanyi-api.baidu.com/api/trans/vip/translate'; 11 | 12 | const salt = nanoid(); 13 | if (appid === '' || secret === '') { 14 | throw 'Please configure appid and secret'; 15 | } 16 | 17 | const str = appid + text + salt + secret; 18 | const sign = md5(str); 19 | 20 | let res = await fetch(url, { 21 | query: { 22 | q: text, 23 | from: from, 24 | to: to, 25 | appid: appid, 26 | salt: salt, 27 | sign: sign, 28 | }, 29 | }); 30 | if (res.ok) { 31 | let result = res.data; 32 | let target = ''; 33 | 34 | const { trans_result } = result; 35 | if (trans_result) { 36 | for (let i in trans_result) { 37 | target = target + trans_result[i]['dst'] + '\n'; 38 | } 39 | return target.trim(); 40 | } else { 41 | throw JSON.stringify(result); 42 | } 43 | } else { 44 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 45 | } 46 | } 47 | 48 | export * from './Config'; 49 | export * from './info'; 50 | -------------------------------------------------------------------------------- /src/services/translate/baidu/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'baidu', 3 | icon: 'logo/baidu.svg', 4 | }; 5 | // https://fanyi-api.baidu.com/product/113 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'cht', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'jp', 13 | ko = 'kor', 14 | fr = 'fra', 15 | es = 'spa', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pot', 22 | vi = 'vie', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'may', 26 | ar = 'ar', 27 | hi = 'hi', 28 | km = 'hkm', 29 | nb_no = 'nob', 30 | nn_no = 'nno', 31 | fa = 'per', 32 | sv = 'swe', 33 | pl = 'pl', 34 | nl = 'nl', 35 | uk = 'ukr', 36 | he = 'heb', 37 | } 38 | -------------------------------------------------------------------------------- /src/services/translate/baidu_field/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | import { nanoid } from 'nanoid'; 3 | import md5 from 'md5'; 4 | 5 | export async function translate(text, from, to, options = {}) { 6 | const { config } = options; 7 | 8 | const { appid, secret, field } = config; 9 | 10 | const url = 'https://fanyi-api.baidu.com/api/trans/vip/fieldtranslate'; 11 | 12 | const salt = nanoid(); 13 | if (appid === '' || secret === '') { 14 | throw 'Please configure appid and secret'; 15 | } 16 | 17 | const str = appid + text + salt + field + secret; 18 | const sign = md5(str); 19 | 20 | let res = await fetch(url, { 21 | query: { 22 | q: text, 23 | from: from, 24 | to: to, 25 | appid: appid, 26 | salt: salt, 27 | sign: sign, 28 | domain: field, 29 | }, 30 | }); 31 | 32 | if (res.ok) { 33 | let result = res.data; 34 | let target = ''; 35 | 36 | const { trans_result } = result; 37 | if (trans_result) { 38 | for (let i in trans_result) { 39 | target = target + trans_result[i]['dst'] + '\n'; 40 | } 41 | return target.trim(); 42 | } else { 43 | throw JSON.stringify(result); 44 | } 45 | } else { 46 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 47 | } 48 | } 49 | 50 | export * from './Config'; 51 | export * from './info'; 52 | -------------------------------------------------------------------------------- /src/services/translate/baidu_field/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'baidu_field', 3 | icon: 'logo/baidu.svg', 4 | }; 5 | // https://fanyi-api.baidu.com/product/113 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'cht', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'jp', 13 | ko = 'kor', 14 | fr = 'fra', 15 | es = 'spa', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pot', 22 | vi = 'vie', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'may', 26 | ar = 'ar', 27 | hi = 'hi', 28 | km = 'hkm', 29 | nb_no = 'nob', 30 | nn_no = 'nno', 31 | fa = 'per', 32 | sv = 'swe', 33 | pl = 'pl', 34 | nl = 'nl', 35 | uk = 'ukr', 36 | he = 'heb', 37 | } 38 | -------------------------------------------------------------------------------- /src/services/translate/bing/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/bing/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to) { 4 | const token_url = 'https://edge.microsoft.com/translate/auth'; 5 | 6 | let token = await fetch(token_url, { 7 | method: 'GET', 8 | headers: { 9 | 'User-Agent': 10 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42', 11 | }, 12 | responseType: 2, 13 | }); 14 | 15 | if (token.ok) { 16 | const url = 'https://api-edge.cognitive.microsofttranslator.com/translate'; 17 | 18 | let res = await fetch(url, { 19 | method: 'POST', 20 | headers: { 21 | accept: '*/*', 22 | 'accept-language': 'zh-TW,zh;q=0.9,ja;q=0.8,zh-CN;q=0.7,en-US;q=0.6,en;q=0.5', 23 | authorization: 'Bearer ' + token.data, 24 | 'cache-control': 'no-cache', 25 | 'content-type': 'application/json', 26 | pragma: 'no-cache', 27 | 'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"', 28 | 'sec-ch-ua-mobile': '?0', 29 | 'sec-ch-ua-platform': '"Windows"', 30 | 'sec-fetch-dest': 'empty', 31 | 'sec-fetch-mode': 'cors', 32 | 'sec-fetch-site': 'cross-site', 33 | Referer: 'https://appsumo.com/', 34 | 'Referrer-Policy': 'strict-origin-when-cross-origin', 35 | 'User-Agent': 36 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42', 37 | }, 38 | query: { 39 | from: from, 40 | to: to, 41 | 'api-version': '3.0', 42 | includeSentenceLength: 'true', 43 | }, 44 | body: { type: 'Json', payload: [{ Text: text }] }, 45 | }); 46 | 47 | if (res.ok) { 48 | let result = res.data; 49 | if (result[0].translations) { 50 | return result[0].translations[0].text.trim(); 51 | } else { 52 | throw JSON.stringify(result); 53 | } 54 | } else { 55 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 56 | } 57 | } else { 58 | throw 'Get Token Failed'; 59 | } 60 | } 61 | 62 | export * from './Config'; 63 | export * from './info'; 64 | -------------------------------------------------------------------------------- /src/services/translate/bing/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'bing', 3 | icon: 'logo/bing.svg', 4 | }; 5 | // https://learn.microsoft.com/en-us/azure/ai-services/translator/language-support 6 | export enum Language { 7 | auto = '', 8 | zh_cn = 'zh-Hans', 9 | zh_tw = 'zh-Hant', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'ja', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt-pt', 21 | pt_br = 'pt', 22 | vi = 'vi', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'ms', 26 | ar = 'ar', 27 | hi = 'hi', 28 | mn_cy = 'mn-Cyrl', 29 | mn_mo = 'mn-Mong', 30 | km = 'km', 31 | nb_no = 'nb', 32 | fa = 'fa', 33 | sv = 'sv', 34 | pl = 'pl', 35 | nl = 'nl', 36 | uk = 'uk', 37 | he = 'he', 38 | } 39 | -------------------------------------------------------------------------------- /src/services/translate/bing_dict/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/bing_dict/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | const DISPLAY_FORMAT_DEFAULT = '发音, 快速释义, 变形'; 3 | 4 | export async function translate(text, from, to) { 5 | if (from == 'auto') { 6 | if (/^[\u4e00-\u9fff]/.test(text)) { 7 | from = 'zh-cn'; 8 | } else if (/^[A-Za-z]/.test(text)) { 9 | from = 'en-us'; 10 | } 11 | } 12 | if (from == to) { 13 | return text; 14 | } 15 | // only supports word translation 16 | // if (text.split(/[\s,,]/).length > 1) { 17 | // return ''; 18 | // } 19 | 20 | const res = await fetch( 21 | `https://www.bing.com/api/v6/dictionarywords/search?q=${text}&appid=371E7B2AF0F9B84EC491D731DF90A55719C7D209&mkt=zh-cn&pname=bingdict` 22 | ); 23 | if (res.ok) { 24 | const result = res.data; 25 | const meaningGroups = result.value[0].meaningGroups; 26 | if (meaningGroups.length === 0) { 27 | throw `Words not yet included: ${text}`; 28 | } 29 | const formats = DISPLAY_FORMAT_DEFAULT.trim().split(/,\s*/); 30 | const formatGroups = meaningGroups.reduce( 31 | (acc, cur) => { 32 | const group = acc[cur.partsOfSpeech?.[0]?.description || cur.partsOfSpeech?.[0]?.name]; 33 | if (Array.isArray(group)) { 34 | group.push(cur); 35 | } 36 | return acc; 37 | }, 38 | formats.reduce((acc, cur) => { 39 | acc[cur] = []; 40 | return acc; 41 | }, {}) 42 | ); 43 | let target = { pronunciations: [], explanations: [], associations: [], sentence: [] }; 44 | for (const pronunciation of formatGroups['发音']) { 45 | target.pronunciations.push({ 46 | region: pronunciation.partsOfSpeech[0].name, 47 | symbol: pronunciation.meanings[0].richDefinitions[0].fragments[0].text, 48 | voice: '', 49 | }); 50 | } 51 | for (const explanation of formatGroups['快速释义']) { 52 | target.explanations.push({ 53 | trait: explanation.partsOfSpeech[0].name, 54 | explains: explanation.meanings[0].richDefinitions[0].fragments.map((x) => { 55 | return x.text; 56 | }), 57 | }); 58 | } 59 | if (formatGroups['变形'][0]) { 60 | for (const association of formatGroups['变形'][0].meanings[0].richDefinitions[0].fragments) { 61 | target.associations.push(association.text); 62 | } 63 | } 64 | return target; 65 | } else { 66 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 67 | } 68 | } 69 | 70 | export * from './Config'; 71 | export * from './info'; 72 | -------------------------------------------------------------------------------- /src/services/translate/bing_dict/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'bing_dict', 3 | icon: 'logo/bing.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh-cn', 9 | zh_tw = 'zh-cn', 10 | en = 'en-us', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/translate/caiyun/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to, options = {}) { 4 | const { config } = options; 5 | 6 | const { token } = config; 7 | 8 | const url = 'https://api.interpreter.caiyunai.com/v1/translator'; 9 | 10 | if (token === '') { 11 | throw 'Please configure token'; 12 | } 13 | 14 | const body = { 15 | source: [text], 16 | trans_type: `${from}2${to}`, 17 | request_id: 'demo', 18 | detect: true, 19 | }; 20 | 21 | const headers = { 22 | 'content-type': 'application/json', 23 | 'x-authorization': 'token ' + token, 24 | }; 25 | 26 | let res = await fetch(url, { 27 | method: 'POST', 28 | headers: headers, 29 | body: Body.json(body), 30 | }); 31 | 32 | if (res.ok) { 33 | let result = res.data; 34 | const { target } = result; 35 | if (target[0]) { 36 | return target[0]; 37 | } else { 38 | throw JSON.stringify(result.trim()); 39 | } 40 | } else { 41 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 42 | } 43 | } 44 | 45 | export * from './Config'; 46 | export * from './info'; 47 | -------------------------------------------------------------------------------- /src/services/translate/caiyun/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'caiyun', 3 | icon: 'logo/caiyun.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh', 10 | en = 'en', 11 | ja = 'ja', 12 | } 13 | -------------------------------------------------------------------------------- /src/services/translate/cambridge_dict/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/cambridge_dict/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'cambridge_dict', 3 | icon: 'logo/cambridge_dict.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | en = 'english', 9 | zh_cn = 'chinese-simplified', 10 | zh_tw = 'chinese-traditional', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/translate/chatglm/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'chatglm', 3 | icon: 'logo/chatglm.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'Auto', 8 | zh_cn = 'Simplified Chinese', 9 | zh_tw = 'Traditional Chinese', 10 | yue = 'Cantonese', 11 | ja = 'Japanese', 12 | en = 'English', 13 | ko = 'Korean', 14 | fr = 'French', 15 | es = 'Spanish', 16 | ru = 'Russian', 17 | de = 'German', 18 | it = 'Italian', 19 | tr = 'Turkish', 20 | pt_pt = 'Portuguese', 21 | pt_br = 'Brazilian Portuguese', 22 | vi = 'Vietnamese', 23 | id = 'Indonesian', 24 | th = 'Thai', 25 | ms = 'Malay', 26 | ar = 'Arabic', 27 | hi = 'Hindi', 28 | mn_mo = 'Mongolian', 29 | mn_cy = 'Mongolian(Cyrillic)', 30 | km = 'Khmer', 31 | nb_no = 'Norwegian Bokmål', 32 | nn_no = 'Norwegian Nynorsk', 33 | fa = 'Persian', 34 | sv = 'Swedish', 35 | pl = 'Polish', 36 | nl = 'Dutch', 37 | uk = 'Ukrainian', 38 | he = 'Hebrew', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/deepl/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'deepl', 3 | icon: 'logo/deepl.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'ZH', 9 | zh_tw = 'ZH', 10 | ja = 'JA', 11 | en = 'EN', 12 | ko = 'KO', 13 | fr = 'FR', 14 | es = 'ES', 15 | ru = 'RU', 16 | de = 'DE', 17 | it = 'IT', 18 | tr = 'TR', 19 | pt_pt = 'PT-PT', 20 | pt_br = 'PT-BR', 21 | id = 'ID', 22 | sv = 'SV', 23 | pl = 'PL', 24 | nl = 'NL', 25 | uk = 'UK', 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/ecdict/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/ecdict/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, _from, _to) { 4 | const res = await fetch(`https://pot-app.com/api/dict`, { 5 | method: 'POST', 6 | body: Body.json({ text }), 7 | }); 8 | 9 | if (res.ok) { 10 | let result = res.data; 11 | return result; 12 | } else { 13 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 14 | } 15 | } 16 | 17 | export * from './Config'; 18 | export * from './info'; 19 | -------------------------------------------------------------------------------- /src/services/translate/ecdict/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'ecdict', 3 | icon: 'logo/ecdict.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = '', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh', 10 | en = 'en', 11 | } 12 | -------------------------------------------------------------------------------- /src/services/translate/geminipro/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'geminipro', 3 | icon: 'logo/geminipro.webp', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'Auto', 8 | zh_cn = 'Simplified Chinese', 9 | zh_tw = 'Traditional Chinese', 10 | yue = 'Cantonese', 11 | ja = 'Japanese', 12 | en = 'English', 13 | ko = 'Korean', 14 | fr = 'French', 15 | es = 'Spanish', 16 | ru = 'Russian', 17 | de = 'German', 18 | it = 'Italian', 19 | tr = 'Turkish', 20 | pt_pt = 'Portuguese', 21 | pt_br = 'Brazilian Portuguese', 22 | vi = 'Vietnamese', 23 | id = 'Indonesian', 24 | th = 'Thai', 25 | ms = 'Malay', 26 | ar = 'Arabic', 27 | hi = 'Hindi', 28 | mn_mo = 'Mongolian', 29 | mn_cy = 'Mongolian(Cyrillic)', 30 | km = 'Khmer', 31 | nb_no = 'Norwegian Bokmål', 32 | nn_no = 'Norwegian Nynorsk', 33 | fa = 'Persian', 34 | sv = 'Swedish', 35 | pl = 'Polish', 36 | nl = 'Dutch', 37 | uk = 'Ukrainian', 38 | he = 'Hebrew', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/google/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to, options = {}) { 4 | const { config } = options; 5 | 6 | let { custom_url } = config; 7 | 8 | if (custom_url === undefined || custom_url === '') { 9 | custom_url = 'https://translate.google.com'; 10 | } 11 | if (!custom_url.startsWith('http')) { 12 | custom_url = 'https://' + custom_url; 13 | } 14 | 15 | let res = await fetch( 16 | `${custom_url}/translate_a/single?dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t`, 17 | { 18 | method: 'GET', 19 | headers: { 'content-type': 'application/json' }, 20 | query: { 21 | client: 'gtx', 22 | sl: from, 23 | tl: to, 24 | hl: to, 25 | ie: 'UTF-8', 26 | oe: 'UTF-8', 27 | otf: '1', 28 | ssel: '0', 29 | tsel: '0', 30 | kc: '7', 31 | q: text, 32 | }, 33 | } 34 | ); 35 | if (res.ok) { 36 | let result = res.data; 37 | // 词典模式 38 | if (result[1]) { 39 | let target = { pronunciations: [], explanations: [], associations: [], sentence: [] }; 40 | // 发音 41 | if (result[0][1][3]) { 42 | target.pronunciations.push({ symbol: result[0][1][3], voice: '' }); 43 | } 44 | // 释义 45 | for (let i of result[1]) { 46 | target.explanations.push({ 47 | trait: i[0], 48 | explains: i[2].map((x) => { 49 | return x[0]; 50 | }), 51 | }); 52 | } 53 | // 例句 54 | if (result[13]) { 55 | for (let i of result[13][0]) { 56 | target.sentence.push({ source: i[0] }); 57 | } 58 | } 59 | return target; 60 | } else { 61 | // 翻译模式 62 | let target = ''; 63 | for (let r of result[0]) { 64 | if (r[0]) { 65 | target = target + r[0]; 66 | } 67 | } 68 | return target.trim(); 69 | } 70 | } else { 71 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 72 | } 73 | } 74 | 75 | export * from './Config'; 76 | export * from './info'; 77 | -------------------------------------------------------------------------------- /src/services/translate/google/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'google', 3 | icon: 'logo/google.svg', 4 | }; 5 | // https://cloud.google.com/translate/docs/languages?hl=zh-cn 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh-CN', 9 | zh_tw = 'zh-TW', 10 | ja = 'ja', 11 | en = 'en', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | mn_cy = 'mn', 28 | km = 'km', 29 | nb_no = 'no', 30 | nn_no = 'no', 31 | fa = 'fa', 32 | sv = 'sv', 33 | pl = 'pl', 34 | nl = 'nl', 35 | uk = 'uk', 36 | he = 'he', 37 | } 38 | -------------------------------------------------------------------------------- /src/services/translate/index.jsx: -------------------------------------------------------------------------------- 1 | import * as _deepl from './deepl'; 2 | import * as _bing from './bing'; 3 | import * as _yandex from './yandex'; 4 | import * as _openai from './openai'; 5 | import * as _google from './google'; 6 | import * as _transmart from './transmart'; 7 | import * as _alibaba from './alibaba'; 8 | import * as _baidu from './baidu'; 9 | import * as _baidu_field from './baidu_field'; 10 | import * as _tencent from './tencent'; 11 | import * as _volcengine from './volcengine'; 12 | import * as _niutrans from './niutrans'; 13 | import * as _youdao from './youdao'; 14 | import * as _bing_dict from './bing_dict'; 15 | import * as _cambridge_dict from './cambridge_dict'; 16 | import * as _caiyun from './caiyun'; 17 | import * as _chatglm from './chatglm'; 18 | import * as _geminipro from './geminipro'; 19 | import * as _ollama from './ollama'; 20 | import * as _ecdict from './ecdict'; 21 | import * as _lingva from './lingva'; 22 | 23 | export const deepl = _deepl; 24 | export const bing = _bing; 25 | export const yandex = _yandex; 26 | export const openai = _openai; 27 | export const google = _google; 28 | export const transmart = _transmart; 29 | export const alibaba = _alibaba; 30 | export const baidu = _baidu; 31 | export const baidu_field = _baidu_field; 32 | export const tencent = _tencent; 33 | export const volcengine = _volcengine; 34 | export const niutrans = _niutrans; 35 | export const youdao = _youdao; 36 | export const bing_dict = _bing_dict; 37 | export const cambridge_dict = _cambridge_dict; 38 | export const caiyun = _caiyun; 39 | export const chatglm = _chatglm; 40 | export const geminipro = _geminipro; 41 | export const ollama = _ollama; 42 | export const ecdict = _ecdict; 43 | export const lingva = _lingva; 44 | -------------------------------------------------------------------------------- /src/services/translate/lingva/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/lingva/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to) { 4 | let plain_text = text.replaceAll('/', '@@'); 5 | let encode_text = encodeURIComponent(plain_text); 6 | const res = await fetch(`https://lingva.pot-app.com/api/v1/${from}/${to}/${encode_text}`, { 7 | method: 'GET', 8 | }); 9 | 10 | if (res.ok) { 11 | let result = res.data; 12 | const { translation } = result; 13 | if (translation) { 14 | return translation.replaceAll('@@', '/'); 15 | } else { 16 | throw JSON.stringify(result.trim()); 17 | } 18 | } else { 19 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 20 | } 21 | } 22 | 23 | export * from './Config'; 24 | export * from './info'; 25 | -------------------------------------------------------------------------------- /src/services/translate/lingva/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'lingva', 3 | icon: 'logo/lingva.svg', 4 | }; 5 | 6 | // https://cloud.google.com/translate/docs/languages?hl=zh-cn 7 | export enum Language { 8 | auto = 'auto', 9 | zh_cn = 'zh', 10 | zh_tw = 'zh_HANT', 11 | en = 'en', 12 | ja = 'ja', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pt', 22 | vi = 'vi', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'ms', 26 | ar = 'ar', 27 | hi = 'hi', 28 | mn_cy = 'mn', 29 | mn_mo = 'mn', 30 | km = 'km', 31 | nb_no = 'no', 32 | nn_no = 'no', 33 | fa = 'fa', 34 | sv = 'sv', 35 | pl = 'pl', 36 | nl = 'nl', 37 | uk = 'uk', 38 | he = 'he', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/niutrans/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to, options = {}) { 4 | const { config } = options; 5 | 6 | const { https, apikey } = config; 7 | 8 | const url = `${https ? 'https' : 'http'}://api.niutrans.com/NiuTransServer/translation`; 9 | 10 | let res = await fetch(url, { 11 | method: 'POST', 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | }, 15 | body: Body.json({ 16 | from: from, 17 | to: to, 18 | apikey: apikey, 19 | src_text: text, 20 | }), 21 | }); 22 | 23 | // 返回翻译结果 24 | if (res.ok) { 25 | let result = res.data; 26 | if (result && result['tgt_text']) { 27 | return result['tgt_text'].trim(); 28 | } else { 29 | throw JSON.stringify(result); 30 | } 31 | } else { 32 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 33 | } 34 | } 35 | 36 | export * from './Config'; 37 | export * from './info'; 38 | -------------------------------------------------------------------------------- /src/services/translate/niutrans/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'niutrans', 3 | icon: 'logo/niutrans.svg', 4 | }; 5 | // https://niutrans.com/documents/contents/trans_text#languageList 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'cht', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'ja', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pt', 22 | vi = 'vi', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'ms', 26 | ar = 'ar', 27 | hi = 'hi', 28 | mn_cy = 'mn', 29 | mn_mo = 'mo', 30 | km = 'km', 31 | nb_no = 'nb', 32 | nn_no = 'nn', 33 | fa = 'fa', 34 | sv = 'sv', 35 | pl = 'pl', 36 | nl = 'nl', 37 | uk = 'uk', 38 | he = 'he', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/ollama/index.jsx: -------------------------------------------------------------------------------- 1 | import { Language } from './info'; 2 | import { Ollama } from 'ollama/browser'; 3 | 4 | export async function translate(text, from, to, options = {}) { 5 | const { config, setResult, detect } = options; 6 | 7 | let { stream, promptList, requestPath, model } = config; 8 | 9 | if (!/https?:\/\/.+/.test(requestPath)) { 10 | requestPath = `https://${requestPath}`; 11 | } 12 | if (requestPath.endsWith('/')) { 13 | requestPath = requestPath.slice(0, -1); 14 | } 15 | const ollama = new Ollama({ host: requestPath }); 16 | 17 | promptList = promptList.map((item) => { 18 | return { 19 | ...item, 20 | content: item.content 21 | .replaceAll('$text', text) 22 | .replaceAll('$from', from) 23 | .replaceAll('$to', to) 24 | .replaceAll('$detect', Language[detect]), 25 | }; 26 | }); 27 | 28 | const response = await ollama.chat({ model, messages: promptList, stream: stream }); 29 | 30 | if (stream) { 31 | let target = ''; 32 | for await (const part of response) { 33 | target += part.message.content; 34 | if (setResult) { 35 | setResult(target + '_'); 36 | } else { 37 | ollama.abort(); 38 | return '[STREAM]'; 39 | } 40 | } 41 | setResult(target.trim()); 42 | return target.trim(); 43 | } else { 44 | return response.message.content; 45 | } 46 | } 47 | 48 | export * from './Config'; 49 | export * from './info'; 50 | -------------------------------------------------------------------------------- /src/services/translate/ollama/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'ollama', 3 | icon: 'logo/ollama.png', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'Auto', 8 | zh_cn = 'Simplified Chinese', 9 | zh_tw = 'Traditional Chinese', 10 | yue = 'Cantonese', 11 | ja = 'Japanese', 12 | en = 'English', 13 | ko = 'Korean', 14 | fr = 'French', 15 | es = 'Spanish', 16 | ru = 'Russian', 17 | de = 'German', 18 | it = 'Italian', 19 | tr = 'Turkish', 20 | pt_pt = 'Portuguese', 21 | pt_br = 'Brazilian Portuguese', 22 | vi = 'Vietnamese', 23 | id = 'Indonesian', 24 | th = 'Thai', 25 | ms = 'Malay', 26 | ar = 'Arabic', 27 | hi = 'Hindi', 28 | mn_mo = 'Mongolian', 29 | mn_cy = 'Mongolian(Cyrillic)', 30 | km = 'Khmer', 31 | nb_no = 'Norwegian Bokmål', 32 | nn_no = 'Norwegian Nynorsk', 33 | fa = 'Persian', 34 | sv = 'Swedish', 35 | pl = 'Polish', 36 | nl = 'Dutch', 37 | uk = 'Ukrainian', 38 | he = 'Hebrew', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/openai/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'openai', 3 | icon: 'logo/openai.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'Auto', 8 | zh_cn = 'Simplified Chinese', 9 | zh_tw = 'Traditional Chinese', 10 | yue = 'Cantonese', 11 | ja = 'Japanese', 12 | en = 'English', 13 | ko = 'Korean', 14 | fr = 'French', 15 | es = 'Spanish', 16 | ru = 'Russian', 17 | de = 'German', 18 | it = 'Italian', 19 | tr = 'Turkish', 20 | pt_pt = 'Portuguese', 21 | pt_br = 'Brazilian Portuguese', 22 | vi = 'Vietnamese', 23 | id = 'Indonesian', 24 | th = 'Thai', 25 | ms = 'Malay', 26 | ar = 'Arabic', 27 | hi = 'Hindi', 28 | mn_mo = 'Mongolian', 29 | mn_cy = 'Mongolian(Cyrillic)', 30 | km = 'Khmer', 31 | nb_no = 'Norwegian Bokmål', 32 | nn_no = 'Norwegian Nynorsk', 33 | fa = 'Persian', 34 | sv = 'Swedish', 35 | pl = 'Polish', 36 | nl = 'Dutch', 37 | uk = 'Ukrainian', 38 | he = 'Hebrew', 39 | } 40 | -------------------------------------------------------------------------------- /src/services/translate/tencent/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'tencent', 3 | icon: 'logo/tencent.svg', 4 | }; 5 | // 腾讯只支持这么多语言 6 | // https://cloud.tencent.com/document/product/551/15619 7 | export enum Language { 8 | auto = 'auto', 9 | zh_cn = 'zh', 10 | zh_tw = 'zh-TW', 11 | en = 'en', 12 | ja = 'ja', 13 | ko = 'ko', 14 | fr = 'fr', 15 | es = 'es', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pt', 22 | vi = 'vi', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'ms', 26 | ar = 'ar', 27 | hi = 'hi', 28 | } 29 | -------------------------------------------------------------------------------- /src/services/translate/transmart/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | 3 | export async function translate(text, from, to, options = {}) { 4 | const { config } = options; 5 | 6 | const { username: user, token } = config; 7 | 8 | let header = {}; 9 | if (user !== '' && token !== '') { 10 | header['user'] = user; 11 | header['token'] = token; 12 | } 13 | 14 | const url = 'https://transmart.qq.com/api/imt'; 15 | 16 | const res = await fetch(url, { 17 | method: 'POST', 18 | body: Body.json({ 19 | header: { 20 | fn: 'auto_translation', 21 | ...header, 22 | }, 23 | type: 'plain', 24 | source: { 25 | lang: from, 26 | text_list: [text], 27 | }, 28 | target: { 29 | lang: to, 30 | }, 31 | }), 32 | }); 33 | if (res.ok) { 34 | const result = res.data; 35 | if (result['auto_translation']) { 36 | let target = ''; 37 | for (let line of result['auto_translation']) { 38 | target += line; 39 | target += '\n'; 40 | } 41 | return target.trim(); 42 | } else { 43 | throw JSON.stringify(result); 44 | } 45 | } else { 46 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 47 | } 48 | } 49 | 50 | export * from './Config'; 51 | export * from './info'; 52 | -------------------------------------------------------------------------------- /src/services/translate/transmart/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'transmart', 3 | icon: 'logo/transmart.svg', 4 | }; 5 | // https://docs.qq.com/doc/DY2NxUWpmdnB2RXV3 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh-TW', 10 | en = 'en', 11 | ja = 'ja', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | km = 'km', 27 | } 28 | -------------------------------------------------------------------------------- /src/services/translate/volcengine/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'volcengine', 3 | icon: 'logo/volcengine.svg', 4 | }; 5 | // https://www.volcengine.com/docs/4640/35107 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh-Hant', 10 | ja = 'ja', 11 | en = 'en', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | mn_cy = 'mn', 28 | nb_no = 'nb', 29 | nn_no = 'no', 30 | fa = 'fa', 31 | sv = 'sv', 32 | pl = 'pl', 33 | nl = 'nl', 34 | uk = 'uk', 35 | he = 'he', 36 | } 37 | -------------------------------------------------------------------------------- /src/services/translate/yandex/Config.jsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@nextui-org/react'; 3 | import React from 'react'; 4 | 5 | export function Config(props) { 6 | const { updateServiceList, onClose } = props; 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
{t('services.no_need')}
12 |
13 | 23 |
24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/translate/yandex/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch, Body } from '@tauri-apps/api/http'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | 4 | export async function translate(text, from, to) { 5 | const url = 'https://translate.yandex.net/api/v1/tr.json/translate'; 6 | const res = await fetch(url, { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/x-www-form-urlencoded', 10 | }, 11 | query: { 12 | id: uuidv4().replaceAll('-', '') + '-0-0', 13 | srv: 'android', 14 | }, 15 | body: Body.form({ 16 | source_lang: from, 17 | target_lang: to, 18 | text, 19 | }), 20 | }); 21 | if (res.ok) { 22 | const result = res.data; 23 | if (result.text) { 24 | return result.text[0]; 25 | } else { 26 | throw JSON.stringify(result); 27 | } 28 | } else { 29 | throw `Http Request Error\nHttp Status: ${res.status}\n${JSON.stringify(res.data)}`; 30 | } 31 | } 32 | 33 | export * from './Config'; 34 | export * from './info'; 35 | -------------------------------------------------------------------------------- /src/services/translate/yandex/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'yandex', 3 | icon: 'logo/yandex.svg', 4 | }; 5 | // https://yandex.com/dev/translate/doc/en/concepts/api-overview 6 | export enum Language { 7 | auto = '', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh', 10 | en = 'en', 11 | ja = 'ja', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | nb_no = 'no', 28 | nn_no = 'no', 29 | fa = 'fa', 30 | sv = 'sv', 31 | pl = 'pl', 32 | nl = 'nl', 33 | uk = 'uk', 34 | he = 'he', 35 | } 36 | -------------------------------------------------------------------------------- /src/services/translate/youdao/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'youdao', 3 | icon: 'logo/youdao.svg', 4 | }; 5 | // https://ai.youdao.com/DOCSIRMA/html/trans/api/wbfy/index.html 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh-CHS', 9 | zh_tw = 'zh-CHT', 10 | yue = 'yue', 11 | en = 'en', 12 | ja = 'jp', 13 | ko = 'kor', 14 | fr = 'fra', 15 | es = 'spa', 16 | ru = 'ru', 17 | de = 'de', 18 | it = 'it', 19 | tr = 'tr', 20 | pt_pt = 'pt', 21 | pt_br = 'pt', 22 | vi = 'vie', 23 | id = 'id', 24 | th = 'th', 25 | ms = 'may', 26 | ar = 'ar', 27 | hi = 'hi', 28 | mn_mo = 'mn', 29 | km = 'km', 30 | nb_no = 'no', 31 | nn_no = 'no', 32 | fa = 'fa', 33 | sv = 'sv', 34 | pl = 'pl', 35 | nl = 'nl', 36 | uk = 'uk', 37 | he = 'he', 38 | } 39 | -------------------------------------------------------------------------------- /src/services/tts/index.jsx: -------------------------------------------------------------------------------- 1 | import * as _lingva_tts from './lingva'; 2 | 3 | export const lingva_tts = _lingva_tts; 4 | -------------------------------------------------------------------------------- /src/services/tts/lingva/index.jsx: -------------------------------------------------------------------------------- 1 | import { fetch } from '@tauri-apps/api/http'; 2 | 3 | export async function tts(text, lang, options = {}) { 4 | const { config } = options; 5 | 6 | let { requestPath = 'lingva.pot-app.com' } = config; 7 | 8 | if (requestPath.length === 0) { 9 | requestPath = 'lingva.pot-app.com'; 10 | } 11 | 12 | if (!requestPath.startsWith('http')) { 13 | requestPath = 'https://' + requestPath; 14 | } 15 | const res = await fetch(`${requestPath}/api/v1/audio/${lang}/${encodeURIComponent(text)}`); 16 | 17 | if (res.ok) { 18 | return res.data['audio']; 19 | } 20 | } 21 | 22 | export * from './Config'; 23 | export * from './info'; 24 | -------------------------------------------------------------------------------- /src/services/tts/lingva/info.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | name: 'lingva_tts', 3 | icon: 'logo/lingva.svg', 4 | }; 5 | 6 | export enum Language { 7 | auto = 'auto', 8 | zh_cn = 'zh', 9 | zh_tw = 'zh_HANT', 10 | ja = 'ja', 11 | en = 'en', 12 | ko = 'ko', 13 | fr = 'fr', 14 | es = 'es', 15 | ru = 'ru', 16 | de = 'de', 17 | it = 'it', 18 | tr = 'tr', 19 | pt_pt = 'pt', 20 | pt_br = 'pt', 21 | vi = 'vi', 22 | id = 'id', 23 | th = 'th', 24 | ms = 'ms', 25 | ar = 'ar', 26 | hi = 'hi', 27 | mn_cy = 'mn', 28 | km = 'km', 29 | nb_no = 'no', 30 | nn_no = 'no', 31 | fa = 'fa', 32 | sv = 'sv', 33 | pl = 'pl', 34 | nl = 'nl', 35 | uk = 'uk', 36 | he = 'he', 37 | } 38 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | html { 11 | border-radius: 10px !important; 12 | background: transparent !important; 13 | overflow: hidden; 14 | } 15 | 16 | *::-webkit-scrollbar { 17 | width: 5px; 18 | } 19 | 20 | *::-webkit-scrollbar-thumb { 21 | background: #c0c1c550; 22 | border-radius: 5px; 23 | } 24 | 25 | *::-webkit-scrollbar-thumb:hover { 26 | background: #c0c1c550; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/env.js: -------------------------------------------------------------------------------- 1 | import { type, arch as archFn, version } from '@tauri-apps/api/os'; 2 | import { getVersion } from '@tauri-apps/api/app'; 3 | 4 | export let osType = ''; 5 | export let arch = ''; 6 | export let osVersion = ''; 7 | export let appVersion = ''; 8 | 9 | export async function initEnv() { 10 | osType = await type(); 11 | arch = await archFn(); 12 | osVersion = await version(); 13 | appVersion = await getVersion(); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const debounce = (fn, delay = 500) => { 2 | let timer = null; 3 | return (...args) => { 4 | timer && clearTimeout(timer); 5 | timer = setTimeout(() => fn(...args), delay); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/invoke_plugin.js: -------------------------------------------------------------------------------- 1 | import { appCacheDir, appConfigDir, join } from "@tauri-apps/api/path"; 2 | import { readBinaryFile, readTextFile } from "@tauri-apps/api/fs"; 3 | import { invoke } from "@tauri-apps/api/tauri"; 4 | import Database from "tauri-plugin-sql-api"; 5 | import { http } from "@tauri-apps/api"; 6 | import CryptoJS from "crypto-js"; 7 | import { osType } from "./env"; 8 | 9 | export async function invoke_plugin(pluginType, pluginName) { 10 | let configDir = await appConfigDir(); 11 | let cacheDir = await appCacheDir(); 12 | let pluginDir = await join(configDir, "plugins", pluginType, pluginName); 13 | let entryFile = await join(pluginDir, "main.js"); 14 | let script = await readTextFile(entryFile); 15 | async function run(cmdName, args) { 16 | return await invoke("run_binary", { 17 | pluginType, 18 | pluginName, 19 | cmdName, 20 | args 21 | }); 22 | } 23 | const utils = { 24 | tauriFetch: http.fetch, 25 | http, 26 | readBinaryFile, 27 | readTextFile, 28 | Database, 29 | CryptoJS, 30 | run, 31 | cacheDir, // String 32 | pluginDir, // String 33 | osType,// "Windows_NT", "Darwin", "Linux" 34 | } 35 | return [eval(`${script} ${pluginType}`), utils]; 36 | } -------------------------------------------------------------------------------- /src/utils/language.ts: -------------------------------------------------------------------------------- 1 | // ISO-639-1 + Country Code (Option) 2 | // https://zh.wikipedia.org/wiki/ISO_639-1%E4%BB%A3%E7%A0%81%E8%A1%A8 3 | export const languageList = [ 4 | 'zh_cn', 5 | 'zh_tw', 6 | 'mn_mo', 7 | 'en', 8 | 'ja', 9 | 'ko', 10 | 'fr', 11 | 'es', 12 | 'ru', 13 | 'de', 14 | 'it', 15 | 'tr', 16 | 'pt_pt', 17 | 'pt_br', 18 | 'vi', 19 | 'id', 20 | 'th', 21 | 'ms', 22 | 'ar', 23 | 'hi', 24 | 'km', 25 | 'mn_cy', 26 | 'nb_no', 27 | 'nn_no', 28 | 'fa', 29 | 'sv', 30 | 'pl', 31 | 'nl', 32 | 'uk', 33 | 'he', 34 | ]; 35 | 36 | // https://flagicons.lipis.dev/ 37 | export enum LanguageFlag { 38 | zh_cn = 'cn', 39 | zh_tw = 'cn', 40 | mn_mo = 'cn', 41 | en = 'gb', 42 | ja = 'jp', 43 | ko = 'kr', 44 | fr = 'fr', 45 | es = 'es', 46 | ru = 'ru', 47 | de = 'de', 48 | it = 'it', 49 | tr = 'tr', 50 | pt_pt = 'pt', 51 | pt_br = 'br', 52 | vi = 'vn', 53 | id = 'id', 54 | th = 'th', 55 | ms = 'ms', 56 | ar = 'ae', 57 | hi = 'in', 58 | km = 'kh', 59 | mn_cy = 'mn', 60 | nb_no = 'no', 61 | nn_no = 'no', 62 | fa = 'ir', 63 | sv = 'se', 64 | pl = 'pl', 65 | nl = 'nl', 66 | uk = 'ua', 67 | he = 'il', 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/service_instance.ts: -------------------------------------------------------------------------------- 1 | export enum ServiceType { 2 | TRANSLATE = 'translate', 3 | RECOGNIZE = 'recognize', 4 | TTS = 'tts', 5 | COLLECTION = 'collection', 6 | } 7 | 8 | export enum ServiceSourceType { 9 | BUILDIN = 'buildin', 10 | PLUGIN = 'plugin', 11 | } 12 | 13 | export function getServiceSouceType(serviceInstanceKey: string): ServiceSourceType { 14 | if (serviceInstanceKey.startsWith('plugin')) { 15 | return ServiceSourceType.PLUGIN; 16 | } else { 17 | return ServiceSourceType.BUILDIN; 18 | } 19 | } 20 | 21 | export function whetherPluginService(serviceInstanceKey: string): boolean { 22 | return getServiceSouceType(serviceInstanceKey) === ServiceSourceType.PLUGIN; 23 | } 24 | 25 | // The serviceInstanceKey consists of the service name and it's id, separated by @ 26 | // In earlier versions, the @ separator and id were optional, so they all have only one instance. 27 | export function createServiceInstanceKey(serviceName: string): string { 28 | const randomId = Math.random().toString(36).substring(2); 29 | return `${serviceName}@${randomId}`; 30 | } 31 | 32 | // if the serviceInstanceKey is from a plugin, serviceName is it's pluginId 33 | export function getServiceName(serviceInstanceKey: string): string { 34 | return serviceInstanceKey.split('@')[0]; 35 | } 36 | 37 | export function getDisplayInstanceName(instanceName: string, serviceNameSupplier: () => string): string { 38 | return instanceName || serviceNameSupplier(); 39 | } 40 | 41 | export const INSTANCE_NAME_CONFIG_KEY = 'instanceName'; 42 | 43 | export function whetherAvailableService( 44 | serviceInstanceKey: string, 45 | availableServices: Record> 46 | ) { 47 | const serviceSourceType = getServiceSouceType(serviceInstanceKey); 48 | const serviceName = getServiceName(serviceInstanceKey); 49 | return availableServices[serviceSourceType]?.[serviceName] !== undefined; 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | import { Store } from 'tauri-plugin-store-api'; 2 | import { appConfigDir, join } from '@tauri-apps/api/path'; 3 | import { watch } from 'tauri-plugin-fs-watch-api'; 4 | import { invoke } from '@tauri-apps/api'; 5 | 6 | export let store = new Store(); 7 | 8 | export async function initStore() { 9 | const appConfigDirPath = await appConfigDir(); 10 | const appConfigPath = await join(appConfigDirPath, 'config.json'); 11 | store = new Store(appConfigPath); 12 | const _ = await watch(appConfigPath, async () => { 13 | await store.load(); 14 | await invoke('reload_store'); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/window/Config/index.jsx: -------------------------------------------------------------------------------- 1 | import { useLocation, useRoutes } from 'react-router-dom'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { appWindow } from '@tauri-apps/api/window'; 4 | import { Card, Divider } from '@nextui-org/react'; 5 | import { useTranslation } from 'react-i18next'; 6 | 7 | import WindowControl from '../../components/WindowControl'; 8 | import SideBar from './components/SideBar'; 9 | import { osType } from '../../utils/env'; 10 | import { useConfig } from '../../hooks'; 11 | import routes from './routes'; 12 | import './style.css'; 13 | 14 | export default function Config() { 15 | const [transparent] = useConfig('transparent', true); 16 | const { t } = useTranslation(); 17 | const location = useLocation(); 18 | const page = useRoutes(routes); 19 | 20 | useEffect(() => { 21 | if (appWindow.label === 'config') { 22 | appWindow.show(); 23 | } 24 | }, []); 25 | 26 | return ( 27 | <> 28 | 36 |
37 |
41 |
42 |
43 |
44 | pot logo 50 |
51 |
52 | 53 | 54 |
59 |
63 |
64 |
65 |

{t(`config.${location.pathname.slice(1)}.title`)}

66 |
67 | 68 |
{osType !== 'Darwin' && }
69 |
70 | 71 |
76 | {page} 77 |
78 |
79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /src/window/Config/pages/Backup/utils/local.jsx: -------------------------------------------------------------------------------- 1 | import { save, open } from '@tauri-apps/api/dialog'; 2 | import { invoke } from '@tauri-apps/api'; 3 | 4 | export async function backup() { 5 | const selected = await save({ 6 | filters: [ 7 | { 8 | name: 'Backup', 9 | extensions: ['zip'], 10 | }, 11 | ], 12 | }); 13 | if (selected !== null) { 14 | return await invoke('local', { 15 | operate: 'put', 16 | path: selected, 17 | }); 18 | } else { 19 | throw 'Invalid File'; 20 | } 21 | } 22 | 23 | export async function get() { 24 | const selected = await open({ 25 | multiple: false, 26 | directory: false, 27 | filters: [ 28 | { 29 | name: '*.zip', 30 | extensions: ['zip'], 31 | }, 32 | ], 33 | }); 34 | 35 | if (selected !== null && selected.endsWith('zip')) { 36 | return await invoke('local', { 37 | operate: 'get', 38 | path: selected, 39 | }); 40 | } else { 41 | throw 'Invalid File'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/window/Config/pages/Backup/utils/webdav.jsx: -------------------------------------------------------------------------------- 1 | import { invoke } from '@tauri-apps/api'; 2 | 3 | export async function backup(url, username, password, name) { 4 | return await invoke('webdav', { 5 | operate: 'put', 6 | url, 7 | username, 8 | password, 9 | name, 10 | }); 11 | } 12 | 13 | export async function list(url, username, password) { 14 | const backup_list_text = await invoke('webdav', { 15 | operate: 'list', 16 | url, 17 | username, 18 | password, 19 | }); 20 | let backup_list = JSON.parse(backup_list_text); 21 | backup_list = backup_list.filter((item) => { 22 | return item.hasOwnProperty('File'); 23 | }); 24 | return backup_list.map((file) => { 25 | return file.File.href.split('/').slice(-1)[0]; 26 | }); 27 | } 28 | 29 | export async function get(url, username, password, name) { 30 | const _ = await invoke('webdav', { 31 | operate: 'get', 32 | url, 33 | username, 34 | password, 35 | name, 36 | }); 37 | } 38 | 39 | export async function remove(url, username, password, name) { 40 | return await invoke('webdav', { 41 | operate: 'delete', 42 | url, 43 | username, 44 | password, 45 | name, 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/window/Config/pages/Service/Collection/SelectModal/index.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import React from 'react'; 4 | 5 | import { createServiceInstanceKey } from '../../../../../../utils/service_instance'; 6 | import * as builtinServices from '../../../../../../services/collection'; 7 | 8 | export default function SelectModal(props) { 9 | const { isOpen, onOpenChange, setCurrentConfigKey, onConfigOpen } = props; 10 | const { t } = useTranslation(); 11 | 12 | return ( 13 | 18 | 19 | {(onClose) => ( 20 | <> 21 | {t('config.service.add_service')} 22 | 23 | {Object.keys(builtinServices).map((x) => { 24 | return ( 25 |
26 | 43 |
44 | ); 45 | })} 46 |
47 | 48 | 55 | 56 | 57 | )} 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/window/Config/pages/Service/Recognize/SelectModal/index.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import React from 'react'; 4 | 5 | import { createServiceInstanceKey } from '../../../../../../utils/service_instance'; 6 | import * as builtinServices from '../../../../../../services/recognize'; 7 | import { osType } from '../../../../../../utils/env'; 8 | 9 | export default function SelectModal(props) { 10 | const { isOpen, onOpenChange, setCurrentConfigKey, onConfigOpen } = props; 11 | const { t } = useTranslation(); 12 | 13 | return ( 14 | 19 | 20 | {(onClose) => ( 21 | <> 22 | {t('config.service.add_service')} 23 | 24 | {Object.keys(builtinServices).map((x) => { 25 | return ( 26 |
27 | 48 |
49 | ); 50 | })} 51 |
52 | 53 | 60 | 61 | 62 | )} 63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/window/Config/pages/Service/Translate/SelectModal/index.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import React from 'react'; 4 | 5 | import * as builtinServices from '../../../../../../services/translate'; 6 | import { createServiceInstanceKey } from '../../../../../../utils/service_instance'; 7 | 8 | export default function SelectModal(props) { 9 | const { isOpen, onOpenChange, setCurrentConfigKey, onConfigOpen } = props; 10 | const { t } = useTranslation(); 11 | 12 | return ( 13 | 18 | 19 | {(onClose) => ( 20 | <> 21 | {t('config.service.add_service')} 22 | 23 | {Object.keys(builtinServices).map((x) => { 24 | return ( 25 |
26 | 43 |
44 | ); 45 | })} 46 |
47 | 48 | 55 | 56 | 57 | )} 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/window/Config/pages/Service/Tts/SelectModal/index.jsx: -------------------------------------------------------------------------------- 1 | import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import React from 'react'; 4 | 5 | import { createServiceInstanceKey } from '../../../../../../utils/service_instance'; 6 | import * as builtinServices from '../../../../../../services/tts'; 7 | 8 | export default function SelectModal(props) { 9 | const { isOpen, onOpenChange, setCurrentConfigKey, onConfigOpen } = props; 10 | const { t } = useTranslation(); 11 | 12 | return ( 13 | 18 | 19 | {(onClose) => ( 20 | <> 21 | {t('config.service.add_service')} 22 | 23 | {Object.keys(builtinServices).map((x) => { 24 | return ( 25 |
26 | 43 |
44 | ); 45 | })} 46 |
47 | 48 | 55 | 56 | 57 | )} 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/window/Config/routes/index.jsx: -------------------------------------------------------------------------------- 1 | import { Navigate } from 'react-router-dom'; 2 | 3 | import Translate from '../pages/Translate'; 4 | import Recognize from '../pages/Recognize'; 5 | import General from '../pages/General'; 6 | import Service from '../pages/Service'; 7 | import History from '../pages/History'; 8 | import Hotkey from '../pages/Hotkey'; 9 | import Backup from '../pages/Backup'; 10 | import About from '../pages/About'; 11 | 12 | const routes = [ 13 | { 14 | path: '/general', 15 | element: , 16 | }, 17 | { 18 | path: '/translate', 19 | element: , 20 | }, 21 | { 22 | path: '/recognize', 23 | element: , 24 | }, 25 | { 26 | path: '/hotkey', 27 | element: , 28 | }, 29 | { 30 | path: '/service', 31 | element: , 32 | }, 33 | { 34 | path: '/history', 35 | element: , 36 | }, 37 | { 38 | path: '/backup', 39 | element: , 40 | }, 41 | { 42 | path: '/about', 43 | element: , 44 | }, 45 | { 46 | path: '/', 47 | element: , 48 | }, 49 | ]; 50 | 51 | export default routes; 52 | -------------------------------------------------------------------------------- /src/window/Config/style.css: -------------------------------------------------------------------------------- 1 | .config-item { 2 | margin: 10px 0; 3 | display: flex; 4 | justify-content: space-between; 5 | } 6 | -------------------------------------------------------------------------------- /src/window/Recognize/ImageArea/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card, CardBody, CardFooter, Button, Tooltip } from '@nextui-org/react'; 2 | import { appWindow } from '@tauri-apps/api/window'; 3 | import React, { useEffect, useRef } from 'react'; 4 | import { listen } from '@tauri-apps/api/event'; 5 | import { MdContentCopy } from 'react-icons/md'; 6 | import { useTranslation } from 'react-i18next'; 7 | import { invoke } from '@tauri-apps/api'; 8 | import { atom, useAtom } from 'jotai'; 9 | 10 | import { useConfig } from '../../../hooks'; 11 | 12 | export const base64Atom = atom(''); 13 | let unlisten = null; 14 | 15 | export default function ImageArea() { 16 | const [hideWindow] = useConfig('recognize_hide_window', false); 17 | const [base64, setBase64] = useAtom(base64Atom); 18 | const imgRef = useRef(); 19 | const { t } = useTranslation(); 20 | const load_img = () => { 21 | invoke('get_base64').then((v) => { 22 | setBase64(v); 23 | if (hideWindow) { 24 | appWindow.hide(); 25 | } else { 26 | appWindow.show(); 27 | appWindow.setFocus(true); 28 | } 29 | }); 30 | }; 31 | 32 | useEffect(() => { 33 | if (hideWindow !== null) { 34 | load_img(); 35 | if (unlisten) { 36 | unlisten.then((f) => { 37 | f(); 38 | }); 39 | } 40 | unlisten = listen('new_image', (_) => { 41 | load_img(); 42 | }); 43 | } 44 | }, [hideWindow]); 45 | 46 | return ( 47 | 52 | 53 | {base64 !== '' && ( 54 | 60 | )} 61 | 62 | 63 | 64 | 77 | 78 | 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | const { nextui } = require('@nextui-org/react'); 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: [ 7 | // ... 8 | './index.html', 9 | './src/**/*.{js,ts,jsx,tsx}', 10 | './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}', 11 | ], 12 | theme: { 13 | extend: {}, 14 | }, 15 | darkMode: 'class', 16 | plugins: [ 17 | nextui({ 18 | themes: { 19 | dark: { 20 | colors: { 21 | background: '#202020', 22 | foreground: '#e7e7e7', 23 | content1: '#282828', 24 | content2: '#303030', 25 | content3: '#383838', 26 | content4: '#404040', 27 | default: { 28 | DEFAULT: '#484848', 29 | 50: '#282828', 30 | 100: '#383838', 31 | 200: '#484848', 32 | 300: '#585858', 33 | 400: '#686868', 34 | 500: '#a7a7a7', 35 | 600: '#b7b7b7', 36 | 700: '#c7c7c7', 37 | 800: '#d7d7d7', 38 | 900: '#e7e7e7', 39 | }, 40 | primary: { 41 | DEFAULT: '#49cee9', 42 | foreground: '#181818', 43 | }, 44 | }, 45 | }, 46 | light: { 47 | colors: { 48 | background: '#ffffff', 49 | foreground: '#181818', 50 | content1: '#eeeeee', 51 | content2: '#dddddd', 52 | content3: '#cccccc', 53 | content4: '#bbbbbb', 54 | default: { 55 | DEFAULT: '#999999', 56 | 50: '#eeeeee', 57 | 100: '#cccccc', 58 | 200: '#aaaaaa', 59 | 300: '#999999', 60 | 400: '#888888', 61 | 500: '#686868', 62 | 600: '#585858', 63 | 700: '#484848', 64 | 800: '#383838', 65 | 900: '#282828', 66 | }, 67 | primary: { 68 | foreground: '#ffffff', 69 | DEFAULT: '#3578e5', 70 | }, 71 | }, 72 | }, 73 | }, 74 | }), 75 | ], 76 | }; 77 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | import { resolve } from 'path'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig(async () => ({ 7 | plugins: [react()], 8 | 9 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 10 | // prevent vite from obscuring rust errors 11 | clearScreen: false, 12 | // tauri expects a fixed port, fail if that port is not available 13 | server: { 14 | port: 1420, 15 | strictPort: true, 16 | }, 17 | // to make use of `TAURI_DEBUG` and other env variables 18 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand 19 | envPrefix: ['VITE_', 'TAURI_'], 20 | build: { 21 | rollupOptions: { 22 | input: { 23 | index: resolve(__dirname, 'index.html'), 24 | daemon: resolve(__dirname, 'daemon.html'), 25 | }, 26 | }, 27 | // Tauri supports es2021 28 | target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari11', 29 | // don't minify for debug builds 30 | minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, 31 | // produce sourcemaps for debug builds 32 | sourcemap: !!process.env.TAURI_DEBUG, 33 | }, 34 | })); 35 | --------------------------------------------------------------------------------