├── .github └── workflows │ ├── build-app.yaml │ ├── build-server.yaml │ └── debug-server.yaml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── assets │ ├── aigc.webp │ ├── banner.jpg │ ├── demo.webp │ ├── download.png │ ├── launch.png │ ├── macos-home.png │ ├── macos-open.png │ ├── mirror-flip.jpg │ ├── mirror-input.jpg │ ├── mirror-me.jpg │ ├── mirror-result.jpg │ ├── train.webp │ ├── windows-defender.jpg │ └── windows-home.png ├── cn │ ├── faq.md │ ├── install.md │ ├── readme.md │ └── usage.md └── en │ ├── faq.md │ ├── install.md │ └── usage.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── scripts ├── build-server.sh └── dist.js ├── src-python ├── magic │ ├── __init__.py │ ├── app.py │ └── face.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── server.py ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── capabilities │ └── default.json ├── 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 ├── images │ ├── dmg-background.jpg │ ├── nsis_header.bmp │ └── nsis_sidebar.bmp ├── src │ ├── commands.rs │ ├── lib.rs │ ├── main.rs │ └── utils.rs └── tauri.conf.json ├── src ├── App.tsx ├── assets │ ├── images │ │ ├── magic-mirror.svg │ │ ├── menu.webp │ │ ├── mirror-bg.svg │ │ ├── mirror-input.webp │ │ └── mirror-me.webp │ └── locales │ │ ├── en.json │ │ └── zh.json ├── components │ ├── LanguageSwitcher.tsx │ └── ProgressBar.tsx ├── hooks │ ├── useDownload.ts │ ├── useDragDrop.ts │ ├── useOS.ts │ ├── useServer.ts │ └── useSwapFace.ts ├── main.tsx ├── pages │ ├── Launch.tsx │ └── Mirror.tsx ├── services │ ├── i18n.ts │ ├── server.ts │ └── utils.ts ├── styles │ ├── global.css │ ├── mirror.css │ └── reset.css └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── uno.config.ts └── vite.config.ts /.github/workflows/build-app.yaml: -------------------------------------------------------------------------------- 1 | name: Build APP 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build-for-macos: 7 | name: macOS 8 | permissions: 9 | contents: write 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - target: universal-apple-darwin 15 | build: macos 16 | os: macos-latest 17 | arch: universal 18 | - target: aarch64-apple-darwin 19 | build: macos 20 | os: macos-latest 21 | arch: aarch64 22 | - target: x86_64-apple-darwin 23 | build: macos 24 | os: macos-latest 25 | arch: x86_64 26 | 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Setup PNPM 31 | uses: pnpm/action-setup@v3 32 | with: 33 | version: 8.5.1 34 | - name: Setup Node.js 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | cache: "pnpm" 39 | cache-dependency-path: "pnpm-lock.yaml" 40 | - name: Setup Rust 41 | uses: dtolnay/rust-toolchain@stable 42 | with: 43 | toolchain: stable 44 | targets: ${{ matrix.arch == 'universal' && 'aarch64-apple-darwin,x86_64-apple-darwin' || matrix.target }} 45 | - name: Setup Rust Cache 46 | uses: swatinem/rust-cache@v2 47 | with: 48 | workspaces: './src-tauri -> target' 49 | - name: Build APP 50 | run: | 51 | pnpm install 52 | VERSION=$(node -p "require('./package.json').version") 53 | CI=false pnpm tauri build -c "{\"version\":\"$VERSION\"}" -t ${{ matrix.target }} 54 | APP_NAME="MagicMirror_${VERSION}_${{ matrix.build }}_${{ matrix.arch }}" 55 | node scripts/dist.js ${{ matrix.target }} $APP_NAME 56 | - name: Upload App 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: app_${{ matrix.build }}_${{ matrix.arch }} 60 | path: dist/MagicMirror_* 61 | 62 | build-for-windows: 63 | name: Windows 64 | permissions: 65 | contents: write 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | include: 70 | - target: x86_64-pc-windows-msvc 71 | build: windows 72 | os: windows-latest 73 | arch: x86_64 74 | - target: aarch64-pc-windows-msvc 75 | build: windows 76 | os: windows-latest 77 | arch: aarch64 78 | 79 | runs-on: ${{ matrix.os }} 80 | steps: 81 | - uses: actions/checkout@v4 82 | - name: Setup PNPM 83 | uses: pnpm/action-setup@v3 84 | with: 85 | version: 8.5.1 86 | - name: Setup Node.js 87 | uses: actions/setup-node@v4 88 | with: 89 | node-version: 20 90 | cache: "pnpm" 91 | cache-dependency-path: "pnpm-lock.yaml" 92 | - name: Setup Rust 93 | uses: dtolnay/rust-toolchain@stable 94 | with: 95 | toolchain: stable 96 | targets: ${{ matrix.target }} 97 | - name: Setup Rust Cache 98 | uses: swatinem/rust-cache@v2 99 | with: 100 | workspaces: './src-tauri -> target' 101 | - name: Build APP 102 | shell: pwsh 103 | run: | 104 | pnpm install 105 | $VERSION = node -p "require('./package.json').version" 106 | pnpm tauri build -c "{\`"version\`":\`"$VERSION\`"}" -t ${{ matrix.target }} --bundles nsis 107 | $APP_NAME = "MagicMirror_${VERSION}_${{ matrix.build }}_${{ matrix.arch }}" 108 | node scripts/dist.js ${{ matrix.target }} $APP_NAME 109 | - name: Upload App 110 | uses: actions/upload-artifact@v4 111 | with: 112 | name: app_${{ matrix.build }}_${{ matrix.arch }} 113 | path: dist/MagicMirror_* 114 | 115 | release: 116 | name: Release 117 | needs: [build-for-macos, build-for-windows] 118 | runs-on: ubuntu-latest 119 | 120 | steps: 121 | - uses: actions/checkout@v4 122 | - name: Download Artifacts 123 | uses: actions/download-artifact@v4 124 | with: 125 | pattern: app_* 126 | path: dist 127 | merge-multiple: true 128 | - name: Setup Node.js 129 | uses: actions/setup-node@v4 130 | with: 131 | node-version: 20 132 | - name: Check Version 133 | id: version 134 | run: | 135 | VERSION=$(node -p "require('./package.json').version") 136 | echo "version=$VERSION" >> $GITHUB_OUTPUT 137 | - name: Release MagicMirror v${{ steps.version.outputs.version }} 138 | uses: ncipollo/release-action@v1 139 | with: 140 | allowUpdates: true 141 | token: ${{ secrets.GITHUB_TOKEN }} 142 | tag: app-v${{ steps.version.outputs.version }} 143 | name: MagicMirror v${{ steps.version.outputs.version }} 144 | body: MagicMirror APP v${{ steps.version.outputs.version }} 145 | draft: true 146 | prerelease: false 147 | makeLatest: latest 148 | removeArtifacts: true 149 | artifacts: dist/* 150 | -------------------------------------------------------------------------------- /.github/workflows/build-server.yaml: -------------------------------------------------------------------------------- 1 | name: Build Server 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | models: 7 | name: Download Model Files 8 | permissions: 9 | contents: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Download Model Files 14 | id: models 15 | run: | 16 | VERSION=$(awk -F ' = "' '/version =/ {print $2}' src-python/pyproject.toml | tr -d '"') 17 | echo "version=$VERSION" >> $GITHUB_OUTPUT 18 | mkdir -p src-python/models && cd src-python/models 19 | BASE_URL="https://github.com/idootop/TinyFace/releases/download/models-1.0.0" 20 | curl -# -O -L "${BASE_URL}/arcface_w600k_r50.onnx" 21 | curl -# -O -L "${BASE_URL}/gfpgan_1.4.onnx" 22 | curl -# -O -L "${BASE_URL}/inswapper_128_fp16.onnx" 23 | curl -# -O -L "${BASE_URL}/scrfd_2.5g.onnx" 24 | cd ${{ github.workspace }} 25 | - name: Upload models 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: models 29 | path: src-python/models 30 | if-no-files-found: error 31 | outputs: 32 | version: ${{ steps.models.outputs.version }} 33 | 34 | build-for-macos: 35 | name: macOS 36 | needs: models 37 | permissions: 38 | contents: write 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | include: 43 | - target: x86_64-apple-darwin 44 | build: macos 45 | os: macos-13 46 | arch: x86_64 47 | 48 | runs-on: ${{ matrix.os }} 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Download Models 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: models 55 | path: src-python/models 56 | - name: Setup Python 57 | uses: actions/setup-python@v5 58 | with: 59 | python-version: 3.10.11 60 | cache: "pip" 61 | cache-dependency-path: "**/requirements.txt" 62 | - name: Install Python Dependencies 63 | run: | 64 | cd src-python 65 | pip install -r requirements.txt 66 | pip install -e . 67 | cd ${{ github.workspace }} 68 | - name: Build Server 69 | run: | 70 | python -m nuitka --standalone --assume-yes-for-downloads \ 71 | --include-data-files="src-python/models/*.onnx=models/" \ 72 | --output-dir=out src-python/server.py 73 | cd out/server.dist && zip -r ../server_${{ matrix.build }}_${{ matrix.arch }}.zip . 74 | cd ${{ github.workspace }} 75 | - name: Upload Server 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: server_${{ matrix.build }}_${{ matrix.arch }} 79 | path: out/*.zip 80 | 81 | build-for-windows: 82 | name: Windows 83 | needs: models 84 | permissions: 85 | contents: write 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | include: 90 | - target: x86_64-pc-windows-msvc 91 | build: windows 92 | os: windows-2019 93 | arch: x86_64 94 | 95 | runs-on: ${{ matrix.os }} 96 | steps: 97 | - uses: actions/checkout@v4 98 | - name: Download Models 99 | uses: actions/download-artifact@v4 100 | with: 101 | name: models 102 | path: src-python/models 103 | - name: Setup Python 104 | uses: actions/setup-python@v5 105 | with: 106 | python-version: 3.10.11 107 | cache: "pip" 108 | cache-dependency-path: "**/requirements.txt" 109 | - name: Install Python Dependencies 110 | run: | 111 | cd src-python 112 | pip install -r requirements.txt 113 | pip install -e . 114 | cd ${{ github.workspace }} 115 | - name: Build Server 116 | shell: pwsh 117 | run: | 118 | python -m nuitka --standalone --assume-yes-for-downloads ` 119 | --mingw64 --windows-console-mode=disable ` 120 | --include-data-files="src-python/models/*.onnx=models/" ` 121 | --output-dir=out src-python/server.py 122 | cd out/server.dist 123 | Compress-Archive -Path * -DestinationPath "../server_${{ matrix.build }}_${{ matrix.arch }}.zip" 124 | cd ${{ github.workspace }} 125 | - name: Upload Server 126 | uses: actions/upload-artifact@v4 127 | with: 128 | name: server_${{ matrix.build }}_${{ matrix.arch }} 129 | path: out/*.zip 130 | 131 | release: 132 | name: Release 133 | needs: [models, build-for-macos, build-for-windows] 134 | runs-on: ubuntu-latest 135 | 136 | steps: 137 | - uses: actions/checkout@v4 138 | - name: Downoad Artifacts 139 | uses: actions/download-artifact@v4 140 | with: 141 | pattern: server_* 142 | path: dist 143 | merge-multiple: true 144 | - name: Release Server v${{ needs.models.outputs.version }} 145 | uses: ncipollo/release-action@v1 146 | with: 147 | allowUpdates: true 148 | token: ${{ secrets.GITHUB_TOKEN }} 149 | name: Server v${{ needs.models.outputs.version }} 150 | tag: server-v${{ needs.models.outputs.version }} 151 | body: MagicMirror Server v${{ needs.models.outputs.version }} 152 | draft: true 153 | prerelease: false 154 | removeArtifacts: true 155 | artifacts: dist/*.zip 156 | -------------------------------------------------------------------------------- /.github/workflows/debug-server.yaml: -------------------------------------------------------------------------------- 1 | name: Build Server (Debug Only) 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | models: 7 | name: Download Model Files 8 | permissions: 9 | contents: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Download Model Files 14 | id: models 15 | run: | 16 | VERSION=$(awk -F ' = "' '/version =/ {print $2}' src-python/pyproject.toml | tr -d '"') 17 | echo "version=$VERSION" >> $GITHUB_OUTPUT 18 | mkdir -p src-python/models && cd src-python/models 19 | BASE_URL="https://github.com/idootop/TinyFace/releases/download/models-1.0.0" 20 | curl -# -O -L "${BASE_URL}/arcface_w600k_r50.onnx" 21 | curl -# -O -L "${BASE_URL}/gfpgan_1.4.onnx" 22 | curl -# -O -L "${BASE_URL}/inswapper_128_fp16.onnx" 23 | curl -# -O -L "${BASE_URL}/scrfd_2.5g.onnx" 24 | cd ${{ github.workspace }} 25 | - name: Upload models 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: models 29 | path: src-python/models 30 | if-no-files-found: error 31 | outputs: 32 | version: ${{ steps.models.outputs.version }} 33 | 34 | build-for-windows: 35 | name: Windows 36 | needs: models 37 | permissions: 38 | contents: write 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | include: 43 | - target: x86_64-pc-windows-msvc 44 | build: windows 45 | os: windows-2019 46 | arch: x86_64 47 | 48 | runs-on: ${{ matrix.os }} 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Download Models 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: models 55 | path: src-python/models 56 | - name: Setup Python 57 | uses: actions/setup-python@v5 58 | with: 59 | python-version: 3.10.11 60 | cache: "pip" 61 | cache-dependency-path: "**/requirements.txt" 62 | - name: Install Python Dependencies 63 | run: | 64 | cd src-python 65 | pip install -r requirements.txt 66 | pip install -e . 67 | cd ${{ github.workspace }} 68 | - name: Build Server 69 | shell: pwsh 70 | run: | 71 | python -m nuitka --standalone --assume-yes-for-downloads ` 72 | --mingw64 --windows-console-mode=force ` 73 | --include-data-files="src-python/models/*.onnx=models/" ` 74 | --output-dir=out src-python/server.py 75 | cd out/server.dist 76 | Compress-Archive -Path * -DestinationPath "../server_${{ matrix.build }}_${{ matrix.arch }}_debug.zip" 77 | cd ${{ github.workspace }} 78 | - name: Upload Server 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: server_${{ matrix.build }}_${{ matrix.arch }}_debug 82 | path: out/*.zip 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .mypy_cache 4 | __pycache__ 5 | *.egg-info 6 | .venv 7 | *.env 8 | data 9 | models 10 | dist 11 | out 12 | temp 13 | test.* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Current", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "program": "${file}", 9 | "justMyCode": false, 10 | "console": "internalConsole", 11 | "internalConsoleOptions": "openOnSessionStart" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.insertSpaces": true, 4 | "editor.formatOnSave": true, 5 | "python.analysis.typeCheckingMode": "off", 6 | "python.analysis.autoImportCompletions": true, 7 | "ruff.organizeImports": true, 8 | "[python]": { 9 | "editor.defaultFormatter": "charliermarsh.ruff", 10 | "editor.codeActionsOnSave": { 11 | "source.organizeImports": "explicit", 12 | "source.fixAll": "explicit" 13 | } 14 | }, 15 | "files.exclude": { 16 | "**/.git": true, 17 | "**/node_modules": true, 18 | "**/__pycache__": true, 19 | "*.egg-info": true, 20 | ".venv": true, 21 | "build": true, 22 | ".mypy_cache": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0.0 2 | 3 | ✅ 使用文档与演示动图 4 | 5 | # v0.5.0 6 | 7 | ✅ 跨平台构建与分发(GitHub Action) 8 | 9 | # v0.4.0 10 | 11 | ✅ 应用页面设计 12 | 13 | # v0.3.0 14 | 15 | ✅ 服务器跨平台构建,构建产物下载与解压缩 16 | 17 | # v0.2.0 18 | 19 | ✅ 完成服务器启动、加载模型、换脸 Pipeline 20 | 21 | # v0.1.0 22 | 23 | ✅ Python 一键换脸 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Del Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MagicMirror 2 | 3 | Instant AI Face Swap — One click to a brand new you! 4 | 5 | [![GitHub release](https://img.shields.io/github/v/release/idootop/MagicMirror.svg)](https://github.com/idootop/MagicMirror/releases) [![Build APP](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml/badge.svg)](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml) [![Build Server](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml/badge.svg)](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml) 6 | 7 | ![](./docs/assets/banner.jpg) 8 | 9 | ## Features 10 | 11 | - **Where AI Meets Your Beauty**: Effortlessly try on new faces, hairstyles, and outfits 12 | - **Stupid Simple**: Drag and drop photos to instantly change faces - no complex settings required 13 | - **Hardware Friendly**: Runs smoothly on standard computers, no dedicated GPU hardware required 14 | - **Privacy First**: Completely offline processing - your images never leave your device 15 | - **Ultra-Lightweight**: Tiny footprint with <10MB installer and <1GB model files 16 | 17 | ## Get Started 18 | 19 | > [👉 中文教程和下载地址请戳这里](./docs/cn/readme.md) 20 | 21 | To get started with MagicMirror: 22 | 23 | 1. Follow the [Installation Guide](./docs/en/install.md) 24 | 2. Check out the [Usage Guide](./docs/en/usage.md) 25 | 3. See the [FAQ](./docs/en/faq.md) for common issues 26 | 27 | If you have any questions, please [submit an issue](https://github.com/idootop/MagicMirror/issues). 28 | 29 | > Note: MagicMirror only supports macOS 13 (Ventura) and Windows 10 and above. 30 | 31 | ## Motivation 32 | 33 | ![391785246-b3b52898-4d43-40db-8fbe-acbc00d78eec](https://github.com/user-attachments/assets/64ba0436-d7c2-4e81-bc4a-9ec00b5b7d7a) 34 | 35 | Ever found yourself endlessly scrolling through hairstyles and outfits, wondering "How would this look on me?" As someone who loves exploring different styles but hates the hassle, I dreamed of an app that could instantly show me wearing any look I fancy. 36 | 37 | While AI technology has advanced tremendously, most AI face applications either require complex setup, demand high-end GPU hardware, or rely on cloud processing that compromises privacy. 38 | 39 | **The ideal solution should be as simple as taking a selfie** - just drag, drop, and transform. No technical expertise needed, no expensive hardware required, and no privacy concerns. 40 | 41 | So, why not build one myself? 42 | 43 | And that’s how MagicMirror came to life ✨ 44 | 45 | Enjoy! ;) 46 | 47 | ## Acknowledgments 48 | 49 | MagicMirror builds upon several outstanding open-source projects: 50 | 51 | - [TinyFace](https://github.com/idootop/TinyFace): The minimalist face swapping tool that just works. 52 | - [FaceFusion](https://github.com/facefusion/facefusion): Industry leading face manipulation platform 53 | - [InsightFace](https://github.com/deepinsight/insightface): State-of-the-art 2D and 3D Face Analysis Project 54 | - [Nuitka](https://github.com/Nuitka/Nuitka): Nuitka is a Python compiler written in Python. 55 | - [Tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop and mobile applications with a web frontend. 56 | 57 | ## Disclaimer 58 | 59 | MagicMirror is designed for personal entertainment and creative purposes only. Commercial use is prohibited. Please note: 60 | 61 | - **Ethical Usage**: This software must not be used for activities including, but not limited to: a) impersonating others with malicious intent, b) spreading misinformation, c) violating personal privacy or dignity, d) creating explicit or inappropriate content. 62 | - **Content Rights**: Users are responsible for: a) obtaining necessary permissions for source images, b) respecting copyrights and intellectual property, c) complying with local laws and regulations on AI-generated content. 63 | - **Limitation of Liability**: The software and its developers are not liable for any user-generated content. Users assume full responsibility for the use of the generated content and any consequences that may arise from its use. 64 | 65 | By using MagicMirror, you agree to these terms and commit to using the software responsibly. 66 | 67 | ## License 68 | 69 | This project is licensed under the [MIT License](./LICENSE). 70 | -------------------------------------------------------------------------------- /docs/assets/aigc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/aigc.webp -------------------------------------------------------------------------------- /docs/assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/banner.jpg -------------------------------------------------------------------------------- /docs/assets/demo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/demo.webp -------------------------------------------------------------------------------- /docs/assets/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/download.png -------------------------------------------------------------------------------- /docs/assets/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/launch.png -------------------------------------------------------------------------------- /docs/assets/macos-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/macos-home.png -------------------------------------------------------------------------------- /docs/assets/macos-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/macos-open.png -------------------------------------------------------------------------------- /docs/assets/mirror-flip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/mirror-flip.jpg -------------------------------------------------------------------------------- /docs/assets/mirror-input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/mirror-input.jpg -------------------------------------------------------------------------------- /docs/assets/mirror-me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/mirror-me.jpg -------------------------------------------------------------------------------- /docs/assets/mirror-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/mirror-result.jpg -------------------------------------------------------------------------------- /docs/assets/train.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/train.webp -------------------------------------------------------------------------------- /docs/assets/windows-defender.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/windows-defender.jpg -------------------------------------------------------------------------------- /docs/assets/windows-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/docs/assets/windows-home.png -------------------------------------------------------------------------------- /docs/cn/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ![](../assets/banner.jpg) 4 | 5 | ## 常见问题 6 | 7 | ### 如何使用 GPU 加速推理? 8 | 9 | MagicMirror 默认使用 CPU 进行推理,暂不支持启用硬件加速选项。 10 | 11 | 不过你可以尝试 MagicMirror 的 CLI 版本 [TinyFace](https://github.com/idootop/TinyFace),或者使用 [FaceFusion](https://github.com/facefusion/facefusion)。 12 | 13 | ### MagicMirror 的体积为什么那么小,是怎么做到的? 14 | 15 | 这多亏了 [Tauri](https://tauri.app/),它使用系统自带的 WebView 组件,所以不用像 [Electron](https://www.electronjs.org/) 那样直接把整个 Chromium 浏览器和 Node.js 运行时打包进应用安装包。如果你对此感兴趣,可以在这里了解更多[技术细节](https://tauri.app/start/)。 16 | 17 | ### MagicMirror 的 LOGO 使用 AI 生成的吗? 18 | 19 | 是的。MagicMirror 内的所有设计资源:从 Logo、字体到 UI 界面,都是由 AI 生成的 ✨ 20 | 21 | ![](../assets/aigc.webp) 22 | 23 | 这里我使用的是 [Tensor.ART](https://tusiart.com/):一个免费的在线 AI 生图网站。像最新的 Flux 和 SD 3.5 等模型都可以直接使用,而且你也可以在线训练自己的模型。 24 | 25 | 比如 MagicMirror 的 LOGO, 就是我从 Dribbble 上搜集了一些参考图,然后用 Flux 做底模训练的一个模型生成的,非常方便。 26 | 27 | ![](../assets/train.webp) 28 | 29 | 相比 [Civitai](https://civitai.com/) 和 [LibLib.AI](https://www.liblib.art/) 等 AI 生图平台,[Tensor.ART](https://tusiart.com/) 的模型数量更多,价格更便宜,性价比应该是这三者里最高的。 30 | 31 | 如果你也想要尝试 AI 生图,或者正在寻找更有性价比的生图平台,不妨试试看 [Tensor.ART](https://tusiart.com/)。 32 | 33 | ## 安装问题 34 | 35 | ### 【macOS】打开 APP 时提示: “MagicMirror”已损坏,无法打开。 你应该将它移到废纸篓。 36 | 37 | 这是因为 macOS 默认只允许从 App Store 或已知的开发者来源安装应用,以保护用户的安全和隐私。 38 | 39 | 但是目前我还没有注册苹果开发者账号($99 一年),所以无法为 MagicMirror 提供有效的签名并上架应用商店。 40 | 41 | 你可以: 42 | 43 | 1. 打开访达,然后在右侧菜单栏选择应用程序 44 | 2. 找到提示无法验证的应用,右键点击打开即可 45 | 46 | ![](../assets/macos-open.png) 47 | 48 | 如果在运行过程中仍然弹窗提示无法打开“xxx”(尤其是 macOS 15 系统版本及以上), 49 | 50 | 请自行百度 **macOS 如何开启任何来源**,参考:https://www.ghxi.com/jc202309058.html 51 | 52 | ### 【Windows】提示不是有效的 Win32 应用 53 | 54 | MagicMirror 目前只提供了 x64 和 ARM64 架构的安装包,对于较老的 x32 电脑暂不支持。 55 | 56 | 推荐的运行环境是 Windows 11,如果低于 Windows 10 可能会无法正常运行。 57 | 58 | ## 运行问题 59 | 60 | ### 【macOS】卡在启动界面超过 10 分钟 61 | 62 | 如果你的电脑非 Intel 芯片(比如:M1、M4 芯片),首次启动 APP 时需要进行 Rosatta 转译,大概 3-5 分钟左右。 63 | 64 | 后续启动将恢复正常时长(5-10s)。如果超过 10 分钟仍未正常启动,请关闭应用后重试。 65 | 66 | 如果重启后还是无法正常启动,请检查你的 macOS 系统是否不低于 13 版本 (macOS Ventura),较老的 macOS 系统可能无法正常运行。 67 | 68 | 查看此处了解更多信息:https://github.com/521xueweihan/HelloGitHub/issues/2859#issuecomment-2562637177 69 | 70 | ### 【Windows】卡在启动界面超过 10 分钟 71 | 72 | 如果你在 Windows 系统上一直卡在启动界面,请检查你的 `$HOME/MagicMirror/server.exe` 文件是否存在。 73 | 74 | 这是使用 [Nuitka](https://github.com/Nuitka/Nuitka) 编译的一个 Python 应用程序,MagicMirror 的正常运行离不开此程序。 75 | 76 | ![](../assets/windows-home.png) 77 | 78 | 由于许多病毒软件也喜欢使用 Nuitka 来编译他们的应用程序,从而混淆源代码隐藏自己的恶意行为特征。 79 | 80 | 所以同样使用 Nuitka 编译的 `server.exe` 文件,在启动时也容易被 Windows 安全应用误标记为病毒文件并删除。 81 | 82 | ![](../assets/windows-defender.jpg) 83 | 84 | 你可以在 Windows 安全中心 - 保护历史记录里,手动还原被删除的 `server.exe` 文件。然后重启 MagicMirror 应该就能正常运行了。 85 | 86 | > 如果你对 Nuitka 被 Widnows 安全应用误报感兴趣,可以[查看此处](https://github.com/Nuitka/Nuitka/issues/2685#issuecomment-1923357489)了解更多技术细节。 87 | 88 | 如果仍然启动失败,查看此处了解更多:https://github.com/idootop/MagicMirror/issues/6#issuecomment-2560949972 89 | 90 | ### 提示换脸失败 91 | 92 | 你可以换几张其他的图片试试看。比较冷门的图片格式、文件名包含特殊字符、图片分辨率过大等都可能会导致换脸失败。 93 | 94 | ## 其他问题 95 | 96 | ### MagicMirror 与 FaceFusion 有什么联系? 97 | 98 | 简单来说,你可以把 MagicMirror 视为 FaceFusion 的精简版。 99 | 100 | 我在 [FaceFusion](https://github.com/facefusion/facefusion) 的基础上,移除了所有不必要的模块,只保留了最核心的换脸功能,并由此创造出了 [TinyFace](https://github.com/idootop/TinyFace):一个超轻量的 Python 换脸工具。 101 | 102 | MagicMirror 是在 [TinyFace](https://github.com/idootop/TinyFace) 之上构建的一个 GUI 项目,方便使用。 103 | 104 | ### MagicMirror 与 InsightFace 有什么联系? 105 | 106 | 一个经典的换脸工作流,通常由人脸检测、识别、换脸、画质增强等多个步骤组成,不同的步骤依赖不同的模型,而 [InsightFace](https://github.com/deepinsight/insightface/tree/master/examples/in_swapper) 的 `inswapper_128.onnx` 则是换脸过程中使用到的模型,是整个应用的核心。 107 | -------------------------------------------------------------------------------- /docs/cn/install.md: -------------------------------------------------------------------------------- 1 | # 安装教程 2 | 3 | ![](../assets/banner.jpg) 4 | 5 | ## 下载地址 6 | 7 | 首先根据你的操作系统,下载对应的 APP 安装包并安装: 8 | 9 | 1. Windows: [MagicMirror_1.0.0_windows_x86_64.exe](https://github.com/idootop/MagicMirror/releases/download/app-v1.0.0/MagicMirror_1.0.0_windows_x86_64.exe) 10 | 2. macOS: [MagicMirror_1.0.0_macos_universal.dmg](https://github.com/idootop/MagicMirror/releases/download/app-v1.0.0/MagicMirror_1.0.0_macos_universal.dmg) 11 | 12 | > 如果你访问不了上面的 GitHub 下载链接,请使用国内[下载地址](https://del-wang.lanzout.com/b01qdt5nba) 密码: `4ro2` 13 | 14 | ## 下载模型 15 | 16 | 首次启动 APP,需要下载模型文件(默认会自动下载),初始化成功后才能使用。 17 | 18 | ![](../assets/download.png) 19 | 20 | 如果你的下载进度一直是 0,或者下到一半卡住了,请按照下面的步骤手动初始化: 21 | 22 | **下载模型文件** 23 | 24 | 首先根据你的操作系统和架构,选择对应的模型文件: 25 | 26 | - [server_windows_x86_64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_windows_x86_64.zip)(Windows,大多数的电脑都是这种) 27 | - [server_windows_aarch64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_windows_aarch64.zip)(Windows,如果你是 ARM64 设备) 28 | - [server_macos_aarch64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_macos_aarch64.zip)(macOS, 苹果芯片,比如 M1、M4 芯片) 29 | - [server_macos_x86_64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_macos_x86_64.zip)(macOS, Intel 芯片) 30 | 31 | > 如果你访问不了上面的 GitHub 下载链接,可以使用国内的[下载地址](https://pan.quark.cn/s/b8ad043794bb) 32 | 33 | **解压下载的文件** 34 | 35 | 解压后应该是一个文件夹,把它重命名成: `MagicMirror` 然后移动到你电脑的 `HOME` 目录下,比如: 36 | 37 | ![](../assets/windows-home.png) 38 | 39 | ![](../assets/macos-home.png) 40 | 41 | 完成上面两步后,重启 MagicMirror 应该就能正常启动了。 42 | 43 | ## 启动 APP 44 | 45 | 下载完模型文件后,第一次启动应用可能比较缓慢,耐心等待即可。 46 | 47 | ![](../assets/launch.png) 48 | 49 | > 一般 3 分钟以内即可启动成功,如果超过 10 分钟还未正常启动,请查看[常见问题](./faq.md) 50 | 51 | ## 遇到问题? 52 | 53 | 大部分问题都能在「[常见问题](./faq.md)」里找到答案,如果你还有其他问题,请在此[提交反馈](https://github.com/idootop/MagicMirror/issues)。 54 | -------------------------------------------------------------------------------- /docs/cn/readme.md: -------------------------------------------------------------------------------- 1 | # MagicMirror 2 | 3 | 一键 AI 换脸,发现更美的自己 ✨ 4 | 5 | [![GitHub release](https://img.shields.io/github/v/release/idootop/MagicMirror.svg)](https://github.com/idootop/MagicMirror/releases) [![Build APP](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml/badge.svg)](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml) [![Build Server](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml/badge.svg)](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml) 6 | 7 | ![](../assets/banner.jpg) 8 | 9 | ## 功能亮点 10 | 11 | - 一键换脸:打开安装包,拖张照片进去就能换脸,无需配置各种复杂参数。 12 | - 超低门槛:不用 GPU 也能运行,普通的小白电脑也可以轻松玩转 AI 换脸。 13 | - 隐私安全:完全在你的电脑本地运行,不需要联网,不用担心你的图片会被上传到任何地方。 14 | - 极致精简:安装包不到 10 MB,模型文件加起来不到 1 GB,这可能是最轻量的离线换脸应用之一。 15 | 16 | ## 快速开始 17 | 18 | > 🔥 演示视频:https://www.bilibili.com/video/BV1TTzfYDEUe 19 | 20 | MagicMirror 仅支持 macOS 13(Ventura)和 Windows 10 及以上版本系统: 21 | 22 | 1. [安装教程](./install.md) 23 | 2. [使用教程](./usage.md) 24 | 3. [常见问题](./faq.md)(90%的问题可以在这里找到答案) 25 | 26 | 如果你有其他问题,请提交 [Issue](https://github.com/idootop/MagicMirror/issues)。 27 | 28 | ## 动机 29 | 30 | ![391785246-b3b52898-4d43-40db-8fbe-acbc00d78eec](https://github.com/user-attachments/assets/6500a393-69e7-42c9-bf78-febc84d7e5e5) 31 | 32 | 33 | 我想你跟我一样,有时会纠结:自己适合哪种发型,或者哪种穿搭最好看? 34 | 35 | 要是有一个应用,可以把我们看到的喜欢的发型或心动的穿搭,直接放到自己的身上预览效果,那真是太好了。 36 | 37 | 现在的 AI 技术已经很成熟了,但是市面上大部分的 AI 换脸应用: 38 | 39 | - 要么需要配置复杂的参数和运行环境,要有性能足够强劲的 GPU 才能运行,使用门槛和成本偏高; 40 | - 要么需要上传自己的图片到服务器进行转换,存在隐私泄漏的风险。 41 | 42 | 理想的解决方案应该像自拍一样简单:不需要设置复杂的参数,不用购买昂贵的设备,也无需担心隐私泄漏的问题。 43 | 44 | 所以,为什么不自己做一个呢? 45 | 46 | 于是便有了 MagicMirror ✨ 47 | 48 | Enjoy! ;) 49 | 50 | ## 鸣谢 51 | 52 | MagicMirror 的实现主要使用和参考了以下开源项目,特此鸣谢: 53 | 54 | - [TinyFace](https://github.com/idootop/TinyFace): The minimalist face swapping tool that just works. 55 | - [FaceFusion](https://github.com/facefusion/facefusion): Industry leading face manipulation platform 56 | - [InsightFace](https://github.com/deepinsight/insightface): State-of-the-art 2D and 3D Face Analysis Project 57 | - [Nuitka](https://github.com/Nuitka/Nuitka): Nuitka is a Python compiler written in Python. 58 | - [Tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop and mobile applications with a web frontend. 59 | 60 | ## 免责声明 61 | 62 | MagicMirror 仅限个人娱乐与创意用途,严禁用于商业用途。请注意: 63 | 64 | - **道德使用**:本软件不得用于以下行为,包括但不限于:a) 恶意冒充他人,b) 散布虚假信息,c) 侵犯个人隐私或尊严,d) 制作淫秽或不当内容。 65 | - **内容版权**:用户应对以下内容负责:a) 获取使用源图像的必要许可,b) 尊重版权及知识产权,c) 遵守当地关于 AI 生成内容的法律法规。 66 | - **免责声明**:用户应对生成的内容以及由其使用引发的任何法律、道德或个人问题承担全部责任。本软件及其开发者对用户生成的内容不承担任何责任。 67 | 68 | 使用 MagicMirror 即表示您已阅读并同意上述条款,并承诺负责任地使用本软件。 69 | 70 | ## License 71 | 72 | This project is licensed under the [MIT License](./LICENSE). 73 | -------------------------------------------------------------------------------- /docs/cn/usage.md: -------------------------------------------------------------------------------- 1 | # 使用教程 2 | 3 | 首先,准备一张你的正脸照,然后拖入到镜子里。 4 | 5 | ![](../assets/mirror-me.jpg) 6 | 7 | 然后把你想要换脸的照片,拖到另一面镜子里,等待换脸完毕即可。 8 | 9 | ![](../assets/mirror-input.jpg) 10 | 11 | > 苹果 M1 芯片换一张脸一般需要 3-5s 的时间,主要看你的电脑配置和图片大小 12 | 13 | 换脸成功后,会在原来的位置生成一张以 `_output` 结尾的新图片。 14 | 15 | ![](../assets/mirror-result.jpg) 16 | 17 | 你可以继续拖入新的照片换脸,或通过右上角的菜单翻转镜像,更换新的脸部图片。 18 | 19 | ![](../assets/mirror-flip.jpg) 20 | 21 | ## 遇到问题? 22 | 23 | 大部分问题都能在「[常见问题](./faq.md)」里找到答案,如果你还有其他问题,请在此[提交反馈](https://github.com/idootop/MagicMirror/issues)。 24 | -------------------------------------------------------------------------------- /docs/en/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ![](../assets/banner.jpg) 4 | 5 | ## Common 6 | 7 | ### How to Enable GPU Acceleration for Inference? 8 | 9 | MagicMirror uses CPU by default for inference and currently does not support hardware acceleration options. However, you can try MagicMirror’s CLI version, [TinyFace](https://github.com/idootop/TinyFace), or use [FaceFusion](https://github.com/facefusion/facefusion). 10 | 11 | ### Why is MagicMirror So Small in Size? 12 | 13 | This is thanks to [Tauri](https://tauri.app/), which uses the system’s built-in WebView component, unlike [Electron](https://www.electronjs.org/), which packages an entire Chromium browser and Node.js runtime into the app. If you're interested in the technical details, [click here](https://tauri.app/start/). 14 | 15 | ### Is the MagicMirror Logo AI-Generated? 16 | 17 | Every design element in MagicMirror - from the logo and typography to the UI - was created using AI ✨ 18 | 19 | ![](../assets/aigc.webp) 20 | 21 | I used [Tensor.ART](https://tensor.art/), a free AI image generation platform. It offers access to the latest models like Flux and SD 3.5, plus the ability to train your own custom models. 22 | 23 | For example, I created MagicMirror's logo by training a Flux-based model on reference images collected from Dribbble - quick and simple. 24 | 25 | ![](../assets/train.webp) 26 | 27 | Compared to platforms like [Civitai](https://civitai.com/) and [LibLib.AI](https://www.liblib.art/), [Tensor.ART](https://tensor.art/) has more models at a lower price, making it the most cost-effective option. 28 | 29 | If you're looking to try AI image generation or want a more affordable platform, I recommend giving [Tensor.ART](https://tensor.art/) a try. 30 | 31 | ## Installation 32 | 33 | ### 【macOS】App is Damaged or Can't Be Verified 34 | 35 | This issue occurs because the app isn't signed with an Apple developer certificate. To resolve this: 36 | 37 | 1. Open Finder and select Applications from the sidebar. 38 | 2. Locate the app with the warning, right-click it, and choose Open. 39 | 40 | ![](../assets/macos-open.png) 41 | 42 | ## Usage 43 | 44 | ### 【Windows】App Stuck on the Startup Screen for Over 10 Minutes 45 | 46 | If the app is stuck at the startup screen, check if the `server.exe` file exists on your system. This file, compiled using [Nuitka](https://github.com/Nuitka/Nuitka) from Python source, is essential for MagicMirror to run. 47 | 48 | ![](../assets/windows-home.png) 49 | 50 | Windows Defender may flag the `server.exe` as malicious due to the use of Nuitka by some malware, leading to it being quarantined. 51 | 52 | ![](../assets/windows-defender.jpg) 53 | 54 | To fix this: 55 | 56 | 1. Open Windows Security Center and go to Protection History. 57 | 2. Find and restore the quarantined `server.exe`. 58 | 3. Restart MagicMirror. 59 | 60 | > For details on Nuitka false positives in Windows Defender, [click here](https://github.com/Nuitka/Nuitka/issues/2685#issuecomment-1923357489). 61 | 62 | If none of the above work, your system likely has compatibility issues with the server. Consider using alternative face swap tools like [TinyFace](https://github.com/idootop/TinyFace) (the underlying library used by MagicMirror), or [FaceFusion](https://github.com/facefusion/facefusion). 63 | 64 | Learn more: https://github.com/idootop/MagicMirror/issues/6#issuecomment-2560949972 65 | 66 | ### 【Windows】Face Swap Fails with Any Image 67 | 68 | First, check if the `server.exe` file exists in `$HOME/MagicMirror/`. If it’s missing, follow the steps in the previous section to restore it. 69 | 70 | ![](../assets/windows-home.png) 71 | 72 | If the issue persists, please [submit an issue](https://github.com/idootop/MagicMirror/issues). 73 | 74 | ### 【Windows】"Not a Valid Win32 Application" Error 75 | 76 | MagicMirror currently only supports x64 and ARM64 architectures. Older x32 systems are not supported. 77 | 78 | The recommended environment is Windows 11 x64. MagicMirror may not run properly on versions lower than Windows 10. 79 | 80 | ### 【macOS】App Stuck on Startup for Over 10 Minutes 81 | 82 | First-time startup on macOS can be slow. If the app doesn’t start within 30 minutes, close it and try again. 83 | 84 | If it still doesn’t launch, ensure your macOS version is at least `13` (Ventura), as older versions may not be compatible. 85 | 86 | Learn more: https://github.com/521xueweihan/HelloGitHub/issues/2859#issuecomment-2562637177 87 | 88 | ## Other 89 | 90 | ### What’s the Relationship Between MagicMirror and FaceFusion? 91 | 92 | In simple terms, MagicMirror is a simplified version of FaceFusion. 93 | 94 | Starting from [FaceFusion](https://github.com/facefusion/facefusion), I removed unnecessary modules and kept only the core face-swapping functionality, which led to the creation of [TinyFace](https://github.com/idootop/TinyFace), a lightweight Python face-swapping tool. 95 | 96 | MagicMirror is a GUI project built on top of [TinyFace](https://github.com/idootop/TinyFace), making it easier to use. 97 | 98 | ### What’s the Relationship Between MagicMirror and InsightFace? 99 | 100 | A typical face-swapping workflow includes multiple steps, such as face detection, recognition, swapping, and quality enhancement. These steps rely on different models. The `inswapper_128.onnx` model from [InsightFace](https://github.com/deepinsight/insightface/tree/master/examples/in_swapper) is a core part of the face-swapping process. 101 | -------------------------------------------------------------------------------- /docs/en/install.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | ![](../assets/banner.jpg) 4 | 5 | ## Installation 6 | 7 | Download and install the MagicMirror installer for your operating system: 8 | 9 | 1. Windows: [MagicMirror_1.0.0_windows_x86_64.exe](https://github.com/idootop/MagicMirror/releases/download/app-v1.0.0/MagicMirror_1.0.0_windows_x86_64.exe) 10 | 2. macOS: [MagicMirror_1.0.0_macos_universal.dmg](https://github.com/idootop/MagicMirror/releases/download/app-v1.0.0/MagicMirror_1.0.0_macos_universal.dmg) 11 | 3. Other: [Go to Release](https://github.com/idootop/MagicMirror/releases/app-v1.0.0) 12 | 13 | ## Download Models 14 | 15 | When you first launch the app, it will automatically download the required model files. 16 | 17 | ![](../assets/download.png) 18 | 19 | If the download progress is stuck at 0% or stops midway, follow these steps for manual setup: 20 | 21 | **Download Model Files** 22 | 23 | Choose the model file that matches your operating system: 24 | 25 | - [server_windows_x86_64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_windows_x86_64.zip)(Windows, most common) 26 | - [server_windows_aarch64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_windows_aarch64.zip)(Windows, for ARM64 devices) 27 | - [server_macos_aarch64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_macos_aarch64.zip)(macOS, Apple Silicon, such as M1, M4 chips) 28 | - [server_macos_x86_64.zip](https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_macos_x86_64.zip)(macOS, Intel chips) 29 | 30 | **Extract the Downloaded File** 31 | 32 | You'll get a folder—rename it to `MagicMirror`. Move this folder to your computer's `HOME` directory, for example: 33 | 34 | ![](../assets/windows-home.png) 35 | 36 | ![](../assets/macos-home.png) 37 | 38 | Restart MagicMirror, and it should now work properly. 39 | 40 | ## Launch APP 41 | 42 | After downloading the model files, the first launch may take some time. 43 | 44 | ![](../assets/launch.png) 45 | 46 | > The app should launch within 3 minutes. If it takes longer than 10 minutes to start, please refer to the [FAQ](./faq.md) 47 | 48 | ## Need help? 49 | 50 | Most issues are addressed in the [FAQ](./faq.md). If you need further assistance, please [submit an issue](https://github.com/idootop/MagicMirror/issues). 51 | -------------------------------------------------------------------------------- /docs/en/usage.md: -------------------------------------------------------------------------------- 1 | # Usage Guide 2 | 3 | First, drag a front-facing photo of yourself into the mirror. 4 | 5 | ![](../assets/mirror-me.jpg) 6 | 7 | Next, drag your desired photo into the other mirror and wait for processing to complete. 8 | 9 | ![](../assets/mirror-input.jpg) 10 | 11 | > On Apple M1 chips, face swapping takes about 3-5 seconds, varying with your computer's specs and image size. 12 | 13 | When processing is complete, a new image will appear in the original location with `_output` added to its filename. 14 | 15 | ![](../assets/mirror-result.jpg) 16 | 17 | You can continue swapping faces with new photos, or use the top-right menu to flip the mirror and change the face image. 18 | 19 | ![](../assets/mirror-flip.jpg) 20 | 21 | ## Need help? 22 | 23 | Most issues are addressed in the [FAQ](./faq.md). If you need further assistance, please [submit an issue](https://github.com/idootop/MagicMirror/issues). 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tauri + React + Typescript 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magic", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "description": "Instant AI Face Swap, Hairstyles & Outfits — One click to a brand new you!", 7 | "author": { 8 | "name": "Del Wang", 9 | "email": "hello@xbox.work" 10 | }, 11 | "license": "MIT", 12 | "homepage": "https://del.wang", 13 | "repository": "https://github.com/idootop/MagicMirror", 14 | "scripts": { 15 | "tauri": "tauri", 16 | "dev": "vite", 17 | "build": "tsc && vite build", 18 | "preview": "vite preview", 19 | "build:server": "bash scripts/build-server.sh" 20 | }, 21 | "dependencies": { 22 | "@tauri-apps/api": "^2", 23 | "@tauri-apps/plugin-os": "^2.0.0", 24 | "@tauri-apps/plugin-process": "^2.0.0", 25 | "@tauri-apps/plugin-shell": "^2.0.0", 26 | "@types/node": "^22.9.0", 27 | "i18next": "^23.16.5", 28 | "i18next-browser-languagedetector": "^8.0.0", 29 | "i18next-localstorage-backend": "^4.2.0", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0", 32 | "react-i18next": "^15.1.1", 33 | "react-router-dom": "^6.28.0", 34 | "xsta": "^2.1.0" 35 | }, 36 | "devDependencies": { 37 | "@tauri-apps/cli": "^2", 38 | "@types/react": "^18.2.15", 39 | "@types/react-dom": "^18.2.7", 40 | "@vitejs/plugin-react": "^4.2.1", 41 | "typescript": "^5.2.2", 42 | "unocss": "^0.64.0", 43 | "vite": "^5.3.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | dependencies: 4 | '@tauri-apps/api': 5 | specifier: ^2 6 | version: 2.0.0 7 | '@tauri-apps/plugin-os': 8 | specifier: ^2.0.0 9 | version: 2.0.0 10 | '@tauri-apps/plugin-process': 11 | specifier: ^2.0.0 12 | version: 2.0.0 13 | '@tauri-apps/plugin-shell': 14 | specifier: ^2.0.0 15 | version: 2.0.0 16 | '@types/node': 17 | specifier: ^22.9.0 18 | version: 22.9.0 19 | i18next: 20 | specifier: ^23.16.5 21 | version: 23.16.5 22 | i18next-browser-languagedetector: 23 | specifier: ^8.0.0 24 | version: 8.0.0 25 | i18next-localstorage-backend: 26 | specifier: ^4.2.0 27 | version: 4.2.0 28 | react: 29 | specifier: ^18.2.0 30 | version: 18.2.0 31 | react-dom: 32 | specifier: ^18.2.0 33 | version: 18.2.0(react@18.2.0) 34 | react-i18next: 35 | specifier: ^15.1.1 36 | version: 15.1.1(i18next@23.16.5)(react-dom@18.2.0)(react@18.2.0) 37 | react-router-dom: 38 | specifier: ^6.28.0 39 | version: 6.28.0(react-dom@18.2.0)(react@18.2.0) 40 | xsta: 41 | specifier: ^2.1.0 42 | version: 2.1.0(@types/react@18.2.15)(react@18.2.0) 43 | 44 | devDependencies: 45 | '@tauri-apps/cli': 46 | specifier: ^2 47 | version: 2.0.0 48 | '@types/react': 49 | specifier: ^18.2.15 50 | version: 18.2.15 51 | '@types/react-dom': 52 | specifier: ^18.2.7 53 | version: 18.2.7 54 | '@vitejs/plugin-react': 55 | specifier: ^4.2.1 56 | version: 4.2.1(vite@5.3.1) 57 | typescript: 58 | specifier: ^5.2.2 59 | version: 5.2.2 60 | unocss: 61 | specifier: ^0.64.0 62 | version: 0.64.0(postcss@8.4.48)(vite@5.3.1)(vue@3.5.12) 63 | vite: 64 | specifier: ^5.3.1 65 | version: 5.3.1(@types/node@22.9.0) 66 | 67 | packages: 68 | 69 | /@ampproject/remapping@2.3.0: 70 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 71 | engines: {node: '>=6.0.0'} 72 | dependencies: 73 | '@jridgewell/gen-mapping': 0.3.5 74 | '@jridgewell/trace-mapping': 0.3.25 75 | dev: true 76 | 77 | /@antfu/install-pkg@0.4.1: 78 | resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} 79 | dependencies: 80 | package-manager-detector: 0.2.2 81 | tinyexec: 0.3.1 82 | dev: true 83 | 84 | /@antfu/utils@0.7.10: 85 | resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} 86 | dev: true 87 | 88 | /@babel/code-frame@7.26.2: 89 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 90 | engines: {node: '>=6.9.0'} 91 | dependencies: 92 | '@babel/helper-validator-identifier': 7.25.9 93 | js-tokens: 4.0.0 94 | picocolors: 1.1.1 95 | dev: true 96 | 97 | /@babel/compat-data@7.26.2: 98 | resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} 99 | engines: {node: '>=6.9.0'} 100 | dev: true 101 | 102 | /@babel/core@7.26.0: 103 | resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} 104 | engines: {node: '>=6.9.0'} 105 | dependencies: 106 | '@ampproject/remapping': 2.3.0 107 | '@babel/code-frame': 7.26.2 108 | '@babel/generator': 7.26.2 109 | '@babel/helper-compilation-targets': 7.25.9 110 | '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) 111 | '@babel/helpers': 7.26.0 112 | '@babel/parser': 7.26.2 113 | '@babel/template': 7.25.9 114 | '@babel/traverse': 7.25.9 115 | '@babel/types': 7.26.0 116 | convert-source-map: 2.0.0 117 | debug: 4.3.7 118 | gensync: 1.0.0-beta.2 119 | json5: 2.2.3 120 | semver: 6.3.1 121 | transitivePeerDependencies: 122 | - supports-color 123 | dev: true 124 | 125 | /@babel/generator@7.26.2: 126 | resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} 127 | engines: {node: '>=6.9.0'} 128 | dependencies: 129 | '@babel/parser': 7.26.2 130 | '@babel/types': 7.26.0 131 | '@jridgewell/gen-mapping': 0.3.5 132 | '@jridgewell/trace-mapping': 0.3.25 133 | jsesc: 3.0.2 134 | dev: true 135 | 136 | /@babel/helper-compilation-targets@7.25.9: 137 | resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} 138 | engines: {node: '>=6.9.0'} 139 | dependencies: 140 | '@babel/compat-data': 7.26.2 141 | '@babel/helper-validator-option': 7.25.9 142 | browserslist: 4.24.2 143 | lru-cache: 5.1.1 144 | semver: 6.3.1 145 | dev: true 146 | 147 | /@babel/helper-module-imports@7.25.9: 148 | resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} 149 | engines: {node: '>=6.9.0'} 150 | dependencies: 151 | '@babel/traverse': 7.25.9 152 | '@babel/types': 7.26.0 153 | transitivePeerDependencies: 154 | - supports-color 155 | dev: true 156 | 157 | /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): 158 | resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} 159 | engines: {node: '>=6.9.0'} 160 | peerDependencies: 161 | '@babel/core': ^7.0.0 162 | dependencies: 163 | '@babel/core': 7.26.0 164 | '@babel/helper-module-imports': 7.25.9 165 | '@babel/helper-validator-identifier': 7.25.9 166 | '@babel/traverse': 7.25.9 167 | transitivePeerDependencies: 168 | - supports-color 169 | dev: true 170 | 171 | /@babel/helper-plugin-utils@7.25.9: 172 | resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} 173 | engines: {node: '>=6.9.0'} 174 | dev: true 175 | 176 | /@babel/helper-string-parser@7.25.9: 177 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 178 | engines: {node: '>=6.9.0'} 179 | dev: true 180 | 181 | /@babel/helper-validator-identifier@7.25.9: 182 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 183 | engines: {node: '>=6.9.0'} 184 | dev: true 185 | 186 | /@babel/helper-validator-option@7.25.9: 187 | resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} 188 | engines: {node: '>=6.9.0'} 189 | dev: true 190 | 191 | /@babel/helpers@7.26.0: 192 | resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} 193 | engines: {node: '>=6.9.0'} 194 | dependencies: 195 | '@babel/template': 7.25.9 196 | '@babel/types': 7.26.0 197 | dev: true 198 | 199 | /@babel/parser@7.26.2: 200 | resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} 201 | engines: {node: '>=6.0.0'} 202 | hasBin: true 203 | dependencies: 204 | '@babel/types': 7.26.0 205 | dev: true 206 | 207 | /@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0): 208 | resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} 209 | engines: {node: '>=6.9.0'} 210 | peerDependencies: 211 | '@babel/core': ^7.0.0-0 212 | dependencies: 213 | '@babel/core': 7.26.0 214 | '@babel/helper-plugin-utils': 7.25.9 215 | dev: true 216 | 217 | /@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0): 218 | resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} 219 | engines: {node: '>=6.9.0'} 220 | peerDependencies: 221 | '@babel/core': ^7.0.0-0 222 | dependencies: 223 | '@babel/core': 7.26.0 224 | '@babel/helper-plugin-utils': 7.25.9 225 | dev: true 226 | 227 | /@babel/runtime@7.26.0: 228 | resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} 229 | engines: {node: '>=6.9.0'} 230 | dependencies: 231 | regenerator-runtime: 0.14.1 232 | dev: false 233 | 234 | /@babel/template@7.25.9: 235 | resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} 236 | engines: {node: '>=6.9.0'} 237 | dependencies: 238 | '@babel/code-frame': 7.26.2 239 | '@babel/parser': 7.26.2 240 | '@babel/types': 7.26.0 241 | dev: true 242 | 243 | /@babel/traverse@7.25.9: 244 | resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} 245 | engines: {node: '>=6.9.0'} 246 | dependencies: 247 | '@babel/code-frame': 7.26.2 248 | '@babel/generator': 7.26.2 249 | '@babel/parser': 7.26.2 250 | '@babel/template': 7.25.9 251 | '@babel/types': 7.26.0 252 | debug: 4.3.7 253 | globals: 11.12.0 254 | transitivePeerDependencies: 255 | - supports-color 256 | dev: true 257 | 258 | /@babel/types@7.26.0: 259 | resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 260 | engines: {node: '>=6.9.0'} 261 | dependencies: 262 | '@babel/helper-string-parser': 7.25.9 263 | '@babel/helper-validator-identifier': 7.25.9 264 | dev: true 265 | 266 | /@esbuild/aix-ppc64@0.21.5: 267 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 268 | engines: {node: '>=12'} 269 | cpu: [ppc64] 270 | os: [aix] 271 | requiresBuild: true 272 | dev: true 273 | optional: true 274 | 275 | /@esbuild/aix-ppc64@0.23.1: 276 | resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 277 | engines: {node: '>=18'} 278 | cpu: [ppc64] 279 | os: [aix] 280 | requiresBuild: true 281 | dev: true 282 | optional: true 283 | 284 | /@esbuild/android-arm64@0.21.5: 285 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 286 | engines: {node: '>=12'} 287 | cpu: [arm64] 288 | os: [android] 289 | requiresBuild: true 290 | dev: true 291 | optional: true 292 | 293 | /@esbuild/android-arm64@0.23.1: 294 | resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} 295 | engines: {node: '>=18'} 296 | cpu: [arm64] 297 | os: [android] 298 | requiresBuild: true 299 | dev: true 300 | optional: true 301 | 302 | /@esbuild/android-arm@0.21.5: 303 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 304 | engines: {node: '>=12'} 305 | cpu: [arm] 306 | os: [android] 307 | requiresBuild: true 308 | dev: true 309 | optional: true 310 | 311 | /@esbuild/android-arm@0.23.1: 312 | resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} 313 | engines: {node: '>=18'} 314 | cpu: [arm] 315 | os: [android] 316 | requiresBuild: true 317 | dev: true 318 | optional: true 319 | 320 | /@esbuild/android-x64@0.21.5: 321 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 322 | engines: {node: '>=12'} 323 | cpu: [x64] 324 | os: [android] 325 | requiresBuild: true 326 | dev: true 327 | optional: true 328 | 329 | /@esbuild/android-x64@0.23.1: 330 | resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} 331 | engines: {node: '>=18'} 332 | cpu: [x64] 333 | os: [android] 334 | requiresBuild: true 335 | dev: true 336 | optional: true 337 | 338 | /@esbuild/darwin-arm64@0.21.5: 339 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 340 | engines: {node: '>=12'} 341 | cpu: [arm64] 342 | os: [darwin] 343 | requiresBuild: true 344 | dev: true 345 | optional: true 346 | 347 | /@esbuild/darwin-arm64@0.23.1: 348 | resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} 349 | engines: {node: '>=18'} 350 | cpu: [arm64] 351 | os: [darwin] 352 | requiresBuild: true 353 | dev: true 354 | optional: true 355 | 356 | /@esbuild/darwin-x64@0.21.5: 357 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 358 | engines: {node: '>=12'} 359 | cpu: [x64] 360 | os: [darwin] 361 | requiresBuild: true 362 | dev: true 363 | optional: true 364 | 365 | /@esbuild/darwin-x64@0.23.1: 366 | resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} 367 | engines: {node: '>=18'} 368 | cpu: [x64] 369 | os: [darwin] 370 | requiresBuild: true 371 | dev: true 372 | optional: true 373 | 374 | /@esbuild/freebsd-arm64@0.21.5: 375 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 376 | engines: {node: '>=12'} 377 | cpu: [arm64] 378 | os: [freebsd] 379 | requiresBuild: true 380 | dev: true 381 | optional: true 382 | 383 | /@esbuild/freebsd-arm64@0.23.1: 384 | resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} 385 | engines: {node: '>=18'} 386 | cpu: [arm64] 387 | os: [freebsd] 388 | requiresBuild: true 389 | dev: true 390 | optional: true 391 | 392 | /@esbuild/freebsd-x64@0.21.5: 393 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 394 | engines: {node: '>=12'} 395 | cpu: [x64] 396 | os: [freebsd] 397 | requiresBuild: true 398 | dev: true 399 | optional: true 400 | 401 | /@esbuild/freebsd-x64@0.23.1: 402 | resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} 403 | engines: {node: '>=18'} 404 | cpu: [x64] 405 | os: [freebsd] 406 | requiresBuild: true 407 | dev: true 408 | optional: true 409 | 410 | /@esbuild/linux-arm64@0.21.5: 411 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 412 | engines: {node: '>=12'} 413 | cpu: [arm64] 414 | os: [linux] 415 | requiresBuild: true 416 | dev: true 417 | optional: true 418 | 419 | /@esbuild/linux-arm64@0.23.1: 420 | resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} 421 | engines: {node: '>=18'} 422 | cpu: [arm64] 423 | os: [linux] 424 | requiresBuild: true 425 | dev: true 426 | optional: true 427 | 428 | /@esbuild/linux-arm@0.21.5: 429 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 430 | engines: {node: '>=12'} 431 | cpu: [arm] 432 | os: [linux] 433 | requiresBuild: true 434 | dev: true 435 | optional: true 436 | 437 | /@esbuild/linux-arm@0.23.1: 438 | resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} 439 | engines: {node: '>=18'} 440 | cpu: [arm] 441 | os: [linux] 442 | requiresBuild: true 443 | dev: true 444 | optional: true 445 | 446 | /@esbuild/linux-ia32@0.21.5: 447 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 448 | engines: {node: '>=12'} 449 | cpu: [ia32] 450 | os: [linux] 451 | requiresBuild: true 452 | dev: true 453 | optional: true 454 | 455 | /@esbuild/linux-ia32@0.23.1: 456 | resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} 457 | engines: {node: '>=18'} 458 | cpu: [ia32] 459 | os: [linux] 460 | requiresBuild: true 461 | dev: true 462 | optional: true 463 | 464 | /@esbuild/linux-loong64@0.21.5: 465 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 466 | engines: {node: '>=12'} 467 | cpu: [loong64] 468 | os: [linux] 469 | requiresBuild: true 470 | dev: true 471 | optional: true 472 | 473 | /@esbuild/linux-loong64@0.23.1: 474 | resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} 475 | engines: {node: '>=18'} 476 | cpu: [loong64] 477 | os: [linux] 478 | requiresBuild: true 479 | dev: true 480 | optional: true 481 | 482 | /@esbuild/linux-mips64el@0.21.5: 483 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 484 | engines: {node: '>=12'} 485 | cpu: [mips64el] 486 | os: [linux] 487 | requiresBuild: true 488 | dev: true 489 | optional: true 490 | 491 | /@esbuild/linux-mips64el@0.23.1: 492 | resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} 493 | engines: {node: '>=18'} 494 | cpu: [mips64el] 495 | os: [linux] 496 | requiresBuild: true 497 | dev: true 498 | optional: true 499 | 500 | /@esbuild/linux-ppc64@0.21.5: 501 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 502 | engines: {node: '>=12'} 503 | cpu: [ppc64] 504 | os: [linux] 505 | requiresBuild: true 506 | dev: true 507 | optional: true 508 | 509 | /@esbuild/linux-ppc64@0.23.1: 510 | resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} 511 | engines: {node: '>=18'} 512 | cpu: [ppc64] 513 | os: [linux] 514 | requiresBuild: true 515 | dev: true 516 | optional: true 517 | 518 | /@esbuild/linux-riscv64@0.21.5: 519 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 520 | engines: {node: '>=12'} 521 | cpu: [riscv64] 522 | os: [linux] 523 | requiresBuild: true 524 | dev: true 525 | optional: true 526 | 527 | /@esbuild/linux-riscv64@0.23.1: 528 | resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} 529 | engines: {node: '>=18'} 530 | cpu: [riscv64] 531 | os: [linux] 532 | requiresBuild: true 533 | dev: true 534 | optional: true 535 | 536 | /@esbuild/linux-s390x@0.21.5: 537 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 538 | engines: {node: '>=12'} 539 | cpu: [s390x] 540 | os: [linux] 541 | requiresBuild: true 542 | dev: true 543 | optional: true 544 | 545 | /@esbuild/linux-s390x@0.23.1: 546 | resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} 547 | engines: {node: '>=18'} 548 | cpu: [s390x] 549 | os: [linux] 550 | requiresBuild: true 551 | dev: true 552 | optional: true 553 | 554 | /@esbuild/linux-x64@0.21.5: 555 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 556 | engines: {node: '>=12'} 557 | cpu: [x64] 558 | os: [linux] 559 | requiresBuild: true 560 | dev: true 561 | optional: true 562 | 563 | /@esbuild/linux-x64@0.23.1: 564 | resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} 565 | engines: {node: '>=18'} 566 | cpu: [x64] 567 | os: [linux] 568 | requiresBuild: true 569 | dev: true 570 | optional: true 571 | 572 | /@esbuild/netbsd-x64@0.21.5: 573 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 574 | engines: {node: '>=12'} 575 | cpu: [x64] 576 | os: [netbsd] 577 | requiresBuild: true 578 | dev: true 579 | optional: true 580 | 581 | /@esbuild/netbsd-x64@0.23.1: 582 | resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} 583 | engines: {node: '>=18'} 584 | cpu: [x64] 585 | os: [netbsd] 586 | requiresBuild: true 587 | dev: true 588 | optional: true 589 | 590 | /@esbuild/openbsd-arm64@0.23.1: 591 | resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} 592 | engines: {node: '>=18'} 593 | cpu: [arm64] 594 | os: [openbsd] 595 | requiresBuild: true 596 | dev: true 597 | optional: true 598 | 599 | /@esbuild/openbsd-x64@0.21.5: 600 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 601 | engines: {node: '>=12'} 602 | cpu: [x64] 603 | os: [openbsd] 604 | requiresBuild: true 605 | dev: true 606 | optional: true 607 | 608 | /@esbuild/openbsd-x64@0.23.1: 609 | resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} 610 | engines: {node: '>=18'} 611 | cpu: [x64] 612 | os: [openbsd] 613 | requiresBuild: true 614 | dev: true 615 | optional: true 616 | 617 | /@esbuild/sunos-x64@0.21.5: 618 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 619 | engines: {node: '>=12'} 620 | cpu: [x64] 621 | os: [sunos] 622 | requiresBuild: true 623 | dev: true 624 | optional: true 625 | 626 | /@esbuild/sunos-x64@0.23.1: 627 | resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} 628 | engines: {node: '>=18'} 629 | cpu: [x64] 630 | os: [sunos] 631 | requiresBuild: true 632 | dev: true 633 | optional: true 634 | 635 | /@esbuild/win32-arm64@0.21.5: 636 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 637 | engines: {node: '>=12'} 638 | cpu: [arm64] 639 | os: [win32] 640 | requiresBuild: true 641 | dev: true 642 | optional: true 643 | 644 | /@esbuild/win32-arm64@0.23.1: 645 | resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} 646 | engines: {node: '>=18'} 647 | cpu: [arm64] 648 | os: [win32] 649 | requiresBuild: true 650 | dev: true 651 | optional: true 652 | 653 | /@esbuild/win32-ia32@0.21.5: 654 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 655 | engines: {node: '>=12'} 656 | cpu: [ia32] 657 | os: [win32] 658 | requiresBuild: true 659 | dev: true 660 | optional: true 661 | 662 | /@esbuild/win32-ia32@0.23.1: 663 | resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} 664 | engines: {node: '>=18'} 665 | cpu: [ia32] 666 | os: [win32] 667 | requiresBuild: true 668 | dev: true 669 | optional: true 670 | 671 | /@esbuild/win32-x64@0.21.5: 672 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 673 | engines: {node: '>=12'} 674 | cpu: [x64] 675 | os: [win32] 676 | requiresBuild: true 677 | dev: true 678 | optional: true 679 | 680 | /@esbuild/win32-x64@0.23.1: 681 | resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} 682 | engines: {node: '>=18'} 683 | cpu: [x64] 684 | os: [win32] 685 | requiresBuild: true 686 | dev: true 687 | optional: true 688 | 689 | /@iconify/types@2.0.0: 690 | resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} 691 | dev: true 692 | 693 | /@iconify/utils@2.1.33: 694 | resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==} 695 | dependencies: 696 | '@antfu/install-pkg': 0.4.1 697 | '@antfu/utils': 0.7.10 698 | '@iconify/types': 2.0.0 699 | debug: 4.3.7 700 | kolorist: 1.8.0 701 | local-pkg: 0.5.0 702 | mlly: 1.7.2 703 | transitivePeerDependencies: 704 | - supports-color 705 | dev: true 706 | 707 | /@jridgewell/gen-mapping@0.3.5: 708 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 709 | engines: {node: '>=6.0.0'} 710 | dependencies: 711 | '@jridgewell/set-array': 1.2.1 712 | '@jridgewell/sourcemap-codec': 1.5.0 713 | '@jridgewell/trace-mapping': 0.3.25 714 | dev: true 715 | 716 | /@jridgewell/resolve-uri@3.1.2: 717 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 718 | engines: {node: '>=6.0.0'} 719 | dev: true 720 | 721 | /@jridgewell/set-array@1.2.1: 722 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 723 | engines: {node: '>=6.0.0'} 724 | dev: true 725 | 726 | /@jridgewell/sourcemap-codec@1.5.0: 727 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 728 | dev: true 729 | 730 | /@jridgewell/trace-mapping@0.3.25: 731 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 732 | dependencies: 733 | '@jridgewell/resolve-uri': 3.1.2 734 | '@jridgewell/sourcemap-codec': 1.5.0 735 | dev: true 736 | 737 | /@polka/url@1.0.0-next.28: 738 | resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} 739 | dev: true 740 | 741 | /@remix-run/router@1.21.0: 742 | resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} 743 | engines: {node: '>=14.0.0'} 744 | dev: false 745 | 746 | /@rollup/pluginutils@5.1.3: 747 | resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} 748 | engines: {node: '>=14.0.0'} 749 | peerDependencies: 750 | rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 751 | peerDependenciesMeta: 752 | rollup: 753 | optional: true 754 | dependencies: 755 | '@types/estree': 1.0.6 756 | estree-walker: 2.0.2 757 | picomatch: 4.0.2 758 | dev: true 759 | 760 | /@rollup/rollup-android-arm-eabi@4.25.0: 761 | resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} 762 | cpu: [arm] 763 | os: [android] 764 | requiresBuild: true 765 | dev: true 766 | optional: true 767 | 768 | /@rollup/rollup-android-arm64@4.25.0: 769 | resolution: {integrity: sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==} 770 | cpu: [arm64] 771 | os: [android] 772 | requiresBuild: true 773 | dev: true 774 | optional: true 775 | 776 | /@rollup/rollup-darwin-arm64@4.25.0: 777 | resolution: {integrity: sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==} 778 | cpu: [arm64] 779 | os: [darwin] 780 | requiresBuild: true 781 | dev: true 782 | optional: true 783 | 784 | /@rollup/rollup-darwin-x64@4.25.0: 785 | resolution: {integrity: sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==} 786 | cpu: [x64] 787 | os: [darwin] 788 | requiresBuild: true 789 | dev: true 790 | optional: true 791 | 792 | /@rollup/rollup-freebsd-arm64@4.25.0: 793 | resolution: {integrity: sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==} 794 | cpu: [arm64] 795 | os: [freebsd] 796 | requiresBuild: true 797 | dev: true 798 | optional: true 799 | 800 | /@rollup/rollup-freebsd-x64@4.25.0: 801 | resolution: {integrity: sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==} 802 | cpu: [x64] 803 | os: [freebsd] 804 | requiresBuild: true 805 | dev: true 806 | optional: true 807 | 808 | /@rollup/rollup-linux-arm-gnueabihf@4.25.0: 809 | resolution: {integrity: sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==} 810 | cpu: [arm] 811 | os: [linux] 812 | requiresBuild: true 813 | dev: true 814 | optional: true 815 | 816 | /@rollup/rollup-linux-arm-musleabihf@4.25.0: 817 | resolution: {integrity: sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==} 818 | cpu: [arm] 819 | os: [linux] 820 | requiresBuild: true 821 | dev: true 822 | optional: true 823 | 824 | /@rollup/rollup-linux-arm64-gnu@4.25.0: 825 | resolution: {integrity: sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==} 826 | cpu: [arm64] 827 | os: [linux] 828 | requiresBuild: true 829 | dev: true 830 | optional: true 831 | 832 | /@rollup/rollup-linux-arm64-musl@4.25.0: 833 | resolution: {integrity: sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==} 834 | cpu: [arm64] 835 | os: [linux] 836 | requiresBuild: true 837 | dev: true 838 | optional: true 839 | 840 | /@rollup/rollup-linux-powerpc64le-gnu@4.25.0: 841 | resolution: {integrity: sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==} 842 | cpu: [ppc64] 843 | os: [linux] 844 | requiresBuild: true 845 | dev: true 846 | optional: true 847 | 848 | /@rollup/rollup-linux-riscv64-gnu@4.25.0: 849 | resolution: {integrity: sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==} 850 | cpu: [riscv64] 851 | os: [linux] 852 | requiresBuild: true 853 | dev: true 854 | optional: true 855 | 856 | /@rollup/rollup-linux-s390x-gnu@4.25.0: 857 | resolution: {integrity: sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==} 858 | cpu: [s390x] 859 | os: [linux] 860 | requiresBuild: true 861 | dev: true 862 | optional: true 863 | 864 | /@rollup/rollup-linux-x64-gnu@4.25.0: 865 | resolution: {integrity: sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==} 866 | cpu: [x64] 867 | os: [linux] 868 | requiresBuild: true 869 | dev: true 870 | optional: true 871 | 872 | /@rollup/rollup-linux-x64-musl@4.25.0: 873 | resolution: {integrity: sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==} 874 | cpu: [x64] 875 | os: [linux] 876 | requiresBuild: true 877 | dev: true 878 | optional: true 879 | 880 | /@rollup/rollup-win32-arm64-msvc@4.25.0: 881 | resolution: {integrity: sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==} 882 | cpu: [arm64] 883 | os: [win32] 884 | requiresBuild: true 885 | dev: true 886 | optional: true 887 | 888 | /@rollup/rollup-win32-ia32-msvc@4.25.0: 889 | resolution: {integrity: sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==} 890 | cpu: [ia32] 891 | os: [win32] 892 | requiresBuild: true 893 | dev: true 894 | optional: true 895 | 896 | /@rollup/rollup-win32-x64-msvc@4.25.0: 897 | resolution: {integrity: sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==} 898 | cpu: [x64] 899 | os: [win32] 900 | requiresBuild: true 901 | dev: true 902 | optional: true 903 | 904 | /@tauri-apps/api@2.0.0: 905 | resolution: {integrity: sha512-moKgCp2EX7X5GiOx/G/bmoEpkFQVVmyS98UaJU4xUVzan+E1BdwlAKcbip+cGldshYOqL4JSwAEN1OkRXeug0Q==} 906 | dev: false 907 | 908 | /@tauri-apps/cli-darwin-arm64@2.0.0: 909 | resolution: {integrity: sha512-+agYqg2c77imaMfKw7mzqecVIDGcwr6bZMdglJ808O2UjTFzMwnAam1sU26YBYU+IyIjwOu00fm9Azpal+N/Ew==} 910 | engines: {node: '>= 10'} 911 | cpu: [arm64] 912 | os: [darwin] 913 | requiresBuild: true 914 | dev: true 915 | optional: true 916 | 917 | /@tauri-apps/cli-darwin-x64@2.0.0: 918 | resolution: {integrity: sha512-keN2PLTTcZmbWwFMup/NGcshmvyLnhRPChO8lbm9C5a0IY7zUNQUD7/o/zIulQdLJqDxkdpWJ1j2jTycAtvtKQ==} 919 | engines: {node: '>= 10'} 920 | cpu: [x64] 921 | os: [darwin] 922 | requiresBuild: true 923 | dev: true 924 | optional: true 925 | 926 | /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0: 927 | resolution: {integrity: sha512-FQJNrlCUBb9E7Fhp5ARy+Or8lSvorG41aVrfi0cGNvv1QlIGSj77TN7SKK+L1jAGzKj1Bl2kCZIESF6Zi8N/+Q==} 928 | engines: {node: '>= 10'} 929 | cpu: [arm] 930 | os: [linux] 931 | requiresBuild: true 932 | dev: true 933 | optional: true 934 | 935 | /@tauri-apps/cli-linux-arm64-gnu@2.0.0: 936 | resolution: {integrity: sha512-TK3VrZG5LK1NGueKwnZA1/3gj/qkwry001MNCHXjT6394dwrDv+digCc9Qc569h+xeH/FF71jyoiRIu3gRE6iA==} 937 | engines: {node: '>= 10'} 938 | cpu: [arm64] 939 | os: [linux] 940 | requiresBuild: true 941 | dev: true 942 | optional: true 943 | 944 | /@tauri-apps/cli-linux-arm64-musl@2.0.0: 945 | resolution: {integrity: sha512-E3hRmS/0m8YUYMTKZtBExpk/284CTi2nymks0dK0L1j+3KjffL7DiilnIfNFmTvWBgMrs0cVCtoaN/ba/A9mNA==} 946 | engines: {node: '>= 10'} 947 | cpu: [arm64] 948 | os: [linux] 949 | requiresBuild: true 950 | dev: true 951 | optional: true 952 | 953 | /@tauri-apps/cli-linux-x64-gnu@2.0.0: 954 | resolution: {integrity: sha512-veX4BJp5xnW8KmxVjchWt4oZEIvKGhuSR7qU1WpqTR21e/eTe/ksGsdXPsqOKQvv/w1X6jhqmlPvhnFmDwUJ/w==} 955 | engines: {node: '>= 10'} 956 | cpu: [x64] 957 | os: [linux] 958 | requiresBuild: true 959 | dev: true 960 | optional: true 961 | 962 | /@tauri-apps/cli-linux-x64-musl@2.0.0: 963 | resolution: {integrity: sha512-9Eso/8wbsWbOyd9PZEIzN/48ZQJrUGQqGZtglcjUku0lO76mnX0fOnit4nQ57Oj0wezJPhv4mgSseG1OsTIVzw==} 964 | engines: {node: '>= 10'} 965 | cpu: [x64] 966 | os: [linux] 967 | requiresBuild: true 968 | dev: true 969 | optional: true 970 | 971 | /@tauri-apps/cli-win32-arm64-msvc@2.0.0: 972 | resolution: {integrity: sha512-ky8vWAuDUf8WGt9+a0G/EbU0OhdIkogelh9qjIYGHbyEYAJqXfN5P40aHUEg3y8ngQ0YGwRX5ePsQsSZiiR5PQ==} 973 | engines: {node: '>= 10'} 974 | cpu: [arm64] 975 | os: [win32] 976 | requiresBuild: true 977 | dev: true 978 | optional: true 979 | 980 | /@tauri-apps/cli-win32-ia32-msvc@2.0.0: 981 | resolution: {integrity: sha512-uD45cLZ/EBaT8o4a27tHW7t5UKFplnvDLt/uSUaCpJ3NyOTV6nMXOUrJBe+hH9hSBohqNAF7LEyYo1p932DWFg==} 982 | engines: {node: '>= 10'} 983 | cpu: [ia32] 984 | os: [win32] 985 | requiresBuild: true 986 | dev: true 987 | optional: true 988 | 989 | /@tauri-apps/cli-win32-x64-msvc@2.0.0: 990 | resolution: {integrity: sha512-oFlo14YMsvyhJHmmHgRuOpJ1L9w15193c1Nfj1DksS2LHj6tLzirI7YrAF9inY/XjHFjNHzYPmBpABibkf/9wQ==} 991 | engines: {node: '>= 10'} 992 | cpu: [x64] 993 | os: [win32] 994 | requiresBuild: true 995 | dev: true 996 | optional: true 997 | 998 | /@tauri-apps/cli@2.0.0: 999 | resolution: {integrity: sha512-xxmPllRa6w/LRRcPczST3yHrYoi8l6ZZmzwabEmM0cgDdhVDmX+Y4oDJkiKD+8cVdxwwEzIuIKuaCwsX8iNsgA==} 1000 | engines: {node: '>= 10'} 1001 | hasBin: true 1002 | optionalDependencies: 1003 | '@tauri-apps/cli-darwin-arm64': 2.0.0 1004 | '@tauri-apps/cli-darwin-x64': 2.0.0 1005 | '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0 1006 | '@tauri-apps/cli-linux-arm64-gnu': 2.0.0 1007 | '@tauri-apps/cli-linux-arm64-musl': 2.0.0 1008 | '@tauri-apps/cli-linux-x64-gnu': 2.0.0 1009 | '@tauri-apps/cli-linux-x64-musl': 2.0.0 1010 | '@tauri-apps/cli-win32-arm64-msvc': 2.0.0 1011 | '@tauri-apps/cli-win32-ia32-msvc': 2.0.0 1012 | '@tauri-apps/cli-win32-x64-msvc': 2.0.0 1013 | dev: true 1014 | 1015 | /@tauri-apps/plugin-os@2.0.0: 1016 | resolution: {integrity: sha512-M7hG/nNyQYTJxVG/UhTKhp9mpXriwWzrs9mqDreB8mIgqA3ek5nHLdwRZJWhkKjZrnDT4v9CpA9BhYeplTlAiA==} 1017 | dependencies: 1018 | '@tauri-apps/api': 2.0.0 1019 | dev: false 1020 | 1021 | /@tauri-apps/plugin-process@2.0.0: 1022 | resolution: {integrity: sha512-OYzi0GnkrF4NAnsHZU7U3tjSoP0PbeAlO7T1Z+vJoBUH9sFQ1NSLqWYWQyf8hcb3gVWe7P1JggjiskO+LST1ug==} 1023 | dependencies: 1024 | '@tauri-apps/api': 2.0.0 1025 | dev: false 1026 | 1027 | /@tauri-apps/plugin-shell@2.0.0: 1028 | resolution: {integrity: sha512-OpW2+ycgJLrEoZityWeWYk+6ZWP9VyiAfbO+N/O8VfLkqyOym8kXh7odKDfINx9RAotkSGBtQM4abyKfJDkcUg==} 1029 | dependencies: 1030 | '@tauri-apps/api': 2.0.0 1031 | dev: false 1032 | 1033 | /@types/babel__core@7.20.5: 1034 | resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} 1035 | dependencies: 1036 | '@babel/parser': 7.26.2 1037 | '@babel/types': 7.26.0 1038 | '@types/babel__generator': 7.6.8 1039 | '@types/babel__template': 7.4.4 1040 | '@types/babel__traverse': 7.20.6 1041 | dev: true 1042 | 1043 | /@types/babel__generator@7.6.8: 1044 | resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} 1045 | dependencies: 1046 | '@babel/types': 7.26.0 1047 | dev: true 1048 | 1049 | /@types/babel__template@7.4.4: 1050 | resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 1051 | dependencies: 1052 | '@babel/parser': 7.26.2 1053 | '@babel/types': 7.26.0 1054 | dev: true 1055 | 1056 | /@types/babel__traverse@7.20.6: 1057 | resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} 1058 | dependencies: 1059 | '@babel/types': 7.26.0 1060 | dev: true 1061 | 1062 | /@types/estree@1.0.6: 1063 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 1064 | dev: true 1065 | 1066 | /@types/node@22.9.0: 1067 | resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} 1068 | dependencies: 1069 | undici-types: 6.19.8 1070 | 1071 | /@types/prop-types@15.7.13: 1072 | resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 1073 | 1074 | /@types/react-dom@18.2.7: 1075 | resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} 1076 | dependencies: 1077 | '@types/react': 18.2.15 1078 | dev: true 1079 | 1080 | /@types/react@18.2.15: 1081 | resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==} 1082 | dependencies: 1083 | '@types/prop-types': 15.7.13 1084 | '@types/scheduler': 0.23.0 1085 | csstype: 3.1.3 1086 | 1087 | /@types/scheduler@0.23.0: 1088 | resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} 1089 | 1090 | /@unocss/astro@0.64.0(vite@5.3.1)(vue@3.5.12): 1091 | resolution: {integrity: sha512-4Ijf3cQblSjdC3XV4SvzkEj17z6gNsuMGy7M+TvNN4cZhGLWQCIChtHR525ESGxJ4kdZ6FoIUoxmLdWHMOpX4Q==} 1092 | peerDependencies: 1093 | vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 1094 | peerDependenciesMeta: 1095 | vite: 1096 | optional: true 1097 | dependencies: 1098 | '@unocss/core': 0.64.0 1099 | '@unocss/reset': 0.64.0 1100 | '@unocss/vite': 0.64.0(vite@5.3.1)(vue@3.5.12) 1101 | vite: 5.3.1(@types/node@22.9.0) 1102 | transitivePeerDependencies: 1103 | - rollup 1104 | - supports-color 1105 | - vue 1106 | dev: true 1107 | 1108 | /@unocss/cli@0.64.0: 1109 | resolution: {integrity: sha512-xfY/qm7vr/4Qaf+CcQHuBJSg5ApZBvvGVD1zwyGFgfhfOFYR1hI3DS2zl75zav6btEwwXsjY7AUv6uYGF4M7dA==} 1110 | engines: {node: '>=14'} 1111 | hasBin: true 1112 | dependencies: 1113 | '@ampproject/remapping': 2.3.0 1114 | '@rollup/pluginutils': 5.1.3 1115 | '@unocss/config': 0.64.0 1116 | '@unocss/core': 0.64.0 1117 | '@unocss/preset-uno': 0.64.0 1118 | cac: 6.7.14 1119 | chokidar: 3.6.0 1120 | colorette: 2.0.20 1121 | consola: 3.2.3 1122 | magic-string: 0.30.12 1123 | pathe: 1.1.2 1124 | perfect-debounce: 1.0.0 1125 | tinyglobby: 0.2.10 1126 | transitivePeerDependencies: 1127 | - rollup 1128 | - supports-color 1129 | dev: true 1130 | 1131 | /@unocss/config@0.64.0: 1132 | resolution: {integrity: sha512-L97x4vEk7jNG5ptZY5Xp0xgEk//tbMpQVm2BzfyL7w+Hg8X3AV4YjFL6hysHvpYiTdUCVaZg+S0s3b7wuj8Mqw==} 1133 | engines: {node: '>=14'} 1134 | dependencies: 1135 | '@unocss/core': 0.64.0 1136 | unconfig: 0.5.5 1137 | transitivePeerDependencies: 1138 | - supports-color 1139 | dev: true 1140 | 1141 | /@unocss/core@0.64.0: 1142 | resolution: {integrity: sha512-Qb8wWPYNlTagCdJGzULew+e3NMM8Bd7fr38lDLgrMj+njop+wzkSe1ZZOyMMH9yHSq/Rznn5eCjnyzyHwxGslQ==} 1143 | dev: true 1144 | 1145 | /@unocss/extractor-arbitrary-variants@0.64.0: 1146 | resolution: {integrity: sha512-oVB8l8zM+x0MQJTkraRcsrfJnWEwyPVgMgtzmNUm//HqV+xTrjZCNtOqHFNIZdj/+w0gkErGQLxzRwyPjlHq4g==} 1147 | dependencies: 1148 | '@unocss/core': 0.64.0 1149 | dev: true 1150 | 1151 | /@unocss/inspector@0.64.0(vue@3.5.12): 1152 | resolution: {integrity: sha512-aFEfxEuPOpbPNH3j1CLLnN7ZyZkc64XoxZbz7RbG20Wy5oJxonOnlu+Wikz9SfGvIyF16MVAMCkHu12WFRRC+g==} 1153 | dependencies: 1154 | '@unocss/core': 0.64.0 1155 | '@unocss/rule-utils': 0.64.0 1156 | gzip-size: 6.0.0 1157 | sirv: 2.0.4 1158 | vue-flow-layout: 0.1.1(vue@3.5.12) 1159 | transitivePeerDependencies: 1160 | - vue 1161 | dev: true 1162 | 1163 | /@unocss/postcss@0.64.0(postcss@8.4.48): 1164 | resolution: {integrity: sha512-OMDhAUDEzbb7i+fcYEYNxwdWJLSYklMrFGSC60ADK96UPX/B9S0z1pBz7N34DRPPIzg6shO6NQfDHOaxLelAeg==} 1165 | engines: {node: '>=14'} 1166 | peerDependencies: 1167 | postcss: ^8.4.21 1168 | dependencies: 1169 | '@unocss/config': 0.64.0 1170 | '@unocss/core': 0.64.0 1171 | '@unocss/rule-utils': 0.64.0 1172 | css-tree: 3.0.1 1173 | postcss: 8.4.48 1174 | tinyglobby: 0.2.10 1175 | transitivePeerDependencies: 1176 | - supports-color 1177 | dev: true 1178 | 1179 | /@unocss/preset-attributify@0.64.0: 1180 | resolution: {integrity: sha512-3T1mktq5rAQxHXtdLkjjj1UOjPwy9iGbVUChvxyaGV5oOsj1mvfe1oetxz8HqAVQak8MtvsJzFzvuuQQln/6OA==} 1181 | dependencies: 1182 | '@unocss/core': 0.64.0 1183 | dev: true 1184 | 1185 | /@unocss/preset-icons@0.64.0: 1186 | resolution: {integrity: sha512-jhozA4r583agZZpKttdootaWfvQ29lY/kHxNU1Ah2xeRQcVXXEh7M3cG0bo9HSIX9/BgXSk5rWQlqSPIqFl4Lw==} 1187 | dependencies: 1188 | '@iconify/utils': 2.1.33 1189 | '@unocss/core': 0.64.0 1190 | ofetch: 1.4.1 1191 | transitivePeerDependencies: 1192 | - supports-color 1193 | dev: true 1194 | 1195 | /@unocss/preset-mini@0.64.0: 1196 | resolution: {integrity: sha512-bc7zanalVQUrETJ06eyS7y/lhceRlY8kBG/lRCV/dYmKl4Ho/s57LrpZH0G63OcO6IfWIjwoZHVC8/RHAqnYvQ==} 1197 | dependencies: 1198 | '@unocss/core': 0.64.0 1199 | '@unocss/extractor-arbitrary-variants': 0.64.0 1200 | '@unocss/rule-utils': 0.64.0 1201 | dev: true 1202 | 1203 | /@unocss/preset-tagify@0.64.0: 1204 | resolution: {integrity: sha512-WlRQXYgtVzJpVlZ+itXhrQyvMj6XW1InNIfvAHMorr5BGvMGETLRnuWwYYhGg2YDF/g+/EucU5PQmk9UkurBzg==} 1205 | dependencies: 1206 | '@unocss/core': 0.64.0 1207 | dev: true 1208 | 1209 | /@unocss/preset-typography@0.64.0: 1210 | resolution: {integrity: sha512-hMKxhHTRUjvwB0gcdWOh6zWWolH9pvIvgB4p2GaFT1vKyFD0wkTZ/7S/Q3OMKJyevSKHyIgKd+PhNGKTx5FuQQ==} 1211 | dependencies: 1212 | '@unocss/core': 0.64.0 1213 | '@unocss/preset-mini': 0.64.0 1214 | dev: true 1215 | 1216 | /@unocss/preset-uno@0.64.0: 1217 | resolution: {integrity: sha512-gUmuL8anty551r/Q2XU5wc0aNZ+te4yydnamXHSUv3EkX6PCphOaiWsQ5f95fj26G8EYH9fLBvxqXurFBPM7og==} 1218 | dependencies: 1219 | '@unocss/core': 0.64.0 1220 | '@unocss/preset-mini': 0.64.0 1221 | '@unocss/preset-wind': 0.64.0 1222 | '@unocss/rule-utils': 0.64.0 1223 | dev: true 1224 | 1225 | /@unocss/preset-web-fonts@0.64.0: 1226 | resolution: {integrity: sha512-qraIhS0tCFHvdPQnzGTfi/dggwyboWPU8UQn8oLMsmPKogNPsYQfjrtTZs8X6F1KNaPV18c6saaWYvVZ8tXPoA==} 1227 | dependencies: 1228 | '@unocss/core': 0.64.0 1229 | ofetch: 1.4.1 1230 | dev: true 1231 | 1232 | /@unocss/preset-wind@0.64.0: 1233 | resolution: {integrity: sha512-cJbZI4etFrIIQoC1VhRqyEZU5fUaYqOH3uIt5lM3osxBdAvHds7SPjLRbdR612US7JbuPeFhMMRnA1EYoo39sQ==} 1234 | dependencies: 1235 | '@unocss/core': 0.64.0 1236 | '@unocss/preset-mini': 0.64.0 1237 | '@unocss/rule-utils': 0.64.0 1238 | dev: true 1239 | 1240 | /@unocss/reset@0.64.0: 1241 | resolution: {integrity: sha512-75SiDtRX/mtg/7GWeoLfDfdWF4z59zF1XesL46FNd2hDZL36a+SZHIKB/J+PPzLyX9irqm3mAETS2PNfynuJpA==} 1242 | dev: true 1243 | 1244 | /@unocss/rule-utils@0.64.0: 1245 | resolution: {integrity: sha512-R5b/uspq6XsmpEqhxSzOOePHsS+pdxya+0pkQw7m6thsUxNDL7kVDpBiz2iNX5lnwagvhyhUWYu85a8XmZ8ymw==} 1246 | engines: {node: '>=14'} 1247 | dependencies: 1248 | '@unocss/core': 0.64.0 1249 | magic-string: 0.30.12 1250 | dev: true 1251 | 1252 | /@unocss/transformer-attributify-jsx@0.64.0: 1253 | resolution: {integrity: sha512-/kG7NFmqMCftK5DJUgMUbe9SWRJt20Z55o36aaCkBcEsrTSYBmWYDyIJPZa3TxsjO8H1qDekRVu7CgDxwlxMEQ==} 1254 | dependencies: 1255 | '@unocss/core': 0.64.0 1256 | dev: true 1257 | 1258 | /@unocss/transformer-compile-class@0.64.0: 1259 | resolution: {integrity: sha512-p1LZG2AUsD0FrkCSo1JOsWVQ+sEMcgnVCm6XtCgxBraV3nPFeZUyxmj9yEkt0HhfYkMTvdT155c3rDhbwP8AFw==} 1260 | dependencies: 1261 | '@unocss/core': 0.64.0 1262 | dev: true 1263 | 1264 | /@unocss/transformer-directives@0.64.0: 1265 | resolution: {integrity: sha512-+e2bDEQMEsfq4KZ2R+GQNrEv0bL3E1KbXGPQXUiMGitmZzzagDfIBk9VTP3gNhU+hgTaWtjXlReeap1eSmwKGQ==} 1266 | dependencies: 1267 | '@unocss/core': 0.64.0 1268 | '@unocss/rule-utils': 0.64.0 1269 | css-tree: 3.0.1 1270 | dev: true 1271 | 1272 | /@unocss/transformer-variant-group@0.64.0: 1273 | resolution: {integrity: sha512-c4CN+W8ShBhGIma3KHHcBe7CRljRwZ0f5UamRrUIMs28a2jfa1TlPlr/4Ke5b6icr0mwTGajJEUaPanOK0Fp1A==} 1274 | dependencies: 1275 | '@unocss/core': 0.64.0 1276 | dev: true 1277 | 1278 | /@unocss/vite@0.64.0(vite@5.3.1)(vue@3.5.12): 1279 | resolution: {integrity: sha512-QrfXlI8YcIaqQc4WRVrLbCho8eEi5pjs1/C8AwnUHGximEDN6MZNUk0htjo4QZ+50IA2b4RrYdz1N3875bJoFg==} 1280 | peerDependencies: 1281 | vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 1282 | dependencies: 1283 | '@ampproject/remapping': 2.3.0 1284 | '@rollup/pluginutils': 5.1.3 1285 | '@unocss/config': 0.64.0 1286 | '@unocss/core': 0.64.0 1287 | '@unocss/inspector': 0.64.0(vue@3.5.12) 1288 | chokidar: 3.6.0 1289 | magic-string: 0.30.12 1290 | tinyglobby: 0.2.10 1291 | vite: 5.3.1(@types/node@22.9.0) 1292 | transitivePeerDependencies: 1293 | - rollup 1294 | - supports-color 1295 | - vue 1296 | dev: true 1297 | 1298 | /@vitejs/plugin-react@4.2.1(vite@5.3.1): 1299 | resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} 1300 | engines: {node: ^14.18.0 || >=16.0.0} 1301 | peerDependencies: 1302 | vite: ^4.2.0 || ^5.0.0 1303 | dependencies: 1304 | '@babel/core': 7.26.0 1305 | '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) 1306 | '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) 1307 | '@types/babel__core': 7.20.5 1308 | react-refresh: 0.14.2 1309 | vite: 5.3.1(@types/node@22.9.0) 1310 | transitivePeerDependencies: 1311 | - supports-color 1312 | dev: true 1313 | 1314 | /@vue/compiler-core@3.5.12: 1315 | resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} 1316 | dependencies: 1317 | '@babel/parser': 7.26.2 1318 | '@vue/shared': 3.5.12 1319 | entities: 4.5.0 1320 | estree-walker: 2.0.2 1321 | source-map-js: 1.2.1 1322 | dev: true 1323 | 1324 | /@vue/compiler-dom@3.5.12: 1325 | resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} 1326 | dependencies: 1327 | '@vue/compiler-core': 3.5.12 1328 | '@vue/shared': 3.5.12 1329 | dev: true 1330 | 1331 | /@vue/compiler-sfc@3.5.12: 1332 | resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} 1333 | dependencies: 1334 | '@babel/parser': 7.26.2 1335 | '@vue/compiler-core': 3.5.12 1336 | '@vue/compiler-dom': 3.5.12 1337 | '@vue/compiler-ssr': 3.5.12 1338 | '@vue/shared': 3.5.12 1339 | estree-walker: 2.0.2 1340 | magic-string: 0.30.12 1341 | postcss: 8.4.48 1342 | source-map-js: 1.2.1 1343 | dev: true 1344 | 1345 | /@vue/compiler-ssr@3.5.12: 1346 | resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} 1347 | dependencies: 1348 | '@vue/compiler-dom': 3.5.12 1349 | '@vue/shared': 3.5.12 1350 | dev: true 1351 | 1352 | /@vue/reactivity@3.5.12: 1353 | resolution: {integrity: sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==} 1354 | dependencies: 1355 | '@vue/shared': 3.5.12 1356 | dev: true 1357 | 1358 | /@vue/runtime-core@3.5.12: 1359 | resolution: {integrity: sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==} 1360 | dependencies: 1361 | '@vue/reactivity': 3.5.12 1362 | '@vue/shared': 3.5.12 1363 | dev: true 1364 | 1365 | /@vue/runtime-dom@3.5.12: 1366 | resolution: {integrity: sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==} 1367 | dependencies: 1368 | '@vue/reactivity': 3.5.12 1369 | '@vue/runtime-core': 3.5.12 1370 | '@vue/shared': 3.5.12 1371 | csstype: 3.1.3 1372 | dev: true 1373 | 1374 | /@vue/server-renderer@3.5.12(vue@3.5.12): 1375 | resolution: {integrity: sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==} 1376 | peerDependencies: 1377 | vue: 3.5.12 1378 | dependencies: 1379 | '@vue/compiler-ssr': 3.5.12 1380 | '@vue/shared': 3.5.12 1381 | vue: 3.5.12(typescript@5.2.2) 1382 | dev: true 1383 | 1384 | /@vue/shared@3.5.12: 1385 | resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} 1386 | dev: true 1387 | 1388 | /acorn@8.14.0: 1389 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 1390 | engines: {node: '>=0.4.0'} 1391 | hasBin: true 1392 | dev: true 1393 | 1394 | /anymatch@3.1.3: 1395 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 1396 | engines: {node: '>= 8'} 1397 | dependencies: 1398 | normalize-path: 3.0.0 1399 | picomatch: 2.3.1 1400 | dev: true 1401 | 1402 | /binary-extensions@2.3.0: 1403 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 1404 | engines: {node: '>=8'} 1405 | dev: true 1406 | 1407 | /braces@3.0.3: 1408 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 1409 | engines: {node: '>=8'} 1410 | dependencies: 1411 | fill-range: 7.1.1 1412 | dev: true 1413 | 1414 | /browserslist@4.24.2: 1415 | resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} 1416 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 1417 | hasBin: true 1418 | dependencies: 1419 | caniuse-lite: 1.0.30001680 1420 | electron-to-chromium: 1.5.55 1421 | node-releases: 2.0.18 1422 | update-browserslist-db: 1.1.1(browserslist@4.24.2) 1423 | dev: true 1424 | 1425 | /bundle-require@5.0.0(esbuild@0.21.5): 1426 | resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} 1427 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1428 | peerDependencies: 1429 | esbuild: '>=0.18' 1430 | dependencies: 1431 | esbuild: 0.21.5 1432 | load-tsconfig: 0.2.5 1433 | dev: true 1434 | 1435 | /cac@6.7.14: 1436 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 1437 | engines: {node: '>=8'} 1438 | dev: true 1439 | 1440 | /caniuse-lite@1.0.30001680: 1441 | resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} 1442 | dev: true 1443 | 1444 | /chokidar@3.6.0: 1445 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 1446 | engines: {node: '>= 8.10.0'} 1447 | dependencies: 1448 | anymatch: 3.1.3 1449 | braces: 3.0.3 1450 | glob-parent: 5.1.2 1451 | is-binary-path: 2.1.0 1452 | is-glob: 4.0.3 1453 | normalize-path: 3.0.0 1454 | readdirp: 3.6.0 1455 | optionalDependencies: 1456 | fsevents: 2.3.3 1457 | dev: true 1458 | 1459 | /colorette@2.0.20: 1460 | resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 1461 | dev: true 1462 | 1463 | /confbox@0.1.8: 1464 | resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 1465 | dev: true 1466 | 1467 | /consola@3.2.3: 1468 | resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} 1469 | engines: {node: ^14.18.0 || >=16.10.0} 1470 | dev: true 1471 | 1472 | /convert-source-map@2.0.0: 1473 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 1474 | dev: true 1475 | 1476 | /css-tree@3.0.1: 1477 | resolution: {integrity: sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==} 1478 | engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} 1479 | dependencies: 1480 | mdn-data: 2.12.1 1481 | source-map-js: 1.2.1 1482 | dev: true 1483 | 1484 | /csstype@3.1.3: 1485 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 1486 | 1487 | /debug@4.3.7: 1488 | resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 1489 | engines: {node: '>=6.0'} 1490 | peerDependencies: 1491 | supports-color: '*' 1492 | peerDependenciesMeta: 1493 | supports-color: 1494 | optional: true 1495 | dependencies: 1496 | ms: 2.1.3 1497 | dev: true 1498 | 1499 | /defu@6.1.4: 1500 | resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 1501 | dev: true 1502 | 1503 | /destr@2.0.3: 1504 | resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} 1505 | dev: true 1506 | 1507 | /duplexer@0.1.2: 1508 | resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} 1509 | dev: true 1510 | 1511 | /electron-to-chromium@1.5.55: 1512 | resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} 1513 | dev: true 1514 | 1515 | /entities@4.5.0: 1516 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 1517 | engines: {node: '>=0.12'} 1518 | dev: true 1519 | 1520 | /esbuild@0.21.5: 1521 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 1522 | engines: {node: '>=12'} 1523 | hasBin: true 1524 | requiresBuild: true 1525 | optionalDependencies: 1526 | '@esbuild/aix-ppc64': 0.21.5 1527 | '@esbuild/android-arm': 0.21.5 1528 | '@esbuild/android-arm64': 0.21.5 1529 | '@esbuild/android-x64': 0.21.5 1530 | '@esbuild/darwin-arm64': 0.21.5 1531 | '@esbuild/darwin-x64': 0.21.5 1532 | '@esbuild/freebsd-arm64': 0.21.5 1533 | '@esbuild/freebsd-x64': 0.21.5 1534 | '@esbuild/linux-arm': 0.21.5 1535 | '@esbuild/linux-arm64': 0.21.5 1536 | '@esbuild/linux-ia32': 0.21.5 1537 | '@esbuild/linux-loong64': 0.21.5 1538 | '@esbuild/linux-mips64el': 0.21.5 1539 | '@esbuild/linux-ppc64': 0.21.5 1540 | '@esbuild/linux-riscv64': 0.21.5 1541 | '@esbuild/linux-s390x': 0.21.5 1542 | '@esbuild/linux-x64': 0.21.5 1543 | '@esbuild/netbsd-x64': 0.21.5 1544 | '@esbuild/openbsd-x64': 0.21.5 1545 | '@esbuild/sunos-x64': 0.21.5 1546 | '@esbuild/win32-arm64': 0.21.5 1547 | '@esbuild/win32-ia32': 0.21.5 1548 | '@esbuild/win32-x64': 0.21.5 1549 | dev: true 1550 | 1551 | /esbuild@0.23.1: 1552 | resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} 1553 | engines: {node: '>=18'} 1554 | hasBin: true 1555 | requiresBuild: true 1556 | optionalDependencies: 1557 | '@esbuild/aix-ppc64': 0.23.1 1558 | '@esbuild/android-arm': 0.23.1 1559 | '@esbuild/android-arm64': 0.23.1 1560 | '@esbuild/android-x64': 0.23.1 1561 | '@esbuild/darwin-arm64': 0.23.1 1562 | '@esbuild/darwin-x64': 0.23.1 1563 | '@esbuild/freebsd-arm64': 0.23.1 1564 | '@esbuild/freebsd-x64': 0.23.1 1565 | '@esbuild/linux-arm': 0.23.1 1566 | '@esbuild/linux-arm64': 0.23.1 1567 | '@esbuild/linux-ia32': 0.23.1 1568 | '@esbuild/linux-loong64': 0.23.1 1569 | '@esbuild/linux-mips64el': 0.23.1 1570 | '@esbuild/linux-ppc64': 0.23.1 1571 | '@esbuild/linux-riscv64': 0.23.1 1572 | '@esbuild/linux-s390x': 0.23.1 1573 | '@esbuild/linux-x64': 0.23.1 1574 | '@esbuild/netbsd-x64': 0.23.1 1575 | '@esbuild/openbsd-arm64': 0.23.1 1576 | '@esbuild/openbsd-x64': 0.23.1 1577 | '@esbuild/sunos-x64': 0.23.1 1578 | '@esbuild/win32-arm64': 0.23.1 1579 | '@esbuild/win32-ia32': 0.23.1 1580 | '@esbuild/win32-x64': 0.23.1 1581 | dev: true 1582 | 1583 | /escalade@3.2.0: 1584 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 1585 | engines: {node: '>=6'} 1586 | dev: true 1587 | 1588 | /estree-walker@2.0.2: 1589 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 1590 | dev: true 1591 | 1592 | /external-shallow-equal@1.0.0: 1593 | resolution: {integrity: sha512-OGermv+d6hXdjdV/rQMIrPqXv4phLC/mIu1wb3dgZ6iLTTt/Ee4svWWsJdZouIZoassSqRZ4aUIoFghzkmClpA==} 1594 | dev: false 1595 | 1596 | /fdir@6.4.2(picomatch@4.0.2): 1597 | resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} 1598 | peerDependencies: 1599 | picomatch: ^3 || ^4 1600 | peerDependenciesMeta: 1601 | picomatch: 1602 | optional: true 1603 | dependencies: 1604 | picomatch: 4.0.2 1605 | dev: true 1606 | 1607 | /fill-range@7.1.1: 1608 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 1609 | engines: {node: '>=8'} 1610 | dependencies: 1611 | to-regex-range: 5.0.1 1612 | dev: true 1613 | 1614 | /fsevents@2.3.3: 1615 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 1616 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1617 | os: [darwin] 1618 | requiresBuild: true 1619 | dev: true 1620 | optional: true 1621 | 1622 | /gensync@1.0.0-beta.2: 1623 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1624 | engines: {node: '>=6.9.0'} 1625 | dev: true 1626 | 1627 | /get-tsconfig@4.8.1: 1628 | resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} 1629 | dependencies: 1630 | resolve-pkg-maps: 1.0.0 1631 | dev: true 1632 | 1633 | /glob-parent@5.1.2: 1634 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1635 | engines: {node: '>= 6'} 1636 | dependencies: 1637 | is-glob: 4.0.3 1638 | dev: true 1639 | 1640 | /globals@11.12.0: 1641 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 1642 | engines: {node: '>=4'} 1643 | dev: true 1644 | 1645 | /gzip-size@6.0.0: 1646 | resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} 1647 | engines: {node: '>=10'} 1648 | dependencies: 1649 | duplexer: 0.1.2 1650 | dev: true 1651 | 1652 | /html-parse-stringify@3.0.1: 1653 | resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} 1654 | dependencies: 1655 | void-elements: 3.1.0 1656 | dev: false 1657 | 1658 | /i18next-browser-languagedetector@8.0.0: 1659 | resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} 1660 | dependencies: 1661 | '@babel/runtime': 7.26.0 1662 | dev: false 1663 | 1664 | /i18next-localstorage-backend@4.2.0: 1665 | resolution: {integrity: sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==} 1666 | dependencies: 1667 | '@babel/runtime': 7.26.0 1668 | dev: false 1669 | 1670 | /i18next@23.16.5: 1671 | resolution: {integrity: sha512-KTlhE3EP9x6pPTAW7dy0WKIhoCpfOGhRQlO+jttQLgzVaoOjWwBWramu7Pp0i+8wDNduuzXfe3kkVbzrKyrbTA==} 1672 | dependencies: 1673 | '@babel/runtime': 7.26.0 1674 | dev: false 1675 | 1676 | /importx@0.4.4: 1677 | resolution: {integrity: sha512-Lo1pukzAREqrBnnHC+tj+lreMTAvyxtkKsMxLY8H15M/bvLl54p3YuoTI70Tz7Il0AsgSlD7Lrk/FaApRcBL7w==} 1678 | dependencies: 1679 | bundle-require: 5.0.0(esbuild@0.21.5) 1680 | debug: 4.3.7 1681 | esbuild: 0.21.5 1682 | jiti: 2.0.0-beta.3 1683 | jiti-v1: /jiti@1.21.6 1684 | pathe: 1.1.2 1685 | tsx: 4.19.2 1686 | transitivePeerDependencies: 1687 | - supports-color 1688 | dev: true 1689 | 1690 | /is-binary-path@2.1.0: 1691 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 1692 | engines: {node: '>=8'} 1693 | dependencies: 1694 | binary-extensions: 2.3.0 1695 | dev: true 1696 | 1697 | /is-extglob@2.1.1: 1698 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1699 | engines: {node: '>=0.10.0'} 1700 | dev: true 1701 | 1702 | /is-glob@4.0.3: 1703 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1704 | engines: {node: '>=0.10.0'} 1705 | dependencies: 1706 | is-extglob: 2.1.1 1707 | dev: true 1708 | 1709 | /is-number@7.0.0: 1710 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1711 | engines: {node: '>=0.12.0'} 1712 | dev: true 1713 | 1714 | /jiti@1.21.6: 1715 | resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} 1716 | hasBin: true 1717 | dev: true 1718 | 1719 | /jiti@2.0.0-beta.3: 1720 | resolution: {integrity: sha512-pmfRbVRs/7khFrSAYnSiJ8C0D5GvzkE4Ey2pAvUcJsw1ly/p+7ut27jbJrjY79BpAJQJ4gXYFtK6d1Aub+9baQ==} 1721 | hasBin: true 1722 | dev: true 1723 | 1724 | /js-tokens@4.0.0: 1725 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1726 | 1727 | /jsesc@3.0.2: 1728 | resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} 1729 | engines: {node: '>=6'} 1730 | hasBin: true 1731 | dev: true 1732 | 1733 | /json5@2.2.3: 1734 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 1735 | engines: {node: '>=6'} 1736 | hasBin: true 1737 | dev: true 1738 | 1739 | /kolorist@1.8.0: 1740 | resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} 1741 | dev: true 1742 | 1743 | /load-tsconfig@0.2.5: 1744 | resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 1745 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1746 | dev: true 1747 | 1748 | /local-pkg@0.5.0: 1749 | resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} 1750 | engines: {node: '>=14'} 1751 | dependencies: 1752 | mlly: 1.7.2 1753 | pkg-types: 1.2.1 1754 | dev: true 1755 | 1756 | /loose-envify@1.4.0: 1757 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1758 | hasBin: true 1759 | dependencies: 1760 | js-tokens: 4.0.0 1761 | dev: false 1762 | 1763 | /lru-cache@5.1.1: 1764 | resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 1765 | dependencies: 1766 | yallist: 3.1.1 1767 | dev: true 1768 | 1769 | /magic-string@0.30.12: 1770 | resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} 1771 | dependencies: 1772 | '@jridgewell/sourcemap-codec': 1.5.0 1773 | dev: true 1774 | 1775 | /mdn-data@2.12.1: 1776 | resolution: {integrity: sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==} 1777 | dev: true 1778 | 1779 | /mlly@1.7.2: 1780 | resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==} 1781 | dependencies: 1782 | acorn: 8.14.0 1783 | pathe: 1.1.2 1784 | pkg-types: 1.2.1 1785 | ufo: 1.5.4 1786 | dev: true 1787 | 1788 | /mrmime@2.0.0: 1789 | resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} 1790 | engines: {node: '>=10'} 1791 | dev: true 1792 | 1793 | /ms@2.1.3: 1794 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1795 | dev: true 1796 | 1797 | /nanoid@3.3.7: 1798 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 1799 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1800 | hasBin: true 1801 | dev: true 1802 | 1803 | /node-fetch-native@1.6.4: 1804 | resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} 1805 | dev: true 1806 | 1807 | /node-releases@2.0.18: 1808 | resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} 1809 | dev: true 1810 | 1811 | /normalize-path@3.0.0: 1812 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1813 | engines: {node: '>=0.10.0'} 1814 | dev: true 1815 | 1816 | /ofetch@1.4.1: 1817 | resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 1818 | dependencies: 1819 | destr: 2.0.3 1820 | node-fetch-native: 1.6.4 1821 | ufo: 1.5.4 1822 | dev: true 1823 | 1824 | /package-manager-detector@0.2.2: 1825 | resolution: {integrity: sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==} 1826 | dev: true 1827 | 1828 | /pathe@1.1.2: 1829 | resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 1830 | dev: true 1831 | 1832 | /perfect-debounce@1.0.0: 1833 | resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} 1834 | dev: true 1835 | 1836 | /picocolors@1.1.1: 1837 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1838 | dev: true 1839 | 1840 | /picomatch@2.3.1: 1841 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1842 | engines: {node: '>=8.6'} 1843 | dev: true 1844 | 1845 | /picomatch@4.0.2: 1846 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 1847 | engines: {node: '>=12'} 1848 | dev: true 1849 | 1850 | /pkg-types@1.2.1: 1851 | resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} 1852 | dependencies: 1853 | confbox: 0.1.8 1854 | mlly: 1.7.2 1855 | pathe: 1.1.2 1856 | dev: true 1857 | 1858 | /postcss@8.4.48: 1859 | resolution: {integrity: sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==} 1860 | engines: {node: ^10 || ^12 || >=14} 1861 | dependencies: 1862 | nanoid: 3.3.7 1863 | picocolors: 1.1.1 1864 | source-map-js: 1.2.1 1865 | dev: true 1866 | 1867 | /react-dom@18.2.0(react@18.2.0): 1868 | resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} 1869 | peerDependencies: 1870 | react: ^18.2.0 1871 | dependencies: 1872 | loose-envify: 1.4.0 1873 | react: 18.2.0 1874 | scheduler: 0.23.2 1875 | dev: false 1876 | 1877 | /react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0)(react@18.2.0): 1878 | resolution: {integrity: sha512-R/Vg9wIli2P3FfeI8o1eNJUJue5LWpFsQePCHdQDmX0Co3zkr6kdT8gAseb/yGeWbNz1Txc4bKDQuZYsC0kQfw==} 1879 | peerDependencies: 1880 | i18next: '>= 23.2.3' 1881 | react: '>= 16.8.0' 1882 | react-dom: '*' 1883 | react-native: '*' 1884 | peerDependenciesMeta: 1885 | react-dom: 1886 | optional: true 1887 | react-native: 1888 | optional: true 1889 | dependencies: 1890 | '@babel/runtime': 7.26.0 1891 | html-parse-stringify: 3.0.1 1892 | i18next: 23.16.5 1893 | react: 18.2.0 1894 | react-dom: 18.2.0(react@18.2.0) 1895 | dev: false 1896 | 1897 | /react-refresh@0.14.2: 1898 | resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} 1899 | engines: {node: '>=0.10.0'} 1900 | dev: true 1901 | 1902 | /react-router-dom@6.28.0(react-dom@18.2.0)(react@18.2.0): 1903 | resolution: {integrity: sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==} 1904 | engines: {node: '>=14.0.0'} 1905 | peerDependencies: 1906 | react: '>=16.8' 1907 | react-dom: '>=16.8' 1908 | dependencies: 1909 | '@remix-run/router': 1.21.0 1910 | react: 18.2.0 1911 | react-dom: 18.2.0(react@18.2.0) 1912 | react-router: 6.28.0(react@18.2.0) 1913 | dev: false 1914 | 1915 | /react-router@6.28.0(react@18.2.0): 1916 | resolution: {integrity: sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==} 1917 | engines: {node: '>=14.0.0'} 1918 | peerDependencies: 1919 | react: '>=16.8' 1920 | dependencies: 1921 | '@remix-run/router': 1.21.0 1922 | react: 18.2.0 1923 | dev: false 1924 | 1925 | /react@18.2.0: 1926 | resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} 1927 | engines: {node: '>=0.10.0'} 1928 | dependencies: 1929 | loose-envify: 1.4.0 1930 | dev: false 1931 | 1932 | /readdirp@3.6.0: 1933 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1934 | engines: {node: '>=8.10.0'} 1935 | dependencies: 1936 | picomatch: 2.3.1 1937 | dev: true 1938 | 1939 | /regenerator-runtime@0.14.1: 1940 | resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 1941 | dev: false 1942 | 1943 | /resolve-pkg-maps@1.0.0: 1944 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 1945 | dev: true 1946 | 1947 | /rollup@4.25.0: 1948 | resolution: {integrity: sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==} 1949 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1950 | hasBin: true 1951 | dependencies: 1952 | '@types/estree': 1.0.6 1953 | optionalDependencies: 1954 | '@rollup/rollup-android-arm-eabi': 4.25.0 1955 | '@rollup/rollup-android-arm64': 4.25.0 1956 | '@rollup/rollup-darwin-arm64': 4.25.0 1957 | '@rollup/rollup-darwin-x64': 4.25.0 1958 | '@rollup/rollup-freebsd-arm64': 4.25.0 1959 | '@rollup/rollup-freebsd-x64': 4.25.0 1960 | '@rollup/rollup-linux-arm-gnueabihf': 4.25.0 1961 | '@rollup/rollup-linux-arm-musleabihf': 4.25.0 1962 | '@rollup/rollup-linux-arm64-gnu': 4.25.0 1963 | '@rollup/rollup-linux-arm64-musl': 4.25.0 1964 | '@rollup/rollup-linux-powerpc64le-gnu': 4.25.0 1965 | '@rollup/rollup-linux-riscv64-gnu': 4.25.0 1966 | '@rollup/rollup-linux-s390x-gnu': 4.25.0 1967 | '@rollup/rollup-linux-x64-gnu': 4.25.0 1968 | '@rollup/rollup-linux-x64-musl': 4.25.0 1969 | '@rollup/rollup-win32-arm64-msvc': 4.25.0 1970 | '@rollup/rollup-win32-ia32-msvc': 4.25.0 1971 | '@rollup/rollup-win32-x64-msvc': 4.25.0 1972 | fsevents: 2.3.3 1973 | dev: true 1974 | 1975 | /scheduler@0.23.2: 1976 | resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 1977 | dependencies: 1978 | loose-envify: 1.4.0 1979 | dev: false 1980 | 1981 | /semver@6.3.1: 1982 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1983 | hasBin: true 1984 | dev: true 1985 | 1986 | /sirv@2.0.4: 1987 | resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} 1988 | engines: {node: '>= 10'} 1989 | dependencies: 1990 | '@polka/url': 1.0.0-next.28 1991 | mrmime: 2.0.0 1992 | totalist: 3.0.1 1993 | dev: true 1994 | 1995 | /source-map-js@1.2.1: 1996 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1997 | engines: {node: '>=0.10.0'} 1998 | dev: true 1999 | 2000 | /tinyexec@0.3.1: 2001 | resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} 2002 | dev: true 2003 | 2004 | /tinyglobby@0.2.10: 2005 | resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} 2006 | engines: {node: '>=12.0.0'} 2007 | dependencies: 2008 | fdir: 6.4.2(picomatch@4.0.2) 2009 | picomatch: 4.0.2 2010 | dev: true 2011 | 2012 | /to-regex-range@5.0.1: 2013 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 2014 | engines: {node: '>=8.0'} 2015 | dependencies: 2016 | is-number: 7.0.0 2017 | dev: true 2018 | 2019 | /totalist@3.0.1: 2020 | resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} 2021 | engines: {node: '>=6'} 2022 | dev: true 2023 | 2024 | /tsx@4.19.2: 2025 | resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} 2026 | engines: {node: '>=18.0.0'} 2027 | hasBin: true 2028 | dependencies: 2029 | esbuild: 0.23.1 2030 | get-tsconfig: 4.8.1 2031 | optionalDependencies: 2032 | fsevents: 2.3.3 2033 | dev: true 2034 | 2035 | /typescript@5.2.2: 2036 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} 2037 | engines: {node: '>=14.17'} 2038 | hasBin: true 2039 | dev: true 2040 | 2041 | /ufo@1.5.4: 2042 | resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 2043 | dev: true 2044 | 2045 | /unconfig@0.5.5: 2046 | resolution: {integrity: sha512-VQZ5PT9HDX+qag0XdgQi8tJepPhXiR/yVOkn707gJDKo31lGjRilPREiQJ9Z6zd/Ugpv6ZvO5VxVIcatldYcNQ==} 2047 | dependencies: 2048 | '@antfu/utils': 0.7.10 2049 | defu: 6.1.4 2050 | importx: 0.4.4 2051 | transitivePeerDependencies: 2052 | - supports-color 2053 | dev: true 2054 | 2055 | /undici-types@6.19.8: 2056 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 2057 | 2058 | /unocss@0.64.0(postcss@8.4.48)(vite@5.3.1)(vue@3.5.12): 2059 | resolution: {integrity: sha512-wiEFRjGXSogzf/4+KICXjFDgSGloSCV1Ka2Dct/8Z8U+iwRqeVpHGVQcGjBFg9Uh0DH1fSVBbis2aPuIkT0nEA==} 2060 | engines: {node: '>=14'} 2061 | peerDependencies: 2062 | '@unocss/webpack': 0.64.0 2063 | vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 2064 | peerDependenciesMeta: 2065 | '@unocss/webpack': 2066 | optional: true 2067 | vite: 2068 | optional: true 2069 | dependencies: 2070 | '@unocss/astro': 0.64.0(vite@5.3.1)(vue@3.5.12) 2071 | '@unocss/cli': 0.64.0 2072 | '@unocss/core': 0.64.0 2073 | '@unocss/postcss': 0.64.0(postcss@8.4.48) 2074 | '@unocss/preset-attributify': 0.64.0 2075 | '@unocss/preset-icons': 0.64.0 2076 | '@unocss/preset-mini': 0.64.0 2077 | '@unocss/preset-tagify': 0.64.0 2078 | '@unocss/preset-typography': 0.64.0 2079 | '@unocss/preset-uno': 0.64.0 2080 | '@unocss/preset-web-fonts': 0.64.0 2081 | '@unocss/preset-wind': 0.64.0 2082 | '@unocss/transformer-attributify-jsx': 0.64.0 2083 | '@unocss/transformer-compile-class': 0.64.0 2084 | '@unocss/transformer-directives': 0.64.0 2085 | '@unocss/transformer-variant-group': 0.64.0 2086 | '@unocss/vite': 0.64.0(vite@5.3.1)(vue@3.5.12) 2087 | vite: 5.3.1(@types/node@22.9.0) 2088 | transitivePeerDependencies: 2089 | - postcss 2090 | - rollup 2091 | - supports-color 2092 | - vue 2093 | dev: true 2094 | 2095 | /update-browserslist-db@1.1.1(browserslist@4.24.2): 2096 | resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} 2097 | hasBin: true 2098 | peerDependencies: 2099 | browserslist: '>= 4.21.0' 2100 | dependencies: 2101 | browserslist: 4.24.2 2102 | escalade: 3.2.0 2103 | picocolors: 1.1.1 2104 | dev: true 2105 | 2106 | /vite@5.3.1(@types/node@22.9.0): 2107 | resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} 2108 | engines: {node: ^18.0.0 || >=20.0.0} 2109 | hasBin: true 2110 | peerDependencies: 2111 | '@types/node': ^18.0.0 || >=20.0.0 2112 | less: '*' 2113 | lightningcss: ^1.21.0 2114 | sass: '*' 2115 | stylus: '*' 2116 | sugarss: '*' 2117 | terser: ^5.4.0 2118 | peerDependenciesMeta: 2119 | '@types/node': 2120 | optional: true 2121 | less: 2122 | optional: true 2123 | lightningcss: 2124 | optional: true 2125 | sass: 2126 | optional: true 2127 | stylus: 2128 | optional: true 2129 | sugarss: 2130 | optional: true 2131 | terser: 2132 | optional: true 2133 | dependencies: 2134 | '@types/node': 22.9.0 2135 | esbuild: 0.21.5 2136 | postcss: 8.4.48 2137 | rollup: 4.25.0 2138 | optionalDependencies: 2139 | fsevents: 2.3.3 2140 | dev: true 2141 | 2142 | /void-elements@3.1.0: 2143 | resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} 2144 | engines: {node: '>=0.10.0'} 2145 | dev: false 2146 | 2147 | /vue-flow-layout@0.1.1(vue@3.5.12): 2148 | resolution: {integrity: sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==} 2149 | peerDependencies: 2150 | vue: ^3.4.37 2151 | dependencies: 2152 | vue: 3.5.12(typescript@5.2.2) 2153 | dev: true 2154 | 2155 | /vue@3.5.12(typescript@5.2.2): 2156 | resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==} 2157 | peerDependencies: 2158 | typescript: '*' 2159 | peerDependenciesMeta: 2160 | typescript: 2161 | optional: true 2162 | dependencies: 2163 | '@vue/compiler-dom': 3.5.12 2164 | '@vue/compiler-sfc': 3.5.12 2165 | '@vue/runtime-dom': 3.5.12 2166 | '@vue/server-renderer': 3.5.12(vue@3.5.12) 2167 | '@vue/shared': 3.5.12 2168 | typescript: 5.2.2 2169 | dev: true 2170 | 2171 | /xsta@2.1.0(@types/react@18.2.15)(react@18.2.0): 2172 | resolution: {integrity: sha512-gSJ1M+ApVIDgykEIT3AzseV7e1T7bAG9f/fNLs0sq6sRY8joVu+py7oE3Cg9NFbCTTqVK0Wo6JIiRNamSTXx3w==} 2173 | peerDependencies: 2174 | '@types/react': '>=16.8' 2175 | react: '>=16.8' 2176 | peerDependenciesMeta: 2177 | '@types/react': 2178 | optional: true 2179 | react: 2180 | optional: true 2181 | dependencies: 2182 | '@types/react': 18.2.15 2183 | external-shallow-equal: 1.0.0 2184 | react: 18.2.0 2185 | dev: false 2186 | 2187 | /yallist@3.1.1: 2188 | resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 2189 | dev: true 2190 | -------------------------------------------------------------------------------- /scripts/build-server.sh: -------------------------------------------------------------------------------- 1 | python -m nuitka --standalone --assume-yes-for-downloads \ 2 | --include-data-files="src-python/models/*.onnx=models/" \ 3 | --output-dir=out src-python/server.py 4 | 5 | cd out/server.dist && zip -r ../server.zip . 6 | 7 | echo "✅ Done" -------------------------------------------------------------------------------- /scripts/dist.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs/promises"; 4 | import { existsSync, mkdirSync } from "fs"; 5 | import path from "path"; 6 | 7 | const getFiles = async (dir) => { 8 | try { 9 | const files = await fs.readdir(dir); 10 | return files; 11 | } catch (err) { 12 | console.error(`Error reading directory ${dir}:`, err); 13 | return []; 14 | } 15 | }; 16 | 17 | const copyFile = async (from, to) => { 18 | if (!existsSync(from)) { 19 | console.error(`Source file does not exist: ${from}`); 20 | return false; 21 | } 22 | 23 | const dirname = path.dirname(to); 24 | if (!existsSync(dirname)) { 25 | mkdirSync(dirname, { recursive: true }); 26 | } 27 | 28 | await fs.copyFile(from, to).catch(() => null); 29 | }; 30 | 31 | async function main() { 32 | const args = process.argv.slice(2); 33 | const [target, appName] = args; 34 | const bundleDir = path.resolve(`src-tauri/target/${target}/release/bundle`); 35 | let outputs = {}; 36 | switch (process.platform) { 37 | case "darwin": 38 | outputs = { 39 | dmg: [".dmg"], 40 | }; 41 | break; 42 | case "win32": 43 | outputs = { 44 | nsis: [".exe"], 45 | }; 46 | } 47 | for (const dir in outputs) { 48 | const files = await getFiles(path.join(bundleDir, dir)); 49 | for (const filename of files) { 50 | const suffix = outputs[dir].find((e) => filename.endsWith(e)); 51 | if (suffix) { 52 | await copyFile( 53 | path.join(bundleDir, dir, filename), 54 | path.join("dist", appName + suffix) 55 | ); 56 | console.log(`✅ ${appName + suffix}`); 57 | } 58 | } 59 | } 60 | } 61 | 62 | main(); 63 | -------------------------------------------------------------------------------- /src-python/magic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-python/magic/__init__.py -------------------------------------------------------------------------------- /src-python/magic/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from async_tasks import AsyncTask 4 | from bottle import Bottle, request, response 5 | 6 | from .face import load_models, swap_face 7 | 8 | app = Bottle() 9 | 10 | # https://github.com/bottlepy/bottle/issues/881#issuecomment-244024649 11 | app.plugins[0].json_dumps = lambda *args, **kwargs: json.dumps( 12 | *args, ensure_ascii=False, **kwargs 13 | ).encode("utf8") 14 | 15 | 16 | # Enable CORS 17 | @app.hook("after_request") 18 | def enable_cors(): 19 | response.set_header("Access-Control-Allow-Origin", "*") 20 | response.set_header("Access-Control-Allow-Methods", "*") 21 | response.set_header("Access-Control-Allow-Headers", "*") 22 | 23 | 24 | @app.route("", method=["GET", "OPTIONS"]) 25 | def handle_options(path): 26 | response.status = 200 27 | return "MagicMirror ✨" 28 | 29 | 30 | @app.get("/status") 31 | def status(): 32 | return {"status": "running"} 33 | 34 | 35 | @app.post("/prepare") 36 | def prepare(): 37 | return {"success": load_models()} 38 | 39 | 40 | @app.post("/task") 41 | def create_task(): 42 | try: 43 | task_id = request.json["id"] 44 | input_image = request.json["inputImage"] 45 | target_face = request.json["targetFace"] 46 | assert all([task_id, input_image, target_face]) 47 | res, _ = AsyncTask.run( 48 | lambda: swap_face(input_image, target_face), task_id=task_id 49 | ) 50 | return {"result": res} 51 | except BaseException: 52 | response.status = 400 53 | return {"error": "Something went wrong!"} 54 | 55 | 56 | @app.delete("/task/") 57 | def cancel_task(task_id): 58 | AsyncTask.cancel(task_id) 59 | return {"success": True} 60 | -------------------------------------------------------------------------------- /src-python/magic/face.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import lru_cache 3 | 4 | import cv2 5 | import numpy as np 6 | from tinyface import TinyFace 7 | 8 | _tf = TinyFace() 9 | 10 | 11 | def load_models(): 12 | try: 13 | _tf.config.face_detector_model = _get_model_path("scrfd_2.5g.onnx") 14 | _tf.config.face_embedder_model = _get_model_path("arcface_w600k_r50.onnx") 15 | _tf.config.face_swapper_model = _get_model_path("inswapper_128_fp16.onnx") 16 | _tf.config.face_enhancer_model = _get_model_path("gfpgan_1.4.onnx") 17 | _tf.prepare() 18 | return True 19 | except BaseException as _: 20 | return False 21 | 22 | 23 | @lru_cache(maxsize=12) 24 | def swap_face(input_path, face_path): 25 | try: 26 | save_path = _get_output_file_path(input_path) 27 | output_img = _swap_face(input_path, face_path) 28 | _write_image(save_path, output_img) 29 | return save_path 30 | except BaseException as _: 31 | return None 32 | 33 | 34 | @lru_cache(maxsize=12) 35 | def _swap_face(input_path, face_path): 36 | return _tf.swap_face( 37 | vision_frame=_read_image(input_path), 38 | reference_face=_get_one_face(input_path), 39 | destination_face=_get_one_face(face_path), 40 | ) 41 | 42 | 43 | @lru_cache(maxsize=12) 44 | def _get_one_face(face_path: str): 45 | face_img = _read_image(face_path) 46 | return _tf.get_one_face(face_img) 47 | 48 | 49 | @lru_cache(maxsize=12) 50 | def _read_image(img_path: str): 51 | return cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), -1) 52 | 53 | 54 | def _write_image(img_path: str, img): 55 | suffix = os.path.splitext(img_path)[-1] 56 | cv2.imencode(suffix, img)[1].tofile(img_path) 57 | 58 | 59 | def _get_output_file_path(file_name): 60 | base_name, ext = os.path.splitext(file_name) 61 | return base_name + "_output" + ext 62 | 63 | 64 | def _get_model_path(file_name: str): 65 | return os.path.abspath( 66 | os.path.join(os.path.dirname(__file__), os.pardir, "models", file_name) 67 | ) 68 | -------------------------------------------------------------------------------- /src-python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "magic" 3 | version = "2.0.0" 4 | description = "Instant AI Face Swap, Hairstyles & Outfits — One click to a brand new you!" 5 | authors = ["Del Wang "] 6 | 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | tinyface = "^1.0.1" 11 | bottle = "^0.13.2" 12 | 13 | [tool.poetry.group.dev.dependencies] 14 | black = "^24.10.0" 15 | mypy = "^1.13.0" 16 | pylint = "^3.3.1" 17 | nuitka = "^2.5.9" 18 | 19 | 20 | [tool.mypy] 21 | ignore_missing_imports = true 22 | 23 | [tool.pylint.main] 24 | ignored-modules = ["cv2"] 25 | disable = [ 26 | "R0903", 27 | "R0913", 28 | "R0914", 29 | "R0915", 30 | "R0912", 31 | "C0103", 32 | "C0301", 33 | "C0116", 34 | "C0114", 35 | "C0115", 36 | "W0707", 37 | "W0718", 38 | "W0614", 39 | "W0719", 40 | "W0602", 41 | "W0102", 42 | ] 43 | 44 | [build-system] 45 | requires = ["poetry-core"] 46 | build-backend = "poetry.core.masonry.api" 47 | -------------------------------------------------------------------------------- /src-python/requirements.txt: -------------------------------------------------------------------------------- 1 | tinyface >= 1.0.1 2 | bottle >= 0.13.2 3 | nuitka >= 2.5.9 -------------------------------------------------------------------------------- /src-python/server.py: -------------------------------------------------------------------------------- 1 | from magic.app import app 2 | 3 | if __name__ == "__main__": 4 | app.run(host="0.0.0.0", port=8023) 5 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "magic" 3 | version = "1.0.0" 4 | description = "Instant AI Face Swap, Hairstyles & Outfits — One click to a brand new you!" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | # The `_lib` suffix may seem redundant but it is necessary 12 | # to make the lib name unique and wouldn't conflict with the bin name. 13 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 14 | name = "magic_lib" 15 | crate-type = ["staticlib", "cdylib", "rlib"] 16 | 17 | [build-dependencies] 18 | tauri-build = { version = "2", features = [] } 19 | 20 | [dependencies] 21 | tauri = { version = "2", features = [ 22 | "macos-private-api", 23 | "protocol-asset", 24 | "image-png", 25 | "devtools", 26 | ] } 27 | tauri-plugin-process = "2" 28 | tauri-plugin-shell = "2" 29 | tauri-plugin-os = "2.0.0" 30 | serde = { version = "1", features = ["derive"] } 31 | serde_json = "1" 32 | reqwest = { version = "0.12.9", features = ["stream"] } 33 | zip = "2.2.0" 34 | futures-util = "0.3" 35 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:default", 8 | "core:window:allow-start-dragging", 9 | "core:window:allow-close", 10 | "core:window:allow-show", 11 | "core:app:allow-default-window-icon", 12 | "core:menu:allow-set-as-window-menu", 13 | "process:default", 14 | "process:allow-exit", 15 | "shell:allow-open", 16 | "shell:allow-spawn", 17 | "shell:default", 18 | { 19 | "identifier": "shell:allow-execute", 20 | "allow": [ 21 | { 22 | "name": "chmod", 23 | "cmd": "chmod", 24 | "args": true 25 | } 26 | ] 27 | }, 28 | { 29 | "identifier": "shell:allow-spawn", 30 | "allow": [ 31 | { 32 | "name": "server-macos", 33 | "cmd": "$HOME/MagicMirror/server.bin", 34 | "args": true 35 | }, 36 | { 37 | "name": "server-windows", 38 | "cmd": "$HOME/MagicMirror/server.exe", 39 | "args": true 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/images/dmg-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/images/dmg-background.jpg -------------------------------------------------------------------------------- /src-tauri/images/nsis_header.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/images/nsis_header.bmp -------------------------------------------------------------------------------- /src-tauri/images/nsis_sidebar.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src-tauri/images/nsis_sidebar.bmp -------------------------------------------------------------------------------- /src-tauri/src/commands.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use tauri::AppHandle; 3 | 4 | use crate::utils::{download_file, unzip_file}; 5 | 6 | #[tauri::command] 7 | pub fn file_exists(path: String) -> bool { 8 | Path::new(&path).exists() 9 | } 10 | 11 | #[tauri::command] 12 | pub async fn download_and_unzip( 13 | app: AppHandle, 14 | url: String, 15 | target_dir: String, 16 | ) -> Result<(), String> { 17 | let temp_dir = std::env::temp_dir().to_string_lossy().to_string(); 18 | 19 | let temp_path = download_file(&app, &url, &temp_dir).await?; 20 | 21 | unzip_file(&app, &temp_path, &target_dir).await?; 22 | 23 | if let Err(e) = std::fs::remove_file(&temp_path) { 24 | return Err(format!("Failed to remove temp file: {}", e)); 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod commands; 2 | mod utils; 3 | 4 | use commands::{download_and_unzip, file_exists}; 5 | 6 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 7 | pub fn run() { 8 | tauri::Builder::default() 9 | .plugin(tauri_plugin_process::init()) 10 | .plugin(tauri_plugin_shell::init()) 11 | .plugin(tauri_plugin_os::init()) 12 | .invoke_handler(tauri::generate_handler![file_exists, download_and_unzip]) 13 | .run(tauri::generate_context!()) 14 | .expect("error while running tauri application"); 15 | } 16 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | magic_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/src/utils.rs: -------------------------------------------------------------------------------- 1 | use futures_util::StreamExt; 2 | use reqwest::header::CONTENT_LENGTH; 3 | use reqwest::Client; 4 | use std::fs::{self, create_dir_all, File}; 5 | use std::io::{copy, Write}; 6 | use std::path::Path; 7 | use std::time::{SystemTime, UNIX_EPOCH}; 8 | use tauri::AppHandle; 9 | use tauri::Emitter; 10 | use zip::read::ZipArchive; 11 | 12 | pub fn timestamp() -> u128 { 13 | let start = SystemTime::now(); 14 | let duration = start 15 | .duration_since(UNIX_EPOCH) 16 | .expect("Time went backwards"); 17 | duration.as_millis() 18 | } 19 | 20 | pub async fn content_length(client: Client, url: &String) -> Result { 21 | let response = match client.get(url).header("Range", "bytes=0-").send().await { 22 | Ok(res) => res, 23 | Err(e) => return Err(e), 24 | }; 25 | 26 | if let Some(content_length) = response.headers().get(CONTENT_LENGTH) { 27 | if let Some(value) = content_length.to_str().ok() { 28 | if let Ok(size) = value.parse::() { 29 | return Ok(size); 30 | } 31 | } 32 | } 33 | 34 | Ok(0) 35 | } 36 | 37 | pub async fn download_file( 38 | app: &AppHandle, 39 | url: &String, 40 | temp_dir: &String, 41 | ) -> Result { 42 | let temp_path = Path::new(temp_dir).join(format!("{}.zip", timestamp())); 43 | 44 | let client = Client::new(); 45 | let response = match client.get(url).send().await { 46 | Ok(res) => res, 47 | Err(e) => { 48 | return Err(format!("Failed to download file: {}", e)); 49 | } 50 | }; 51 | 52 | let total_size = content_length(client, url).await.unwrap_or(0); 53 | let mut downloaded = 0u64; 54 | let mut file = match File::create(&temp_path) { 55 | Ok(f) => f, 56 | Err(e) => { 57 | return Err(format!("Failed to create temp file: {}", e)); 58 | } 59 | }; 60 | 61 | let mut stream = response.bytes_stream(); 62 | 63 | while let Some(chunk) = stream.next().await { 64 | let chunk = match chunk { 65 | Ok(c) => c, 66 | Err(e) => { 67 | return Err(format!("Failed to read chunk: {}", e)); 68 | } 69 | }; 70 | if let Err(e) = file.write_all(&chunk) { 71 | return Err(format!("Failed to write chunk to file: {}", e)); 72 | } 73 | downloaded = std::cmp::min(downloaded + (chunk.len() as u64), total_size); 74 | if total_size > 0 { 75 | let progress = downloaded as f64 / total_size as f64 * 100.0; 76 | app.emit("download-progress", progress).unwrap_or_default(); 77 | } 78 | } 79 | 80 | Ok(temp_path.to_string_lossy().to_string()) 81 | } 82 | 83 | pub async fn unzip_file( 84 | app: &AppHandle, 85 | file_path: &String, 86 | target_dir: &String, 87 | ) -> Result<(), String> { 88 | let file = match File::open(file_path) { 89 | Ok(f) => f, 90 | Err(e) => { 91 | return Err(format!("Failed to open temp file: {}", e)); 92 | } 93 | }; 94 | 95 | let target_path = Path::new(target_dir); 96 | if target_path.exists() { 97 | if let Err(e) = fs::remove_dir_all(target_path) { 98 | return Err(format!("Failed to remove target directory: {}", e)); 99 | } 100 | } 101 | if let Err(e) = create_dir_all(target_path) { 102 | return Err(format!("Failed to create target directory: {}", e)); 103 | } 104 | 105 | let mut archive = match ZipArchive::new(file) { 106 | Ok(a) => a, 107 | Err(e) => { 108 | return Err(format!("Failed to read zip archive: {}", e)); 109 | } 110 | }; 111 | 112 | let total_files = archive.len(); 113 | for i in 0..total_files { 114 | let mut file = match archive.by_index(i) { 115 | Ok(f) => f, 116 | Err(e) => { 117 | return Err(format!("Failed to read file from zip: {}", e)); 118 | } 119 | }; 120 | 121 | let outpath = match file.enclosed_name() { 122 | Some(path) => target_path.join(path), 123 | None => continue, 124 | }; 125 | 126 | if file.name().ends_with('/') { 127 | if let Err(e) = create_dir_all(&outpath) { 128 | return Err(format!("Failed to create directory: {}", e)); 129 | } 130 | } else { 131 | if let Some(p) = outpath.parent() { 132 | if !p.exists() { 133 | if let Err(e) = create_dir_all(p) { 134 | return Err(format!("Failed to create parent directory: {}", e)); 135 | } 136 | } 137 | } 138 | 139 | let mut outfile = match File::create(&outpath) { 140 | Ok(f) => f, 141 | Err(e) => { 142 | return Err(format!("Failed to create output file: {}", e)); 143 | } 144 | }; 145 | 146 | if let Err(e) = copy(&mut file, &mut outfile) { 147 | return Err(format!("Failed to copy file content: {}", e)); 148 | } 149 | } 150 | 151 | let progress = i as f64 / total_files as f64 * 100.0; 152 | app.emit("unzip-progress", progress).unwrap_or_default(); 153 | } 154 | 155 | Ok(()) 156 | } 157 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "MagicMirror", 4 | "mainBinaryName": "MagicMirror", 5 | "version": "1.0.0", 6 | "identifier": "com.del.magic", 7 | "build": { 8 | "beforeDevCommand": "pnpm dev", 9 | "devUrl": "http://localhost:1420", 10 | "beforeBuildCommand": "pnpm build", 11 | "frontendDist": "../dist" 12 | }, 13 | "app": { 14 | "macOSPrivateApi": true, 15 | "windows": [ 16 | { 17 | "title": "MagicMirror", 18 | "width": 650, 19 | "height": 650, 20 | "resizable": false, 21 | "fullscreen": false, 22 | "shadow": false, 23 | "center": true, 24 | "decorations": false, 25 | "transparent": true, 26 | "hiddenTitle": true, 27 | "titleBarStyle": "Overlay" 28 | } 29 | ], 30 | "security": { 31 | "csp": { 32 | "default-src": "'self' asset:", 33 | "img-src": "'self' asset: http://asset.localhost blob: data:", 34 | "connect-src": "tauri: http://tauri.localhost ipc: http://ipc.localhost http://localhost:8023" 35 | }, 36 | "assetProtocol": { 37 | "enable": true, 38 | "scope": { 39 | "allow": ["**"] 40 | } 41 | } 42 | } 43 | }, 44 | "bundle": { 45 | "active": true, 46 | "targets": "all", 47 | "copyright": "Copyright © 2024 Del Wang", 48 | "shortDescription": "AI Face Swap", 49 | "longDescription": "Instant AI Face Swap, Hairstyles & Outfits — One click to a brand new you!", 50 | "icon": [ 51 | "icons/32x32.png", 52 | "icons/128x128.png", 53 | "icons/128x128@2x.png", 54 | "icons/icon.icns", 55 | "icons/icon.ico" 56 | ], 57 | "macOS": { 58 | "entitlements": null, 59 | "frameworks": [], 60 | "providerShortName": null, 61 | "signingIdentity": null, 62 | "dmg": { 63 | "background": "images/dmg-background.jpg", 64 | "appPosition": { 65 | "x": 180, 66 | "y": 170 67 | }, 68 | "applicationFolderPosition": { 69 | "x": 480, 70 | "y": 170 71 | }, 72 | "windowSize": { 73 | "height": 400, 74 | "width": 660 75 | } 76 | } 77 | }, 78 | "windows": { 79 | "webviewInstallMode": { 80 | "type": "embedBootstrapper" 81 | }, 82 | "nsis": { 83 | "headerImage": "images/nsis_header.bmp", 84 | "sidebarImage": "images/nsis_sidebar.bmp", 85 | "installerIcon": "icons/icon.ico", 86 | "languages": ["English", "SimpChinese"] 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { LaunchPage } from "@/pages/Launch"; 2 | import { MirrorPage } from "@/pages/Mirror"; 3 | import { Route, BrowserRouter as Router, Routes } from "react-router-dom"; 4 | 5 | function App() { 6 | return ( 7 | 8 | 9 | } /> 10 | } /> 11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/assets/images/magic-mirror.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/images/menu.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src/assets/images/menu.webp -------------------------------------------------------------------------------- /src/assets/images/mirror-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/images/mirror-input.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src/assets/images/mirror-input.webp -------------------------------------------------------------------------------- /src/assets/images/mirror-me.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idootop/MagicMirror/427e3fe0a7a57ed0fa3860ba6ac75fe715c90e1a/src/assets/images/mirror-me.webp -------------------------------------------------------------------------------- /src/assets/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Downloading resources, please wait": "Downloading resources, please wait ({{progress}}%)...", 3 | "Extracting resources, please wait": "Extracting resources, please wait ({{progress}}%)...", 4 | "*If the download is stuck or fails, please download and initialize manually. ": "*If the download is stuck or fails, please download and initialize manually. ", 5 | "View tutorial": "View tutorial", 6 | "Starting... First load may take longer, please wait.": "Starting... First load may take longer, please wait.", 7 | "Face swapping... This may take a few seconds, please wait.": "Face swapping... This may take a few seconds, please wait.", 8 | "First, drag your front-facing photo into the mirror.": "First, drag your front-facing photo into the mirror.", 9 | "Then, drag the photo you want to swap faces with into the mirror.": "Now, drag the photo you want to swap faces with into the mirror.", 10 | "Face swap successful! Image saved locally.": "Face swap successful! Image saved locally.", 11 | "Face swap failed. Try a different image.": "Face swap failed. Try a different image.", 12 | "Quit": "Quit", 13 | "Language": "Language", 14 | "Switch": "Switch", 15 | "About": "About", 16 | "downloadURL": "https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_{{type}}_{{arch}}.zip", 17 | "downloadTutorial": "https://thread-sphynx-f26.notion.site/MagicMirror-User-Guide-147aea89ebf680c189cdd76f5668261a", 18 | "aboutLink": "https://github.com/idootop/MagicMirror" 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/locales/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "Downloading resources, please wait": "正在下载资源文件,请耐心等待({{progress}}%)...", 3 | "Extracting resources, please wait": "正在解压资源文件,请耐心等待({{progress}}%)...", 4 | "*If the download is stuck or fails, please download and initialize manually. ": "*如果下载进度卡住或失败,请手动下载并初始化。", 5 | "View tutorial": "查看教程", 6 | "Starting... First load may take longer, please wait.": "启动中... 首次加载较慢,请耐心等待~", 7 | "Face swapping... This may take a few seconds, please wait.": "换脸中... 这可能需要一些时间,请耐心等待", 8 | "First, drag your front-facing photo into the mirror.": "首先,把你的正脸照「拖拽」到镜子里", 9 | "Then, drag the photo you want to swap faces with into the mirror.": "然后,把你想要换脸的照片「拖拽」到镜子里", 10 | "Face swap successful! Image saved locally.": "换脸成功,图片已保存至本地", 11 | "Face swap failed. Try a different image.": "换脸失败,换张图片再试试", 12 | "Quit": "退出", 13 | "Language": "切换语言", 14 | "Switch": "翻转镜像", 15 | "About": "关于", 16 | "downloadURL": "https://github.com/idootop/MagicMirror/releases/download/server-v1.0.0/server_{{type}}_{{arch}}.zip", 17 | "downloadTutorial": "https://ccnpyvkdfkte.feishu.cn/wiki/LAn6w4NqwiqeKmkXBrLc4MEBnDh", 18 | "aboutLink": "https://github.com/idootop/MagicMirror" 19 | } 20 | -------------------------------------------------------------------------------- /src/components/LanguageSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "react-i18next"; 2 | 3 | export const LanguageSwitcher = () => { 4 | const { i18n } = useTranslation(); 5 | 6 | const changeLanguage = (lang: string) => { 7 | i18n.changeLanguage(lang); 8 | }; 9 | 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | export function ProgressBar({ progress }: { progress: number }) { 2 | return ( 3 |
11 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useDownload.ts: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import { useCallback, useEffect } from "react"; 3 | import { createXStaManager } from "xsta"; 4 | import { Server } from "../services/server"; 5 | 6 | type DownloadStatus = 7 | | "idle" 8 | | "downloading" 9 | | "unzipping" 10 | | "success" 11 | | "failed"; 12 | 13 | const kDownloadStates = createXStaManager({ 14 | key: "DownloadStates", 15 | initialState: { 16 | status: "idle" as DownloadStatus, 17 | progress: 0, 18 | error: null as any, 19 | }, 20 | }); 21 | 22 | export function useDownload() { 23 | const [states, setStates] = kDownloadStates.useState(); 24 | 25 | useEffect(() => { 26 | const setup = async () => { 27 | const isDownloaded = await Server.isDownloaded(); 28 | if (isDownloaded) { 29 | setStates((states) => { 30 | states.progress = 100; 31 | states.status = "success"; 32 | }); 33 | return; 34 | } 35 | 36 | const download = await listen("download-progress", (event) => { 37 | const progress = event.payload; 38 | const states = kDownloadStates.getState(); 39 | if (states.status === "downloading") { 40 | setStates((states) => { 41 | states.progress = progress as number; 42 | }); 43 | } 44 | }); 45 | 46 | const unzip = await listen("unzip-progress", (event) => { 47 | const progress = event.payload; 48 | const states = kDownloadStates.getState(); 49 | if (states.status === "downloading") { 50 | setStates((states) => { 51 | states.status = "unzipping"; 52 | states.progress = progress as number; 53 | }); 54 | } else if (states.status === "unzipping") { 55 | setStates((states) => { 56 | states.progress = progress as number; 57 | }); 58 | } 59 | }); 60 | 61 | return { download, unzip }; 62 | }; 63 | 64 | let cleanup: (() => void) | undefined; 65 | 66 | setup().then((unlisten) => { 67 | cleanup = () => { 68 | unlisten?.download(); 69 | unlisten?.unzip(); 70 | }; 71 | }); 72 | 73 | return () => { 74 | cleanup?.(); 75 | }; 76 | }, []); 77 | 78 | const download = useCallback(async () => { 79 | const isDownloaded = await Server.isDownloaded(); 80 | if (isDownloaded) { 81 | return true; 82 | } 83 | try { 84 | const states = kDownloadStates.getState(); 85 | if (["downloading", "unzipping"].includes(states.status)) { 86 | return false; 87 | } 88 | if (states.status === "success") { 89 | return true; 90 | } 91 | setStates((states) => { 92 | states.progress = 0; 93 | states.status = "downloading"; 94 | }); 95 | await Server.download(); 96 | setStates((states) => { 97 | states.progress = 100; 98 | states.status = "success"; 99 | }); 100 | return true; 101 | } catch (error) { 102 | setStates((states) => { 103 | states.error = error; 104 | states.progress = 0; 105 | states.status = "failed"; 106 | }); 107 | return false; 108 | } 109 | }, []); 110 | 111 | return { download, ...states }; 112 | } 113 | -------------------------------------------------------------------------------- /src/hooks/useDragDrop.ts: -------------------------------------------------------------------------------- 1 | import { DragDropEvent, getCurrentWebview } from "@tauri-apps/api/webview"; 2 | import { getCurrentWindow } from "@tauri-apps/api/window"; 3 | import { useCallback, useEffect, useRef, useState } from "react"; 4 | 5 | function debounce(func: any, delay = 100) { 6 | let timeoutId: any; 7 | return function (...args: any) { 8 | clearTimeout(timeoutId); 9 | timeoutId = setTimeout(() => { 10 | //@ts-ignore 11 | func.apply(this, args); 12 | }, delay); 13 | }; 14 | } 15 | 16 | export function useDragDrop(onDrop: (paths: string[]) => void) { 17 | const ref = useRef(null); 18 | const [isOverTarget, setIsOverTarget] = useState(false); 19 | 20 | const onDropped = useCallback( 21 | debounce((paths: string[]) => { 22 | onDrop(paths); 23 | }), 24 | [onDrop] 25 | ); 26 | 27 | useEffect(() => { 28 | const checkIsInside = async (event: DragDropEvent) => { 29 | const targetRect = ref.current?.getBoundingClientRect(); 30 | if (!targetRect || event.type === "leave") { 31 | return false; 32 | } 33 | const factor = await getCurrentWindow().scaleFactor(); 34 | const position = event.position.toLogical(factor); 35 | const isInside = 36 | position.x >= targetRect.left && 37 | position.x <= targetRect.right && 38 | position.y >= targetRect.top && 39 | position.y <= targetRect.bottom; 40 | return isInside; 41 | }; 42 | 43 | const setupListener = async () => { 44 | const unlisten = await getCurrentWebview().onDragDropEvent( 45 | async (event) => { 46 | const isInside = await checkIsInside(event.payload); 47 | if (event.payload.type === "over") { 48 | setIsOverTarget(isInside); 49 | return; 50 | } 51 | if (event.payload.type === "drop" && isInside) { 52 | onDropped(event.payload.paths); 53 | } 54 | setIsOverTarget(false); 55 | } 56 | ); 57 | 58 | return unlisten; 59 | }; 60 | 61 | let cleanup: (() => void) | undefined; 62 | 63 | setupListener().then((unlisten) => { 64 | cleanup = unlisten; 65 | }); 66 | 67 | return () => { 68 | cleanup?.(); 69 | }; 70 | }, []); 71 | 72 | return { isOverTarget, ref }; 73 | } 74 | -------------------------------------------------------------------------------- /src/hooks/useOS.ts: -------------------------------------------------------------------------------- 1 | import { arch, type } from "@tauri-apps/plugin-os"; 2 | 3 | export function useOS() { 4 | return { 5 | os: type(), 6 | arch: arch(), 7 | isMacOS: type() === "macos", 8 | isWindows: type() === "windows", 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useServer.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from "react"; 2 | import { useXState, XSta } from "xsta"; 3 | import { Server, ServerStatus } from "../services/server"; 4 | import { sleep } from "../services/utils"; 5 | 6 | const kStatusKey = "serverStatus"; 7 | 8 | export function useServer() { 9 | const [status, setStatus] = useXState(kStatusKey, "idle"); 10 | 11 | const launch = async () => { 12 | if (status != "idle") { 13 | return true; 14 | } 15 | 16 | setStatus("launching"); 17 | const launched = await Server.launch(() => { 18 | setStatus("idle"); 19 | }); 20 | if (!launched) { 21 | setStatus("idle"); 22 | return false; 23 | } 24 | 25 | while (XSta.get(kStatusKey) === "launching") { 26 | const status = await Server.status(); 27 | if (status === "running") { 28 | break; 29 | } 30 | await sleep(200); 31 | } 32 | 33 | const prepared = await Server.prepare(); 34 | if (prepared) { 35 | setStatus("running"); 36 | } 37 | return prepared; 38 | }; 39 | 40 | const kill = useCallback(() => { 41 | setStatus("idle"); 42 | Server.kill(); 43 | }, []); 44 | 45 | useEffect(() => { 46 | return () => kill(); 47 | }, []); 48 | 49 | return { status, launch, kill }; 50 | } 51 | -------------------------------------------------------------------------------- /src/hooks/useSwapFace.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from "react"; 2 | import { useXState } from "xsta"; 3 | import { Server } from "../services/server"; 4 | 5 | const kSwapFaceRefs: { 6 | id: number; 7 | cancel?: VoidFunction; 8 | } = { 9 | id: 1, 10 | cancel: undefined, 11 | }; 12 | 13 | export function useSwapFace() { 14 | const [isSwapping, setIsSwapping] = useXState("isSwapping", false); 15 | const [output, setOutput] = useXState("swapOutput", null); 16 | 17 | const swapFace = useCallback( 18 | async (inputImage: string, targetFace: string) => { 19 | await kSwapFaceRefs.cancel?.(); 20 | setIsSwapping(true); 21 | const taskId = (kSwapFaceRefs.id++).toString(); 22 | kSwapFaceRefs.cancel = async () => { 23 | const success = await Server.cancelTask(taskId); 24 | if (success) { 25 | setIsSwapping(false); 26 | } 27 | }; 28 | const result = await Server.createTask({ 29 | id: taskId, 30 | inputImage, 31 | targetFace, 32 | }); 33 | kSwapFaceRefs.cancel = undefined; 34 | setOutput(result); 35 | setIsSwapping(false); 36 | return result; 37 | }, 38 | [] 39 | ); 40 | 41 | useEffect(() => { 42 | return () => { 43 | kSwapFaceRefs.cancel?.(); 44 | }; 45 | }, []); 46 | 47 | return { 48 | isSwapping, 49 | output, 50 | swapFace, 51 | cancel: () => kSwapFaceRefs.cancel?.(), 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | import "@/services/i18n"; 6 | import "@/styles/global.css"; 7 | import "virtual:uno.css"; 8 | 9 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/pages/Launch.tsx: -------------------------------------------------------------------------------- 1 | import banner from "@/assets/images/magic-mirror.svg"; 2 | import { ProgressBar } from "@/components/ProgressBar"; 3 | import { useDownload } from "@/hooks/useDownload"; 4 | import { useServer } from "@/hooks/useServer"; 5 | import { open } from "@tauri-apps/plugin-shell"; 6 | import { useEffect, useRef } from "react"; 7 | import { useTranslation } from "react-i18next"; 8 | import { useNavigate } from "react-router-dom"; 9 | 10 | export function LaunchPage() { 11 | const { t } = useTranslation(); 12 | const { progress, download, status: downloadStatus } = useDownload(); 13 | const { launch, status: launchingStatus } = useServer(); 14 | 15 | const launchingStatusRef = useRef(launchingStatus); 16 | launchingStatusRef.current = launchingStatus; 17 | 18 | const navigate = useNavigate(); 19 | 20 | useEffect(() => { 21 | download(); 22 | }, []); 23 | 24 | useEffect(() => { 25 | if (downloadStatus === "success") { 26 | launch(); 27 | Promise.all([ 28 | new Promise((resolve) => { 29 | setTimeout(resolve, 3000); 30 | }), 31 | new Promise((resolve) => { 32 | const checkInterval = setInterval(() => { 33 | if (launchingStatusRef.current === "running") { 34 | clearInterval(checkInterval); 35 | resolve(true); 36 | } 37 | }, 100); 38 | }), 39 | ]).then(() => { 40 | navigate("/mirror"); 41 | }); 42 | } 43 | }, [downloadStatus]); 44 | 45 | const launching = 46 | ["idle", "success"].includes(downloadStatus) || 47 | ["launching", "running"].includes(launchingStatus) ? ( 48 | <> 49 |

{t("Starting... First load may take longer, please wait.")}

50 | 51 | ) : null; 52 | 53 | const downloading = ["downloading", "unzipping", "failed"].includes( 54 | downloadStatus 55 | ) ? ( 56 | <> 57 |

58 | {t("Downloading resources, please wait", { 59 | progress: progress.toFixed(2), 60 | })} 61 |

62 | 63 |

64 | {t( 65 | "*If the download is stuck or fails, please download and initialize manually. " 66 | )} 67 | open(t("downloadTutorial"))} 70 | > 71 | {t("View tutorial")} 72 | 73 |

74 | 75 | ) : null; 76 | 77 | return ( 78 |
87 | 91 | {launching} 92 | {downloading} 93 |
94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/pages/Mirror.tsx: -------------------------------------------------------------------------------- 1 | import { useDragDrop } from "@/hooks/useDragDrop"; 2 | import { useSwapFace } from "@/hooks/useSwapFace"; 3 | import { convertFileSrc } from "@tauri-apps/api/core"; 4 | import { exit } from "@tauri-apps/plugin-process"; 5 | import { open } from "@tauri-apps/plugin-shell"; 6 | import { useEffect, useRef, useState } from "react"; 7 | import { useTranslation } from "react-i18next"; 8 | 9 | import "@/styles/mirror.css"; 10 | 11 | import iconMenu from "@/assets/images/menu.webp"; 12 | import background from "@/assets/images/mirror-bg.svg"; 13 | import mirrorInput from "@/assets/images/mirror-input.webp"; 14 | import mirrorMe from "@/assets/images/mirror-me.webp"; 15 | 16 | interface Asset { 17 | path: string; 18 | src: string; 19 | } 20 | 21 | const kMirrorStates: { 22 | isMe: boolean; 23 | me?: Asset; 24 | input?: Asset; 25 | result?: Asset; 26 | } = { isMe: true }; 27 | 28 | export function MirrorPage() { 29 | const [flag, setFlag] = useState(false); 30 | const rebuild = useRef(); 31 | rebuild.current = () => setFlag(!flag); 32 | 33 | const { i18n, t } = useTranslation(); 34 | 35 | const { isSwapping, swapFace } = useSwapFace(); 36 | const [success, setSuccess] = useState(true); 37 | 38 | useEffect(() => { 39 | setTimeout(() => { 40 | if (kMirrorStates.me && !kMirrorStates.input) { 41 | kMirrorStates.isMe = false; 42 | rebuild.current(); 43 | } 44 | if (kMirrorStates.me && isSwapping) { 45 | kMirrorStates.isMe = false; 46 | rebuild.current(); 47 | } 48 | }); 49 | }, [kMirrorStates.me, kMirrorStates.input, isSwapping]); 50 | 51 | const { ref } = useDragDrop(async (paths) => { 52 | const src = convertFileSrc(paths[0]); 53 | if (kMirrorStates.isMe) { 54 | kMirrorStates.me = { 55 | src, 56 | path: paths[0], 57 | }; 58 | rebuild.current(); 59 | } else { 60 | kMirrorStates.input = { 61 | src, 62 | path: paths[0], 63 | }; 64 | rebuild.current(); 65 | } 66 | 67 | if (kMirrorStates.me && kMirrorStates.input) { 68 | kMirrorStates.result = undefined; 69 | rebuild.current(); 70 | const result = await swapFace( 71 | kMirrorStates.input.path, 72 | kMirrorStates.me.path 73 | ); 74 | setSuccess(result != null); 75 | if (result) { 76 | kMirrorStates.result = { 77 | src: convertFileSrc(result), 78 | path: result, 79 | }; 80 | rebuild.current(); 81 | } 82 | } 83 | }); 84 | 85 | const isReady = kMirrorStates.me && kMirrorStates.input; 86 | const tips = kMirrorStates.isMe 87 | ? t("First, drag your front-facing photo into the mirror.") 88 | : !isReady 89 | ? t("Then, drag the photo you want to swap faces with into the mirror.") 90 | : isSwapping 91 | ? t("Face swapping... This may take a few seconds, please wait.") 92 | : success 93 | ? t("Face swap successful! Image saved locally.") 94 | : t("Face swap failed. Try a different image."); 95 | 96 | return ( 97 |
98 |
99 |
100 |

{tips}

101 |
102 |
103 |
104 | 109 |
110 |
111 |
{ 113 | i18n.changeLanguage(i18n.language === "en" ? "zh" : "en"); 114 | }} 115 | > 116 | {t("Language")} 117 |
118 | {kMirrorStates.me && ( 119 |
{ 121 | kMirrorStates.isMe = !kMirrorStates.isMe; 122 | rebuild.current(); 123 | }} 124 | > 125 | {t("Switch")} 126 |
127 | )} 128 |
open(t("aboutLink"))}>{t("About")}
129 |
exit(0)}>{t("Quit")}
130 |
131 |
132 |
133 |
134 | 145 |
151 |
152 |
153 | 164 |
165 |
166 |
167 |
168 |
169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/services/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | import LanguageDetector from "i18next-browser-languagedetector"; 4 | import LocalStorageBackend from "i18next-localstorage-backend"; 5 | 6 | import enTranslation from "@/assets/locales/en.json"; 7 | import zhTranslation from "@/assets/locales/zh.json"; 8 | 9 | i18n 10 | .use(LanguageDetector) 11 | .use(LocalStorageBackend) 12 | .use(initReactI18next) 13 | .init({ 14 | fallbackLng: "en", 15 | debug: true, 16 | resources: { 17 | en: { translation: enTranslation }, 18 | zh: { translation: zhTranslation }, 19 | }, 20 | detection: { 21 | order: ["localStorage", "navigator"], 22 | caches: ["localStorage"], 23 | }, 24 | }); 25 | 26 | export default i18n; 27 | -------------------------------------------------------------------------------- /src/services/server.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | import { homeDir, join } from "@tauri-apps/api/path"; 3 | import { arch, type } from "@tauri-apps/plugin-os"; 4 | import { Child, Command } from "@tauri-apps/plugin-shell"; 5 | import { t } from "i18next"; 6 | 7 | export type ServerStatus = "idle" | "launching" | "running"; 8 | 9 | export interface Task { 10 | id: string; 11 | inputImage: string; 12 | targetFace: string; 13 | } 14 | 15 | class _Server { 16 | _childProcess?: Child; 17 | _baseURL = "http://localhost:8023"; 18 | 19 | async rootDir() { 20 | return join(await homeDir(), "MagicMirror"); 21 | } 22 | 23 | async isDownloaded() { 24 | try { 25 | const binaryPath = await join( 26 | await this.rootDir(), 27 | type() === "windows" ? "server.exe" : "server.bin" 28 | ); 29 | const exists = await invoke("file_exists", { 30 | path: binaryPath, 31 | }); 32 | if (exists && type() === "macos") { 33 | const output = await Command.create("chmod", [ 34 | "755", 35 | binaryPath, 36 | ]).execute(); 37 | return output.code === 0; 38 | } 39 | return exists; 40 | } catch (error) { 41 | return false; 42 | } 43 | } 44 | 45 | async download() { 46 | if (await this.isDownloaded()) { 47 | return true; 48 | } 49 | await invoke("download_and_unzip", { 50 | url: t("downloadURL", { type: type(), arch: arch() }), 51 | targetDir: await this.rootDir(), 52 | }); 53 | if (!(await this.isDownloaded())) { 54 | throw Error("Unknown error"); 55 | } 56 | return true; 57 | } 58 | 59 | async launch(onStop?: VoidFunction): Promise { 60 | if (this._childProcess) { 61 | return true; 62 | } 63 | try { 64 | const command = Command.create(`server-${type()}`); 65 | command.addListener("close", () => onStop?.()); 66 | this._childProcess = await command.spawn(); 67 | return true; 68 | } catch { 69 | return false; 70 | } 71 | } 72 | 73 | async kill(): Promise { 74 | if (!this._childProcess) { 75 | return true; 76 | } 77 | try { 78 | await this._childProcess.kill(); 79 | return true; 80 | } catch { 81 | return false; 82 | } 83 | } 84 | 85 | async status(): Promise { 86 | try { 87 | const res = await fetch(`${this._baseURL}/status`, { 88 | method: "get", 89 | }); 90 | const data = await res.json(); 91 | return data.status || "idle"; 92 | } catch { 93 | return "idle"; 94 | } 95 | } 96 | 97 | async prepare(): Promise { 98 | try { 99 | const res = await fetch(`${this._baseURL}/prepare`, { 100 | method: "post", 101 | }); 102 | const data = await res.json(); 103 | return data.success || false; 104 | } catch { 105 | return false; 106 | } 107 | } 108 | 109 | async createTask(task: Task): Promise { 110 | try { 111 | const res = await fetch(`${this._baseURL}/task`, { 112 | method: "post", 113 | headers: { 114 | "Content-Type": "application/json;charset=UTF-8", 115 | }, 116 | body: JSON.stringify(task), 117 | }); 118 | const data = await res.json(); 119 | return data.result || null; 120 | } catch { 121 | return null; 122 | } 123 | } 124 | 125 | async cancelTask(taskId: string): Promise { 126 | try { 127 | const res = await fetch(`${this._baseURL}/task/${taskId}`, { 128 | method: "delete", 129 | }); 130 | const data = await res.json(); 131 | return data.success || false; 132 | } catch { 133 | return false; 134 | } 135 | } 136 | } 137 | 138 | export const Server = new _Server(); 139 | -------------------------------------------------------------------------------- /src/services/utils.ts: -------------------------------------------------------------------------------- 1 | export function timestamp() { 2 | return new Date().getTime(); 3 | } 4 | 5 | export async function sleep(time: number) { 6 | return new Promise((resolve) => setTimeout(resolve, time)); 7 | } 8 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @import url("./reset.css"); 2 | 3 | html, 4 | body, 5 | :root { 6 | margin: 0; 7 | padding: 0; 8 | width: 100dvw; 9 | height: 100dvh; 10 | overflow: hidden; 11 | 12 | font-size: 16px; 13 | line-height: 24px; 14 | font-weight: 400; 15 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, 16 | Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 17 | 18 | color: #0f0f0f; 19 | background-color: transparent; 20 | 21 | font-synthesis: none; 22 | text-rendering: optimizeLegibility; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | -webkit-text-size-adjust: 100%; 26 | } 27 | 28 | :root { 29 | --color-red: #e14f43; 30 | --color-blue: #4eacf8; 31 | --color-green: #0fce69; 32 | --color-yellow: #f7cc4f; 33 | } 34 | 35 | body { 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/mirror.css: -------------------------------------------------------------------------------- 1 | .dropdown-menu { 2 | display: none !important; 3 | position: absolute; 4 | top: 65px; 5 | left: 50%; 6 | transform: translateX(-50%); 7 | cursor: pointer; 8 | } 9 | 10 | .dropdown:hover .dropdown-menu { 11 | display: flex !important; 12 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3), 0 8px 20px rgba(0, 0, 0, 0.3); 13 | } 14 | 15 | .dropdown-menu div { 16 | width: 100%; 17 | padding: 4px 8px; 18 | text-align: center; 19 | word-break: keep-all; 20 | -webkit-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .dropdown-menu div:hover { 25 | background: #fff; 26 | color: #000; 27 | } 28 | 29 | .mirror-preview { 30 | position: relative; 31 | width: 100%; 32 | height: 100%; 33 | border-radius: 50%; 34 | overflow: hidden; 35 | } 36 | 37 | .preview-container { 38 | position: relative; 39 | width: 100%; 40 | height: 100%; 41 | overflow: hidden; 42 | } 43 | 44 | .preview-container::before { 45 | content: ""; 46 | position: absolute; 47 | top: 0; 48 | left: -100%; 49 | width: 50%; 50 | height: 100%; 51 | background: linear-gradient( 52 | 90deg, 53 | transparent, 54 | rgba(255, 255, 255, 0.2), 55 | transparent 56 | ); 57 | animation: shine 2s infinite; 58 | z-index: 1; 59 | } 60 | 61 | @keyframes shine { 62 | to { 63 | left: 100%; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/styles/reset.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | * { 8 | margin: 0; 9 | } 10 | 11 | body { 12 | text-rendering: optimizelegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | text-size-adjust: 100%; 16 | } 17 | 18 | img, 19 | picture, 20 | video, 21 | canvas, 22 | svg, 23 | iframe { 24 | display: block; 25 | max-width: 100%; 26 | } 27 | 28 | input, 29 | button, 30 | textarea, 31 | select { 32 | font: inherit; 33 | } 34 | 35 | p, 36 | h1, 37 | h2, 38 | h3, 39 | h4, 40 | h5, 41 | h6, 42 | a, 43 | blockquote { 44 | overflow-wrap: break-word; 45 | } 46 | 47 | #root, 48 | #__next { 49 | isolation: isolate; 50 | } 51 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["src"], 28 | "references": [{ "path": "./tsconfig.node.json" }] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetAttributify, presetUno } from "unocss"; 2 | 3 | export default defineConfig({ 4 | presets: [presetUno(), presetAttributify()], 5 | theme: { 6 | colors: { 7 | red: "var(--color-red)", 8 | blue: "var(--color-blue)", 9 | green: "var(--color-green)", 10 | yellow: "var(--color-yellow)", 11 | }, 12 | }, 13 | shortcuts: [ 14 | ["flex-c", "flex items-center"], 15 | ["flex-c-c", "flex items-center justify-center "], 16 | ["flex-c-sb", "flex items-center justify-between "], 17 | ["flex-col-c", "flex flex-col items-center"], 18 | ["flex-col-c-c", "flex flex-col items-center justify-center"], 19 | ["flex-col-c-sb", "flex flex-col items-center justify-between"], 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import UnoCSS from "unocss/vite"; 4 | import path from "path"; 5 | 6 | const host = process.env.TAURI_DEV_HOST; 7 | 8 | export default defineConfig(async () => ({ 9 | plugins: [react(), UnoCSS()], 10 | clearScreen: false, 11 | resolve: { 12 | alias: { 13 | "@": path.resolve(__dirname, "./src"), 14 | }, 15 | }, 16 | server: { 17 | port: 1420, 18 | strictPort: true, 19 | host: host || false, 20 | hmr: host ? { protocol: "ws", host, port: 1421 } : undefined, 21 | watch: { ignored: ["**/src-tauri/**"] }, 22 | }, 23 | })); 24 | --------------------------------------------------------------------------------