├── .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 | [](https://github.com/idootop/MagicMirror/releases) [](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml) [](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml)
6 |
7 | 
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 | 
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 | 
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 | 
22 |
23 | 这里我使用的是 [Tensor.ART](https://tusiart.com/):一个免费的在线 AI 生图网站。像最新的 Flux 和 SD 3.5 等模型都可以直接使用,而且你也可以在线训练自己的模型。
24 |
25 | 比如 MagicMirror 的 LOGO, 就是我从 Dribbble 上搜集了一些参考图,然后用 Flux 做底模训练的一个模型生成的,非常方便。
26 |
27 | 
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 | 
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 | 
77 |
78 | 由于许多病毒软件也喜欢使用 Nuitka 来编译他们的应用程序,从而混淆源代码隐藏自己的恶意行为特征。
79 |
80 | 所以同样使用 Nuitka 编译的 `server.exe` 文件,在启动时也容易被 Windows 安全应用误标记为病毒文件并删除。
81 |
82 | 
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 | 
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 | 
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 | 
38 |
39 | 
40 |
41 | 完成上面两步后,重启 MagicMirror 应该就能正常启动了。
42 |
43 | ## 启动 APP
44 |
45 | 下载完模型文件后,第一次启动应用可能比较缓慢,耐心等待即可。
46 |
47 | 
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 | [](https://github.com/idootop/MagicMirror/releases) [](https://github.com/idootop/MagicMirror/actions/workflows/build-app.yaml) [](https://github.com/idootop/MagicMirror/actions/workflows/build-server.yaml)
6 |
7 | 
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 | 
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 | 
6 |
7 | 然后把你想要换脸的照片,拖到另一面镜子里,等待换脸完毕即可。
8 |
9 | 
10 |
11 | > 苹果 M1 芯片换一张脸一般需要 3-5s 的时间,主要看你的电脑配置和图片大小
12 |
13 | 换脸成功后,会在原来的位置生成一张以 `_output` 结尾的新图片。
14 |
15 | 
16 |
17 | 你可以继续拖入新的照片换脸,或通过右上角的菜单翻转镜像,更换新的脸部图片。
18 |
19 | 
20 |
21 | ## 遇到问题?
22 |
23 | 大部分问题都能在「[常见问题](./faq.md)」里找到答案,如果你还有其他问题,请在此[提交反馈](https://github.com/idootop/MagicMirror/issues)。
24 |
--------------------------------------------------------------------------------
/docs/en/faq.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
35 |
36 | 
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 | 
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 | 
6 |
7 | Next, drag your desired photo into the other mirror and wait for processing to complete.
8 |
9 | 
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 | 
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 | 
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------