Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4fd26814cb | |||
| 1c090299b1 |
@@ -0,0 +1,79 @@
|
|||||||
|
name: Build Desktop App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Install dependencies (Ubuntu)
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
pip install uv
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
- name: Build Python backend with Nuitka
|
||||||
|
run: |
|
||||||
|
pip install nuitka
|
||||||
|
python build_nuitka.py
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
working-directory: ./dashboard
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build Tauri app
|
||||||
|
working-directory: ./dashboard
|
||||||
|
run: npm run tauri:build
|
||||||
|
|
||||||
|
- name: Upload artifacts (macOS)
|
||||||
|
if: matrix.platform == 'macos-latest'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: astrbot-macos
|
||||||
|
path: dashboard/src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
|
|
||||||
|
- name: Upload artifacts (Windows)
|
||||||
|
if: matrix.platform == 'windows-latest'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: astrbot-windows
|
||||||
|
path: dashboard/src-tauri/target/release/bundle/msi/*.msi
|
||||||
|
|
||||||
|
- name: Upload artifacts (Linux)
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: astrbot-linux
|
||||||
|
path: |
|
||||||
|
dashboard/src-tauri/target/release/bundle/deb/*.deb
|
||||||
|
dashboard/src-tauri/target/release/bundle/appimage/*.AppImage
|
||||||
@@ -32,6 +32,7 @@ tests/astrbot_plugin_openai
|
|||||||
# Dashboard
|
# Dashboard
|
||||||
dashboard/node_modules/
|
dashboard/node_modules/
|
||||||
dashboard/dist/
|
dashboard/dist/
|
||||||
|
dashboard/src-tauri/target
|
||||||
package-lock.json
|
package-lock.json
|
||||||
package.json
|
package.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
@@ -48,5 +49,6 @@ astrbot.lock
|
|||||||
chroma
|
chroma
|
||||||
venv/*
|
venv/*
|
||||||
pytest.ini
|
pytest.ini
|
||||||
|
build/
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
IFLOW.md
|
IFLOW.md
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
# AstrBot 桌面应用构建指南
|
||||||
|
|
||||||
|
本指南介绍如何使用 Nuitka 将 Python 后端打包并集成到 Tauri 桌面应用中。
|
||||||
|
|
||||||
|
## 前置要求
|
||||||
|
|
||||||
|
### 系统要求
|
||||||
|
- Python 3.10+
|
||||||
|
- Node.js 20+
|
||||||
|
- Rust (通过 rustup 安装)
|
||||||
|
- UV 包管理器
|
||||||
|
|
||||||
|
### macOS 额外要求
|
||||||
|
- Xcode Command Line Tools: `xcode-select --install`
|
||||||
|
|
||||||
|
### Linux 额外要求
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev \
|
||||||
|
libappindicator3-dev librsvg2-dev patchelf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows 额外要求
|
||||||
|
- Visual Studio 2019+ with C++ build tools
|
||||||
|
- Windows 10 SDK
|
||||||
|
|
||||||
|
## 构建步骤
|
||||||
|
|
||||||
|
### 1. 安装 Python 依赖
|
||||||
|
```bash
|
||||||
|
pip install uv
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 安装 Nuitka
|
||||||
|
```bash
|
||||||
|
pip install nuitka
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 构建 Python 后端
|
||||||
|
```bash
|
||||||
|
python build_nuitka.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这会使用 Nuitka 将 `main.py` 编译为独立可执行文件,输出到 `build/nuitka/` 目录。
|
||||||
|
|
||||||
|
**注意**: Nuitka 编译过程可能需要 10-30 分钟,取决于您的系统性能。
|
||||||
|
|
||||||
|
### 4. 安装前端依赖
|
||||||
|
```bash
|
||||||
|
cd dashboard
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 构建 Tauri 应用
|
||||||
|
```bash
|
||||||
|
npm run tauri:build
|
||||||
|
```
|
||||||
|
|
||||||
|
构建脚本会自动:
|
||||||
|
1. 运行 `build_nuitka.py` 编译 Python 后端
|
||||||
|
2. 将编译好的可执行文件复制到 `src-tauri/resources/` 目录
|
||||||
|
3. 构建 Tauri 应用并打包所有资源
|
||||||
|
|
||||||
|
### 6. 查找构建产物
|
||||||
|
|
||||||
|
构建完成后,您可以在以下位置找到安装包:
|
||||||
|
|
||||||
|
- **macOS**: `dashboard/src-tauri/target/release/bundle/dmg/AstrBot_*.dmg`
|
||||||
|
- **Windows**: `dashboard/src-tauri/target/release/bundle/msi/AstrBot_*.msi`
|
||||||
|
- **Linux**:
|
||||||
|
- `dashboard/src-tauri/target/release/bundle/deb/astrbot_*.deb`
|
||||||
|
- `dashboard/src-tauri/target/release/bundle/appimage/astrbot_*.AppImage`
|
||||||
|
|
||||||
|
## 开发模式
|
||||||
|
|
||||||
|
在开发时,您可能不想每次都完整编译 Python 后端。
|
||||||
|
|
||||||
|
### 仅开发 Tauri + Vue
|
||||||
|
```bash
|
||||||
|
cd dashboard
|
||||||
|
npm run tauri:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
这会启动开发服务器,但不会自动启动 Python 后端。您需要手动运行:
|
||||||
|
```bash
|
||||||
|
uv run main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试完整集成
|
||||||
|
如果您想测试 Tauri 自动启动 Python 后端的功能:
|
||||||
|
|
||||||
|
1. 先编译一次 Python 后端:
|
||||||
|
```bash
|
||||||
|
python build_nuitka.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 手动复制到资源目录:
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
cp -r build/nuitka/main.app dashboard/src-tauri/resources/astrbot-backend.app
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
copy build\nuitka\main.exe dashboard\src-tauri\resources\astrbot-backend.exe
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
cp build/nuitka/main.bin dashboard/src-tauri/resources/astrbot-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 运行开发模式:
|
||||||
|
```bash
|
||||||
|
cd dashboard
|
||||||
|
npm run tauri:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nuitka 构建选项说明
|
||||||
|
|
||||||
|
`build_nuitka.py` 脚本使用以下关键选项:
|
||||||
|
|
||||||
|
- `--standalone`: 创建包含所有依赖的独立目录
|
||||||
|
- `--onefile`: 将所有内容打包到单个可执行文件
|
||||||
|
- `--follow-imports`: 自动跟踪所有 Python 导入
|
||||||
|
- `--include-package`: 明确包含特定包
|
||||||
|
- `--include-data-dir`: 包含数据目录(插件、配置等)
|
||||||
|
|
||||||
|
### 自定义构建
|
||||||
|
|
||||||
|
如果您需要修改构建选项,编辑 `build_nuitka.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 添加更多要包含的包
|
||||||
|
include_packages = [
|
||||||
|
"astrbot",
|
||||||
|
"your_custom_package",
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
|
||||||
|
# 添加更多数据目录
|
||||||
|
data_includes = [
|
||||||
|
"data/config",
|
||||||
|
"your_custom_data",
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. Nuitka 编译失败
|
||||||
|
**问题**: 编译时出现 "module not found" 错误
|
||||||
|
|
||||||
|
**解决方案**: 在 `build_nuitka.py` 中添加缺失的包到 `include_packages` 列表
|
||||||
|
|
||||||
|
### 2. 运行时找不到资源文件
|
||||||
|
**问题**: 应用启动后提示找不到配置文件或插件
|
||||||
|
|
||||||
|
**解决方案**: 确保在 `build_nuitka.py` 中使用 `--include-data-dir` 包含了所有必要的数据目录
|
||||||
|
|
||||||
|
### 3. macOS 安全警告
|
||||||
|
**问题**: macOS 提示"应用来自未知开发者"
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```bash
|
||||||
|
# 临时解除限制
|
||||||
|
sudo spctl --master-disable
|
||||||
|
|
||||||
|
# 或者为特定应用授权
|
||||||
|
xattr -cr /Applications/AstrBot.app
|
||||||
|
```
|
||||||
|
|
||||||
|
对于生产发布,您需要:
|
||||||
|
1. 注册 Apple Developer 账号
|
||||||
|
2. 对应用进行代码签名
|
||||||
|
3. 提交公证 (Notarization)
|
||||||
|
|
||||||
|
### 4. Windows Defender 报毒
|
||||||
|
**问题**: Windows Defender 或其他杀毒软件报毒
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 这是 Nuitka 打包程序的常见问题
|
||||||
|
- 可以使用 `--windows-company-name` 和 `--windows-product-name` 添加元数据
|
||||||
|
- 对于生产发布,需要购买代码签名证书
|
||||||
|
|
||||||
|
### 5. Linux 依赖问题
|
||||||
|
**问题**: 在某些 Linux 发行版上缺少共享库
|
||||||
|
|
||||||
|
**解决方案**: 使用 AppImage 格式,它包含所有依赖:
|
||||||
|
```bash
|
||||||
|
# 构建时会自动生成 AppImage
|
||||||
|
npm run tauri:build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优化构建大小
|
||||||
|
|
||||||
|
默认的 `--onefile` 模式会生成较大的可执行文件。如果需要减小体积:
|
||||||
|
|
||||||
|
1. 移除不需要的包
|
||||||
|
2. 使用 `--standalone` 而不是 `--onefile`
|
||||||
|
3. 排除不必要的数据文件
|
||||||
|
|
||||||
|
修改 `build_nuitka.py`:
|
||||||
|
```python
|
||||||
|
# 移除 --onefile,使用 --standalone
|
||||||
|
nuitka_cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m", "nuitka",
|
||||||
|
"--standalone", # 只使用 standalone
|
||||||
|
# "--onefile", # 注释掉 onefile
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD 集成
|
||||||
|
|
||||||
|
项目已配置 GitHub Actions 工作流 (`.github/workflows/build-app.yml`),可以自动为所有平台构建应用。
|
||||||
|
|
||||||
|
推送标签时自动触发:
|
||||||
|
```bash
|
||||||
|
git tag v4.5.7
|
||||||
|
git push origin v4.5.7
|
||||||
|
```
|
||||||
|
|
||||||
|
或手动触发:
|
||||||
|
在 GitHub Actions 页面选择 "Build Desktop App" 工作流并点击 "Run workflow"
|
||||||
|
|
||||||
|
## 发布清单
|
||||||
|
|
||||||
|
在发布新版本前:
|
||||||
|
|
||||||
|
- [ ] 更新版本号
|
||||||
|
- `pyproject.toml` - Python 项目版本
|
||||||
|
- `dashboard/package.json` - Node 项目版本
|
||||||
|
- `dashboard/src-tauri/Cargo.toml` - Rust 项目版本
|
||||||
|
- `dashboard/src-tauri/tauri.conf.json` - Tauri 配置版本
|
||||||
|
|
||||||
|
- [ ] 运行代码检查
|
||||||
|
```bash
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format .
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] 本地测试构建
|
||||||
|
```bash
|
||||||
|
python build_nuitka.py
|
||||||
|
cd dashboard && npm run tauri:build
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] 测试安装包
|
||||||
|
- 安装生成的安装包
|
||||||
|
- 验证应用启动
|
||||||
|
- 验证 Python 后端自动启动
|
||||||
|
- 测试核心功能
|
||||||
|
|
||||||
|
- [ ] 创建发布标签
|
||||||
|
```bash
|
||||||
|
git tag -a v4.5.7 -m "Release v4.5.7"
|
||||||
|
git push origin v4.5.7
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Tauri Desktop App │
|
||||||
|
│ (Rust + WebView) │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ Vue.js Dashboard │ │
|
||||||
|
│ │ (Frontend UI) │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ Python Backend │ │
|
||||||
|
│ │ (Nuitka Compiled) │ │
|
||||||
|
│ │ - AstrBot Core │ │
|
||||||
|
│ │ - Plugins │ │
|
||||||
|
│ │ - API Server │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ HTTP/WebSocket │
|
||||||
|
│ localhost:6185 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参考资源
|
||||||
|
|
||||||
|
- [Nuitka 文档](https://nuitka.net/doc/user-manual.html)
|
||||||
|
- [Tauri 文档](https://tauri.app/v1/guides/)
|
||||||
|
- [AstrBot 文档](https://astrbot.fun)
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Use Nuitka to build the AstrBot project into standalone executables
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform_info():
|
||||||
|
"""fetch the current platform information"""
|
||||||
|
system = platform.system()
|
||||||
|
machine = platform.machine()
|
||||||
|
return system, machine
|
||||||
|
|
||||||
|
|
||||||
|
def build_with_nuitka():
|
||||||
|
"""use Nuitka to build the project"""
|
||||||
|
system, machine = get_platform_info()
|
||||||
|
|
||||||
|
print(f"🚀 Starting build for {system} ({machine}) platform...")
|
||||||
|
|
||||||
|
# Output directory
|
||||||
|
output_dir = Path("build/nuitka")
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Base Nuitka command
|
||||||
|
nuitka_cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"nuitka",
|
||||||
|
"--standalone", # Create standalone directory
|
||||||
|
"--onefile", # Single file mode
|
||||||
|
"--follow-imports", # Follow all imports
|
||||||
|
"--enable-plugin=multiprocessing", # Enable multiprocessing support
|
||||||
|
"--output-dir=build/nuitka", # Output directory
|
||||||
|
"--quiet", # Reduce output verbosity
|
||||||
|
"--assume-yes-for-downloads", # Automatically download dependencies
|
||||||
|
"--jobs=4", # Use multiple CPU cores
|
||||||
|
]
|
||||||
|
|
||||||
|
# include specific packages
|
||||||
|
include_packages = [
|
||||||
|
"astrbot",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pkg in include_packages:
|
||||||
|
nuitka_cmd.extend([f"--include-package={pkg}"])
|
||||||
|
|
||||||
|
# include data directories
|
||||||
|
# data_includes = [
|
||||||
|
# "data/config",
|
||||||
|
# "data/plugins",
|
||||||
|
# "data/temp",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# for data_dir in data_includes:
|
||||||
|
# if os.path.exists(data_dir):
|
||||||
|
# nuitka_cmd.extend([f"--include-data-dir={data_dir}={data_dir}"])
|
||||||
|
|
||||||
|
# include packages directory (built-in plugins)
|
||||||
|
# if os.path.exists("packages"):
|
||||||
|
# nuitka_cmd.extend(["--include-data-dir=packages=packages"])
|
||||||
|
|
||||||
|
# Platform specific settings
|
||||||
|
if system == "Darwin": # macOS
|
||||||
|
nuitka_cmd.extend(
|
||||||
|
[
|
||||||
|
"--macos-create-app-bundle", # Create .app bundle
|
||||||
|
"--macos-app-name=AstrBot",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# macOS icon (if exists)
|
||||||
|
icon_path = "dashboard/src-tauri/icons/icon.icns"
|
||||||
|
if os.path.exists(icon_path):
|
||||||
|
nuitka_cmd.extend([f"--macos-app-icon={icon_path}"])
|
||||||
|
elif system == "Windows":
|
||||||
|
nuitka_cmd.extend(
|
||||||
|
[
|
||||||
|
"--windows-console-mode=disable", # 无控制台窗口
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Windows icon (if exists)
|
||||||
|
icon_path = "dashboard/src-tauri/icons/icon.ico"
|
||||||
|
if os.path.exists(icon_path):
|
||||||
|
nuitka_cmd.extend([f"--windows-icon-from-ico={icon_path}"])
|
||||||
|
|
||||||
|
# Main file to compile
|
||||||
|
nuitka_cmd.append("main.py")
|
||||||
|
|
||||||
|
print(f"📦 Executing command: {' '.join(nuitka_cmd)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(nuitka_cmd, check=True)
|
||||||
|
print("✅ Nuitka build successful!")
|
||||||
|
|
||||||
|
# Find the generated executable
|
||||||
|
if system == "Darwin":
|
||||||
|
built_file = list(output_dir.glob("*.app"))
|
||||||
|
if built_file:
|
||||||
|
print(f"Generated macOS app: {built_file[0]}")
|
||||||
|
elif system == "Windows":
|
||||||
|
built_file = list(output_dir.glob("*.exe"))
|
||||||
|
if built_file:
|
||||||
|
print(f"Generated Windows executable: {built_file[0]}")
|
||||||
|
else: # Linux
|
||||||
|
built_file = list(output_dir.glob("main.bin"))
|
||||||
|
if built_file:
|
||||||
|
print(f"Generated Linux executable: {built_file[0]}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Nuitka build failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("AstrBot Nuitka Builder")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
if build_with_nuitka():
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🎉 Build Complete!")
|
||||||
|
print("=" * 60)
|
||||||
|
else:
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("❌ Build Failed")
|
||||||
|
print("=" * 60)
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Use PyInstaller to build the AstrBot project into standalone executables
|
||||||
|
"""
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform_info():
|
||||||
|
"""fetch the current platform information"""
|
||||||
|
system = platform.system()
|
||||||
|
machine = platform.machine()
|
||||||
|
return system, machine
|
||||||
|
|
||||||
|
|
||||||
|
def build_with_pyinstaller():
|
||||||
|
"""use PyInstaller to build the project"""
|
||||||
|
system, machine = get_platform_info()
|
||||||
|
|
||||||
|
print(f"🚀 Starting build for {system} ({machine}) platform...")
|
||||||
|
|
||||||
|
# Output directory
|
||||||
|
output_dir = Path("build/pyinstaller")
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Base PyInstaller command
|
||||||
|
pyinstaller_cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"PyInstaller",
|
||||||
|
"--clean", # Clean cache before build
|
||||||
|
"--noconfirm", # Replace output directory without asking
|
||||||
|
"--onefile", # Single file mode
|
||||||
|
"--distpath=build/pyinstaller/dist", # Distribution directory
|
||||||
|
"--workpath=build/pyinstaller/build", # Work directory
|
||||||
|
"--specpath=build/pyinstaller", # Spec file directory
|
||||||
|
"--name=AstrBot", # Output executable name
|
||||||
|
]
|
||||||
|
# Platform specific settings
|
||||||
|
# if system == "Darwin": # macOS
|
||||||
|
# # macOS icon (if exists)
|
||||||
|
# icon_path = "dashboard/src-tauri/icons/icon.icns"
|
||||||
|
# if os.path.exists(icon_path):
|
||||||
|
# pyinstaller_cmd.extend([f"--icon={icon_path}"])
|
||||||
|
# # Create .app bundle
|
||||||
|
# pyinstaller_cmd.extend(["--windowed"])
|
||||||
|
# elif system == "Windows":
|
||||||
|
# # Windows icon (if exists)
|
||||||
|
# icon_path = "dashboard/src-tauri/icons/icon.ico"
|
||||||
|
# if os.path.exists(icon_path):
|
||||||
|
# pyinstaller_cmd.extend([f"--icon={icon_path}"])
|
||||||
|
# # No console window
|
||||||
|
# pyinstaller_cmd.extend(["--windowed"])
|
||||||
|
# else: # Linux
|
||||||
|
# pyinstaller_cmd.extend(["--console"])
|
||||||
|
|
||||||
|
# Main file to compile
|
||||||
|
pyinstaller_cmd.append("main.py")
|
||||||
|
|
||||||
|
print(f"📦 Executing command: {' '.join(pyinstaller_cmd)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(pyinstaller_cmd, check=True)
|
||||||
|
print("✅ PyInstaller build successful!")
|
||||||
|
|
||||||
|
# Find the generated executable
|
||||||
|
dist_dir = output_dir / "dist"
|
||||||
|
if system == "Darwin":
|
||||||
|
built_file = list(dist_dir.glob("AstrBot.app"))
|
||||||
|
if not built_file:
|
||||||
|
built_file = list(dist_dir.glob("AstrBot"))
|
||||||
|
if built_file:
|
||||||
|
print(f"📱 Generated macOS app: {built_file[0]}")
|
||||||
|
elif system == "Windows":
|
||||||
|
built_file = list(dist_dir.glob("AstrBot.exe"))
|
||||||
|
if built_file:
|
||||||
|
print(f"💻 Generated Windows executable: {built_file[0]}")
|
||||||
|
else: # Linux
|
||||||
|
built_file = list(dist_dir.glob("AstrBot"))
|
||||||
|
if built_file:
|
||||||
|
print(f"🐧 Generated Linux executable: {built_file[0]}")
|
||||||
|
|
||||||
|
print(f"\n📁 Output directory: {dist_dir.absolute()}")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ PyInstaller build failed: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Unexpected error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def install_pyinstaller():
|
||||||
|
"""Install PyInstaller if not already installed"""
|
||||||
|
try:
|
||||||
|
import PyInstaller
|
||||||
|
|
||||||
|
print(f"✅ PyInstaller already installed (version {PyInstaller.__version__})")
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
print("📥 PyInstaller not found, installing...")
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
[sys.executable, "-m", "pip", "install", "pyinstaller"], check=True
|
||||||
|
)
|
||||||
|
print("✅ PyInstaller installed successfully!")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Failed to install PyInstaller: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("AstrBot PyInstaller Builder")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Check and install PyInstaller
|
||||||
|
if not install_pyinstaller():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Build
|
||||||
|
if build_with_pyinstaller():
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🎉 Build Complete!")
|
||||||
|
print("=" * 60)
|
||||||
|
else:
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("❌ Build Failed")
|
||||||
|
print("=" * 60)
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# AstrBot Dashboard - Tauri 桌面应用
|
||||||
|
|
||||||
|
本项目现已支持通过 Tauri 构建为桌面应用,同时保持与 Web 版本的兼容性。
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
### 系统依赖
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
```bash
|
||||||
|
# 安装 Xcode Command Line Tools
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
- 安装 [Microsoft Visual Studio C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
||||||
|
- 安装 [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||||
|
|
||||||
|
**Linux (Ubuntu/Debian):**
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libwebkit2gtk-4.0-dev \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
file \
|
||||||
|
libssl-dev \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libayatana-appindicator3-dev \
|
||||||
|
librsvg2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rust 环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 Rust
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
|
||||||
|
# 验证安装
|
||||||
|
rustc --version
|
||||||
|
cargo --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd dashboard
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发模式
|
||||||
|
|
||||||
|
### Web 端开发(不变)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 http://localhost:3000
|
||||||
|
|
||||||
|
### 桌面端开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run tauri:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
这会同时启动:
|
||||||
|
1. Vite 开发服务器(端口 3000)
|
||||||
|
2. Tauri 桌面应用窗口
|
||||||
|
|
||||||
|
热重载功能正常工作,修改代码后会自动刷新。
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
### Web 端构建(不变)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
输出目录:`dist/`
|
||||||
|
|
||||||
|
### 桌面端构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run tauri:build
|
||||||
|
```
|
||||||
|
|
||||||
|
构建产物位置:
|
||||||
|
- **macOS**: `src-tauri/target/release/bundle/dmg/`
|
||||||
|
- **Windows**: `src-tauri/target/release/bundle/msi/`
|
||||||
|
- **Linux**: `src-tauri/target/release/bundle/deb/` 或 `appimage/`
|
||||||
|
|
||||||
|
## 图标设置
|
||||||
|
|
||||||
|
### 自动生成图标
|
||||||
|
|
||||||
|
准备一个至少 512x512 像素的 PNG 图标,然后运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run tauri icon path/to/your/icon.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动设置图标
|
||||||
|
|
||||||
|
将以下图标放入 `src-tauri/icons/` 目录:
|
||||||
|
- `32x32.png`
|
||||||
|
- `128x128.png`
|
||||||
|
- `128x128@2x.png`
|
||||||
|
- `icon.icns` (macOS)
|
||||||
|
- `icon.ico` (Windows)
|
||||||
|
|
||||||
|
## 代码兼容性
|
||||||
|
|
||||||
|
项目已配置为同时支持 Web 和桌面端,使用相同的代码库。
|
||||||
|
|
||||||
|
### 环境检测工具
|
||||||
|
|
||||||
|
在 `src/utils/tauri.ts` 中提供了环境检测工具:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { isTauri, isWeb, PlatformAPI } from '@/utils/tauri';
|
||||||
|
|
||||||
|
// 检测运行环境
|
||||||
|
if (isTauri()) {
|
||||||
|
console.log('运行在桌面应用中');
|
||||||
|
} else {
|
||||||
|
console.log('运行在浏览器中');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取正确的 API 端点
|
||||||
|
const baseURL = PlatformAPI.getBaseURL();
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 调用注意事项
|
||||||
|
|
||||||
|
- **Web 端**: 使用 Vite 代理,API 路径为 `/api/*`
|
||||||
|
- **桌面端**: 直接连接到 `http://127.0.0.1:6185`
|
||||||
|
|
||||||
|
已在 `PlatformAPI.getBaseURL()` 中处理,使用 axios 时:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import axios from 'axios';
|
||||||
|
import { PlatformAPI } from '@/utils/tauri';
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: PlatformAPI.getBaseURL()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### tauri.conf.json
|
||||||
|
|
||||||
|
主要配置项:
|
||||||
|
- `build.devPath`: 开发服务器地址(http://localhost:3000)
|
||||||
|
- `build.distDir`: 构建输出目录(../dist)
|
||||||
|
- `tauri.allowlist`: API 权限配置
|
||||||
|
- `tauri.windows`: 窗口配置(大小、标题等)
|
||||||
|
|
||||||
|
### 安全性
|
||||||
|
|
||||||
|
默认配置已启用必要的权限:
|
||||||
|
- 文件系统访问(限定在 APPDATA 目录)
|
||||||
|
- HTTP 请求(限定到本地后端)
|
||||||
|
- 窗口控制
|
||||||
|
- 对话框(打开/保存文件)
|
||||||
|
|
||||||
|
可在 `tauri.conf.json` 的 `allowlist` 部分调整权限。
|
||||||
|
|
||||||
|
## 后端连接
|
||||||
|
|
||||||
|
桌面应用需要后端服务运行在 `http://127.0.0.1:6185`。
|
||||||
|
|
||||||
|
### 启动流程
|
||||||
|
|
||||||
|
1. 启动 AstrBot 后端:
|
||||||
|
```bash
|
||||||
|
cd /path/to/AstrBot
|
||||||
|
uv run main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动桌面应用:
|
||||||
|
```bash
|
||||||
|
cd dashboard
|
||||||
|
npm run tauri:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
或直接运行打包后的应用(后端需要已启动)。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 桌面应用无法连接到后端?
|
||||||
|
|
||||||
|
确保:
|
||||||
|
1. AstrBot 后端正在运行(`uv run main.py`)
|
||||||
|
2. 后端监听在 `127.0.0.1:6185`
|
||||||
|
3. 防火墙未阻止连接
|
||||||
|
|
||||||
|
### Q: 图标未显示?
|
||||||
|
|
||||||
|
检查 `src-tauri/icons/` 目录中是否有所需的图标文件,或使用 `npm run tauri icon` 命令生成。
|
||||||
|
|
||||||
|
### Q: 构建失败?
|
||||||
|
|
||||||
|
- 确保已安装 Rust 和系统依赖
|
||||||
|
- 运行 `cargo clean` 清理缓存后重试
|
||||||
|
- 检查 Rust 版本(需要 1.60+)
|
||||||
|
|
||||||
|
### Q: Web 端功能是否受影响?
|
||||||
|
|
||||||
|
不受影响。`npm run dev` 和 `npm run build` 的行为完全不变。
|
||||||
|
|
||||||
|
## 开发建议
|
||||||
|
|
||||||
|
1. **优先使用 Web 端开发**: 更快的热重载,更好的调试体验
|
||||||
|
2. **定期测试桌面端**: 确保跨平台兼容性
|
||||||
|
3. **使用环境检测**: 针对不同平台提供最佳体验
|
||||||
|
4. **注意 API 差异**: Web 和桌面端的某些 API 可能有差异
|
||||||
|
|
||||||
|
## 更多资源
|
||||||
|
|
||||||
|
- [Tauri 官方文档](https://tauri.app/)
|
||||||
|
- [Tauri API 参考](https://tauri.app/v1/api/js/)
|
||||||
|
- [Tauri Discord 社区](https://discord.com/invite/tauri)
|
||||||
@@ -10,10 +10,14 @@
|
|||||||
"build-prod": "vue-tsc --noEmit && vite build --base=/vue/free/",
|
"build-prod": "vue-tsc --noEmit && vite build --base=/vue/free/",
|
||||||
"preview": "vite preview --port 5050",
|
"preview": "vite preview --port 5050",
|
||||||
"typecheck": "vue-tsc --noEmit",
|
"typecheck": "vue-tsc --noEmit",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"tauri": "tauri",
|
||||||
|
"tauri:dev": "tauri dev",
|
||||||
|
"tauri:build": "tauri build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@guolao/vue-monaco-editor": "^1.5.4",
|
"@guolao/vue-monaco-editor": "^1.5.4",
|
||||||
|
"@tauri-apps/api": "^2.9.0",
|
||||||
"@tiptap/starter-kit": "2.1.7",
|
"@tiptap/starter-kit": "2.1.7",
|
||||||
"@tiptap/vue-3": "2.1.7",
|
"@tiptap/vue-3": "2.1.7",
|
||||||
"apexcharts": "3.42.0",
|
"apexcharts": "3.42.0",
|
||||||
@@ -43,6 +47,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdi/font": "7.2.96",
|
"@mdi/font": "7.2.96",
|
||||||
"@rushstack/eslint-patch": "1.3.3",
|
"@rushstack/eslint-patch": "1.3.3",
|
||||||
|
"@tauri-apps/cli": "^2.9.4",
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/node": "^20.5.7",
|
"@types/node": "^20.5.7",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Tauri specific
|
||||||
|
src-tauri/target/
|
||||||
|
src-tauri/WixTools/
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "astrbot-dashboard"
|
||||||
|
version = "4.5.6"
|
||||||
|
description = "AstrBot"
|
||||||
|
authors = ["AstrBot Team"]
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
repository = "https://github.com/AstrBotDevs/AstrBot"
|
||||||
|
default-run = "astrbot-dashboard"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = { version = "2.9.2", features = ["macos-private-api", "protocol-asset"] }
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||||
|
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
|
||||||
|
# DO NOT REMOVE!!
|
||||||
|
custom-protocol = [ "tauri/custom-protocol" ]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 602 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1,104 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use std::process::{Child, Command};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use tauri::{AppHandle, Emitter, Listener, Manager, State};
|
||||||
|
|
||||||
|
struct BackendProcess(Mutex<Option<Child>>);
|
||||||
|
|
||||||
|
fn start_backend_process(app_handle: &AppHandle) -> Option<Child> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let backend_path = "astrbot-backend.app/Contents/MacOS/main";
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let backend_path = "astrbot-backend.exe";
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let backend_path = "astrbot-backend";
|
||||||
|
|
||||||
|
// 获取资源目录
|
||||||
|
let resource_dir = match app_handle
|
||||||
|
.path()
|
||||||
|
.resource_dir()
|
||||||
|
{
|
||||||
|
Ok(dir) => dir,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to get resource directory: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_backend_path = resource_dir.join(backend_path);
|
||||||
|
|
||||||
|
println!("Starting backend process at: {:?}", full_backend_path);
|
||||||
|
|
||||||
|
match Command::new(&full_backend_path).spawn() {
|
||||||
|
Ok(child) => {
|
||||||
|
println!(
|
||||||
|
"Backend process started successfully with PID: {}",
|
||||||
|
child.id()
|
||||||
|
);
|
||||||
|
Some(child)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to start backend process: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn restart_backend(
|
||||||
|
app_handle: AppHandle,
|
||||||
|
backend_state: State<BackendProcess>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut backend = backend_state.0.lock().unwrap();
|
||||||
|
|
||||||
|
// 停止现有进程
|
||||||
|
if let Some(mut child) = backend.take() {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动新进程
|
||||||
|
*backend = start_backend_process(&app_handle);
|
||||||
|
|
||||||
|
if backend.is_some() {
|
||||||
|
Ok("Backend restarted successfully".to_string())
|
||||||
|
} else {
|
||||||
|
Err("Failed to restart backend".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
// 启动后端进程
|
||||||
|
let backend_process = start_backend_process(app.handle());
|
||||||
|
app.manage(BackendProcess(Mutex::new(backend_process)));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![restart_backend])
|
||||||
|
.on_window_event(|window, event| {
|
||||||
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
||||||
|
// 关闭窗口时清理后端进程
|
||||||
|
if let Some(backend_state) = window.app_handle().try_state::<BackendProcess>() {
|
||||||
|
let mut backend = backend_state.0.lock().unwrap();
|
||||||
|
if let Some(mut child) = backend.take() {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "AstrBot",
|
||||||
|
"version": "4.5.6",
|
||||||
|
"identifier": "com.astrbot.app",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
"devUrl": "http://localhost:3000",
|
||||||
|
"beforeBuildCommand": "pnpm build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"macOSPrivateApi": true,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "AstrBot",
|
||||||
|
"label": "main",
|
||||||
|
"url": "/",
|
||||||
|
"width": 1400,
|
||||||
|
"height": 900
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null,
|
||||||
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": [
|
||||||
|
"$APPDATA/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"resources": [
|
||||||
|
"resources/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"fs": {
|
||||||
|
"requireLiteralLeadingDot": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import MarkdownIt from 'markdown-it';
|
|||||||
import { useI18n } from '@/i18n/composables';
|
import { useI18n } from '@/i18n/composables';
|
||||||
import { router } from '@/router';
|
import { router } from '@/router';
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
|
import { isTauri } from '@/utils/tauri';
|
||||||
|
|
||||||
// 配置markdown-it,默认安全设置
|
// 配置markdown-it,默认安全设置
|
||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Tauri 环境检测工具
|
||||||
|
* 用于区分 Web 端和桌面端环境
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否在 Tauri 环境中运行
|
||||||
|
* @returns {boolean} 如果在 Tauri 环境中返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isTauri(): boolean {
|
||||||
|
return typeof window !== 'undefined' &&
|
||||||
|
(window as any).__TAURI_INTERNALS__ !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否在 Web 环境中运行
|
||||||
|
* @returns {boolean} 如果在 Web 环境中返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isWeb(): boolean {
|
||||||
|
return !isTauri();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Tauri API(仅在 Tauri 环境中可用)
|
||||||
|
* @returns {any} Tauri API 对象或 null
|
||||||
|
*/
|
||||||
|
export function getTauriAPI(): any {
|
||||||
|
if (isTauri()) {
|
||||||
|
// Tauri 2.0 建议使用 @tauri-apps/api 包而不是全局对象
|
||||||
|
return (window as any).__TAURI_INTERNALS__;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台特定的 API 调用包装器
|
||||||
|
* 在 Web 环境中使用 HTTP API,在 Tauri 环境中可以使用本地 API
|
||||||
|
*/
|
||||||
|
export class PlatformAPI {
|
||||||
|
/**
|
||||||
|
* 根据平台选择合适的 API 端点
|
||||||
|
* @param webEndpoint Web 端 API 地址
|
||||||
|
* @param tauriEndpoint Tauri 端 API 地址(可选,默认使用 webEndpoint)
|
||||||
|
*/
|
||||||
|
static getEndpoint(webEndpoint: string, tauriEndpoint?: string): string {
|
||||||
|
if (isTauri() && tauriEndpoint) {
|
||||||
|
return tauriEndpoint;
|
||||||
|
}
|
||||||
|
return webEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取基础 URL
|
||||||
|
* Web 端使用相对路径,Tauri 端使用完整的后端地址
|
||||||
|
*/
|
||||||
|
static getBaseURL(): string {
|
||||||
|
if (isTauri()) {
|
||||||
|
// Tauri 环境中,需要连接到本地运行的后端服务
|
||||||
|
return 'http://127.0.0.1:6185';
|
||||||
|
}
|
||||||
|
// Web 环境中使用相对路径,由 Vite 代理处理
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isTauri,
|
||||||
|
isWeb,
|
||||||
|
getTauriAPI,
|
||||||
|
PlatformAPI
|
||||||
|
};
|
||||||
@@ -43,5 +43,8 @@ export default defineConfig({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
// Tauri 特定配置
|
||||||
|
clearScreen: false,
|
||||||
|
envPrefix: ['VITE_', 'TAURI_'],
|
||||||
});
|
});
|
||||||
|
|||||||