Compare commits
21 Commits
v4.5.2
...
feat/tauri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c090299b1 | ||
|
|
b360c8446e | ||
|
|
6d00717655 | ||
|
|
bb5f06498e | ||
|
|
aca5743ab6 | ||
|
|
6903032f7e | ||
|
|
1ce0ff87bd | ||
|
|
e39d6bae0b | ||
|
|
8028e9e9a6 | ||
|
|
817f20ea01 | ||
|
|
ad5579a2f4 | ||
|
|
81a689a79b | ||
|
|
1893dd8336 | ||
|
|
021ca8175b | ||
|
|
39d6207fe1 | ||
|
|
23ce687229 | ||
|
|
3715312fd2 | ||
|
|
8196922cac | ||
|
|
8089ad91da | ||
|
|
2930cc3fd8 | ||
|
|
0e841a8b25 |
@@ -1,6 +1,7 @@
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
# github actions
|
||||
.git
|
||||
.github/
|
||||
.*ignore
|
||||
# User-specific stuff
|
||||
@@ -19,4 +20,5 @@ data/
|
||||
changelogs/
|
||||
tests/
|
||||
.ruff_cache/
|
||||
.astrbot
|
||||
.astrbot
|
||||
astrbot.lock
|
||||
2
.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml
vendored
@@ -16,7 +16,7 @@ body:
|
||||
|
||||
请将插件信息填写到下方的 JSON 代码块中。其中 `tags`(插件标签)和 `social_link`(社交链接)选填。
|
||||
|
||||
不熟悉 JSON ?可以从 [此处](https://plugins.astrbot.app/submit) 生成 JSON ,生成后记得复制粘贴过来.
|
||||
不熟悉 JSON ?可以从 [此站](https://plugins.astrbot.app) 右下角提交。
|
||||
|
||||
- type: textarea
|
||||
id: plugin-info
|
||||
|
||||
79
.github/workflows/build-app.yml
vendored
Normal file
@@ -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
|
||||
2
.gitignore
vendored
@@ -32,6 +32,7 @@ tests/astrbot_plugin_openai
|
||||
# Dashboard
|
||||
dashboard/node_modules/
|
||||
dashboard/dist/
|
||||
dashboard/src-tauri/target
|
||||
package-lock.json
|
||||
package.json
|
||||
|
||||
@@ -47,3 +48,4 @@ astrbot.lock
|
||||
chroma
|
||||
venv/*
|
||||
pytest.ini
|
||||
build/
|
||||
287
BUILD_INSTRUCTIONS.md
Normal file
@@ -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)
|
||||
16
Dockerfile
@@ -18,15 +18,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
||||
apt-get install -y --no-install-recommends nodejs && \
|
||||
echo "3.11" > .python-version && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y curl gnupg \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
RUN python -m pip install --no-cache-dir uv && \
|
||||
uv pip install socksio pilk --no-cache-dir --system
|
||||
RUN python -m pip install uv \
|
||||
&& echo "3.11" > .python-version
|
||||
RUN uv pip install -r requirements.txt --no-cache-dir --system
|
||||
RUN uv pip install socksio uv pilk --no-cache-dir --system
|
||||
|
||||
EXPOSE 6185
|
||||
EXPOSE 6186
|
||||
|
||||
CMD ["uv", "run", "main.py"]
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /AstrBot
|
||||
|
||||
COPY . /AstrBot/
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
build-essential \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
curl \
|
||||
unzip \
|
||||
ca-certificates \
|
||||
bash \
|
||||
git \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
ENV NVM_DIR="/root/.nvm" \
|
||||
NODE_VERSION=22
|
||||
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash && \
|
||||
. "$NVM_DIR/nvm.sh" && \
|
||||
nvm install $NODE_VERSION && \
|
||||
nvm use $NODE_VERSION && \
|
||||
nvm alias default $NODE_VERSION && \
|
||||
node -v && npm -v && \
|
||||
echo "3.11" > .python-version
|
||||
ENV PATH="$NVM_DIR/versions/node/v$(node -v | cut -d 'v' -f 2)/bin:$PATH"
|
||||
|
||||
RUN python -m pip install --no-cache-dir uv
|
||||
|
||||
# 安装项目依赖(根据指南,使用 uv sync)
|
||||
RUN uv sync --no-cache
|
||||
|
||||
EXPOSE 6185
|
||||
EXPOSE 6186
|
||||
|
||||
CMD ["uv", "run", "main.py"]
|
||||
116
README.md
@@ -8,7 +8,7 @@
|
||||
|
||||
<div>
|
||||
<a href="https://trendshift.io/repositories/12875" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12875" alt="Soulter%2FAstrBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
<a href="https://hellogithub.com/repository/AstrBotDevs/AstrBot" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d127d50cd5e54c5382328acc3bb25483&claim_uid=ZO9by7qCXgSd6Lp&t=1" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<a href="https://hellogithub.com/repository/AstrBotDevs/AstrBot" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=d127d50cd5e54c5382328acc3bb25483&claim_uid=ZO9by7qCXgSd6Lp&t=2" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
@@ -119,83 +119,73 @@ uv run main.py
|
||||
|
||||
<a href="https://discord.gg/hAVk6tgV36"><img alt="Discord_community" src="https://img.shields.io/badge/Discord-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
|
||||
|
||||
## ⚡ 消息平台支持情况
|
||||
## 支持的消息平台
|
||||
|
||||
**官方维护**
|
||||
|
||||
| 平台 | 支持性 |
|
||||
| -------- | ------- |
|
||||
| QQ(官方平台) | ✔ |
|
||||
| QQ(OneBot) | ✔ |
|
||||
| Telegram | ✔ |
|
||||
| 企微应用 | ✔ |
|
||||
| 企微智能机器人 | ✔ |
|
||||
| 微信客服 | ✔ |
|
||||
| 微信公众号 | ✔ |
|
||||
| 飞书 | ✔ |
|
||||
| 钉钉 | ✔ |
|
||||
| Slack | ✔ |
|
||||
| Discord | ✔ |
|
||||
| Satori | ✔ |
|
||||
| Misskey | ✔ |
|
||||
| Whatsapp | 将支持 |
|
||||
| LINE | 将支持 |
|
||||
- QQ (官方平台 & OneBot)
|
||||
- Telegram
|
||||
- 企微应用 & 企微智能机器人
|
||||
- 微信客服 & 微信公众号
|
||||
- 飞书
|
||||
- 钉钉
|
||||
- Slack
|
||||
- Discord
|
||||
- Satori
|
||||
- Misskey
|
||||
- Whatsapp (将支持)
|
||||
- LINE (将支持)
|
||||
|
||||
**社区维护**
|
||||
|
||||
| 平台 | 支持性 |
|
||||
| -------- | ------- |
|
||||
| [KOOK](https://github.com/wuyan1003/astrbot_plugin_kook_adapter) | ✔ |
|
||||
| [VoceChat](https://github.com/HikariFroya/astrbot_plugin_vocechat) | ✔ |
|
||||
| [Bilibili 私信](https://github.com/Hina-Chat/astrbot_plugin_bilibili_adapter) | ✔ |
|
||||
| [wxauto](https://github.com/luosheng520qaq/wxauto-repost-onebotv11) | ✔ |
|
||||
- [KOOK](https://github.com/wuyan1003/astrbot_plugin_kook_adapter)
|
||||
- [VoceChat](https://github.com/HikariFroya/astrbot_plugin_vocechat)
|
||||
- [Bilibili 私信](https://github.com/Hina-Chat/astrbot_plugin_bilibili_adapter)
|
||||
- [wxauto](https://github.com/luosheng520qaq/wxauto-repost-onebotv11)
|
||||
|
||||
## ⚡ 提供商支持情况
|
||||
## 支持的模型服务
|
||||
|
||||
**大模型服务**
|
||||
|
||||
| 名称 | 支持性 | 备注 |
|
||||
| -------- | ------- | ------- |
|
||||
| OpenAI | ✔ | 支持任何兼容 OpenAI API 的服务 |
|
||||
| Anthropic | ✔ | |
|
||||
| Google Gemini | ✔ | |
|
||||
| Moonshot AI | ✔ | |
|
||||
| 智谱 AI | ✔ | |
|
||||
| DeepSeek | ✔ | |
|
||||
| Ollama | ✔ | 本地部署 DeepSeek 等开源语言模型 |
|
||||
| LM Studio | ✔ | 本地部署 DeepSeek 等开源语言模型 |
|
||||
| [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_astrbot&referral_code=FV7DcGowN4hB5UuXKgpE74) | ✔ | |
|
||||
| [302.AI](https://share.302.ai/rr1M3l) | ✔ | |
|
||||
| [小马算力](https://www.tokenpony.cn/3YPyf) | ✔ | |
|
||||
| 硅基流动 | ✔ | |
|
||||
| PPIO 派欧云 | ✔ | |
|
||||
| ModelScope | ✔ | |
|
||||
| OneAPI | ✔ | |
|
||||
| Dify | ✔ | |
|
||||
| 阿里云百炼应用 | ✔ | |
|
||||
| Coze | ✔ | |
|
||||
- OpenAI 及兼容服务
|
||||
- Anthropic
|
||||
- Google Gemini
|
||||
- Moonshot AI
|
||||
- 智谱 AI
|
||||
- DeepSeek
|
||||
- Ollama (本地部署)
|
||||
- LM Studio (本地部署)
|
||||
- [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_astrbot&referral_code=FV7DcGowN4hB5UuXKgpE74)
|
||||
- [302.AI](https://share.302.ai/rr1M3l)
|
||||
- [小马算力](https://www.tokenpony.cn/3YPyf)
|
||||
- [硅基流动](https://docs.siliconflow.cn/cn/usercases/use-siliconcloud-in-astrbot)
|
||||
- [PPIO 派欧云](https://ppio.com/user/register?invited_by=AIOONE)
|
||||
- ModelScope
|
||||
- OneAPI
|
||||
|
||||
**LLMOps 平台**
|
||||
|
||||
- Dify
|
||||
- 阿里云百炼应用
|
||||
- Coze
|
||||
|
||||
**语音转文本服务**
|
||||
|
||||
| 名称 | 支持性 | 备注 |
|
||||
| -------- | ------- | ------- |
|
||||
| Whisper | ✔ | 支持 API、本地部署 |
|
||||
| SenseVoice | ✔ | 本地部署 |
|
||||
- OpenAI Whisper
|
||||
- SenseVoice
|
||||
|
||||
**文本转语音服务**
|
||||
|
||||
| 名称 | 支持性 | 备注 |
|
||||
| -------- | ------- | ------- |
|
||||
| OpenAI TTS | ✔ | |
|
||||
| Gemini TTS | ✔ | |
|
||||
| GSVI | ✔ | GPT-Sovits-Inference |
|
||||
| GPT-SoVITs | ✔ | GPT-Sovits |
|
||||
| FishAudio | ✔ | |
|
||||
| Edge TTS | ✔ | Edge 浏览器的免费 TTS |
|
||||
| 阿里云百炼 TTS | ✔ | |
|
||||
| Azure TTS | ✔ | |
|
||||
| Minimax TTS | ✔ | |
|
||||
| 火山引擎 TTS | ✔ | |
|
||||
- OpenAI TTS
|
||||
- Gemini TTS
|
||||
- GPT-Sovits-Inference
|
||||
- GPT-Sovits
|
||||
- FishAudio
|
||||
- Edge TTS
|
||||
- 阿里云百炼 TTS
|
||||
- Azure TTS
|
||||
- Minimax TTS
|
||||
- 火山引擎 TTS
|
||||
|
||||
## ❤️ 贡献
|
||||
|
||||
@@ -229,7 +219,7 @@ pre-commit install
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
> [!TIP]
|
||||
> [!TIP]
|
||||
> 如果本项目对您的生活 / 工作产生了帮助,或者您关注本项目的未来发展,请给项目 Star,这是我们维护这个开源项目的动力 <3
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||
|
||||
VERSION = "4.5.2"
|
||||
VERSION = "4.5.6"
|
||||
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||
|
||||
# 默认配置
|
||||
@@ -740,6 +740,7 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "https://api.openai.com/v1",
|
||||
"timeout": 120,
|
||||
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
"hint": "也兼容所有与 OpenAI API 兼容的服务。",
|
||||
@@ -755,6 +756,7 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "",
|
||||
"timeout": 120,
|
||||
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -768,6 +770,7 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "https://api.x.ai/v1",
|
||||
"timeout": 120,
|
||||
"model_config": {"model": "grok-2-latest", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"xai_native_search": False,
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
@@ -799,6 +802,7 @@ CONFIG_METADATA_2 = {
|
||||
"key": ["ollama"], # ollama 的 key 默认是 ollama
|
||||
"api_base": "http://localhost:11434/v1",
|
||||
"model_config": {"model": "llama3.1-8b", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -813,6 +817,7 @@ CONFIG_METADATA_2 = {
|
||||
"model_config": {
|
||||
"model": "llama-3.1-8b",
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -829,6 +834,7 @@ CONFIG_METADATA_2 = {
|
||||
"model": "gemini-1.5-flash",
|
||||
"temperature": 0.4,
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -870,6 +876,7 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "https://api.deepseek.com/v1",
|
||||
"timeout": 120,
|
||||
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "tool_use"],
|
||||
},
|
||||
@@ -883,6 +890,7 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "https://api.302.ai/v1",
|
||||
"timeout": 120,
|
||||
"model_config": {"model": "gpt-4.1-mini", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -899,6 +907,7 @@ CONFIG_METADATA_2 = {
|
||||
"model": "deepseek-ai/DeepSeek-V3",
|
||||
"temperature": 0.4,
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -915,6 +924,7 @@ CONFIG_METADATA_2 = {
|
||||
"model": "deepseek/deepseek-r1",
|
||||
"temperature": 0.4,
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
},
|
||||
"小马算力": {
|
||||
@@ -930,6 +940,7 @@ CONFIG_METADATA_2 = {
|
||||
"model": "kimi-k2-instruct-0905",
|
||||
"temperature": 0.7,
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
},
|
||||
"优云智算": {
|
||||
@@ -944,6 +955,7 @@ CONFIG_METADATA_2 = {
|
||||
"model_config": {
|
||||
"model": "moonshotai/Kimi-K2-Instruct",
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -957,6 +969,7 @@ CONFIG_METADATA_2 = {
|
||||
"timeout": 120,
|
||||
"api_base": "https://api.moonshot.cn/v1",
|
||||
"model_config": {"model": "moonshot-v1-8k", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -972,6 +985,8 @@ CONFIG_METADATA_2 = {
|
||||
"model_config": {
|
||||
"model": "glm-4-flash",
|
||||
},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
"Dify": {
|
||||
@@ -1028,6 +1043,7 @@ CONFIG_METADATA_2 = {
|
||||
"timeout": 120,
|
||||
"api_base": "https://api-inference.modelscope.cn/v1",
|
||||
"model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4},
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
"modalities": ["text", "image", "tool_use"],
|
||||
},
|
||||
@@ -1040,6 +1056,7 @@ CONFIG_METADATA_2 = {
|
||||
"key": [],
|
||||
"api_base": "https://api.fastgpt.in/api/v1",
|
||||
"timeout": 60,
|
||||
"custom_headers": {},
|
||||
"custom_extra_body": {},
|
||||
},
|
||||
"Whisper(API)": {
|
||||
@@ -1321,6 +1338,12 @@ CONFIG_METADATA_2 = {
|
||||
"render_type": "checkbox",
|
||||
"hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。",
|
||||
},
|
||||
"custom_headers": {
|
||||
"description": "自定义添加请求头",
|
||||
"type": "dict",
|
||||
"items": {},
|
||||
"hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。",
|
||||
},
|
||||
"custom_extra_body": {
|
||||
"description": "自定义请求体参数",
|
||||
"type": "dict",
|
||||
|
||||
@@ -429,6 +429,10 @@ class LLMRequestSubStage(Stage):
|
||||
logger.error(f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。")
|
||||
return
|
||||
|
||||
streaming_response = self.streaming_response
|
||||
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
|
||||
streaming_response = bool(enable_streaming)
|
||||
|
||||
if event.get_extra("provider_request"):
|
||||
req = event.get_extra("provider_request")
|
||||
assert isinstance(req, ProviderRequest), (
|
||||
@@ -548,7 +552,7 @@ class LLMRequestSubStage(Stage):
|
||||
provider=provider,
|
||||
first_provider_request=req,
|
||||
curr_provider_request=req,
|
||||
streaming=self.streaming_response,
|
||||
streaming=streaming_response,
|
||||
event=event,
|
||||
)
|
||||
await agent_runner.reset(
|
||||
@@ -560,10 +564,10 @@ class LLMRequestSubStage(Stage):
|
||||
),
|
||||
tool_executor=FunctionToolExecutor(),
|
||||
agent_hooks=MAIN_AGENT_HOOKS,
|
||||
streaming=self.streaming_response,
|
||||
streaming=streaming_response,
|
||||
)
|
||||
|
||||
if self.streaming_response:
|
||||
if streaming_response:
|
||||
# 流式响应
|
||||
event.set_result(
|
||||
MessageEventResult()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import binascii
|
||||
import sys
|
||||
from collections.abc import AsyncGenerator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
@@ -21,11 +21,6 @@ from astrbot.api.platform import AstrBotMessage, At, PlatformMetadata
|
||||
from .client import DiscordBotClient
|
||||
from .components import DiscordEmbed, DiscordView
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override
|
||||
else:
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
# 自定义Discord视图组件(兼容旧版本)
|
||||
class DiscordViewComponent(BaseMessageComponent):
|
||||
@@ -49,7 +44,6 @@ class DiscordPlatformEvent(AstrMessageEvent):
|
||||
self.client = client
|
||||
self.interaction_followup_webhook = interaction_followup_webhook
|
||||
|
||||
@override
|
||||
async def send(self, message: MessageChain):
|
||||
"""发送消息到Discord平台"""
|
||||
# 解析消息链为 Discord 所需的对象
|
||||
@@ -98,6 +92,21 @@ class DiscordPlatformEvent(AstrMessageEvent):
|
||||
|
||||
await super().send(message)
|
||||
|
||||
async def send_streaming(
|
||||
self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
|
||||
):
|
||||
buffer = None
|
||||
async for chain in generator:
|
||||
if not buffer:
|
||||
buffer = chain
|
||||
else:
|
||||
buffer.chain.extend(chain.chain)
|
||||
if not buffer:
|
||||
return None
|
||||
buffer.squash_plain()
|
||||
await self.send(buffer)
|
||||
return await super().send_streaming(generator, use_fallback)
|
||||
|
||||
async def _get_channel(self) -> discord.abc.Messageable | None:
|
||||
"""获取当前事件对应的频道对象"""
|
||||
try:
|
||||
|
||||
@@ -163,6 +163,9 @@ class WebChatAdapter(Platform):
|
||||
_, _, payload = message.raw_message # type: ignore
|
||||
message_event.set_extra("selected_provider", payload.get("selected_provider"))
|
||||
message_event.set_extra("selected_model", payload.get("selected_model"))
|
||||
message_event.set_extra(
|
||||
"enable_streaming", payload.get("enable_streaming", True)
|
||||
)
|
||||
|
||||
self.commit_event(message_event)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import io
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiohttp
|
||||
@@ -50,6 +51,21 @@ class WeChatPadProMessageEvent(AstrMessageEvent):
|
||||
await self._send_voice(session, comp)
|
||||
await super().send(message)
|
||||
|
||||
async def send_streaming(
|
||||
self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
|
||||
):
|
||||
buffer = None
|
||||
async for chain in generator:
|
||||
if not buffer:
|
||||
buffer = chain
|
||||
else:
|
||||
buffer.chain.extend(chain.chain)
|
||||
if not buffer:
|
||||
return None
|
||||
buffer.squash_plain()
|
||||
await self.send(buffer)
|
||||
return await super().send_streaming(generator, use_fallback)
|
||||
|
||||
async def _send_image(self, session: aiohttp.ClientSession, comp: Image):
|
||||
b64 = await comp.convert_to_base64()
|
||||
raw = self._validate_base64(b64)
|
||||
|
||||
@@ -43,14 +43,23 @@ class ProviderOpenAIOfficial(Provider):
|
||||
self.api_keys: list = super().get_keys()
|
||||
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None
|
||||
self.timeout = provider_config.get("timeout", 120)
|
||||
self.custom_headers = provider_config.get("custom_headers", {})
|
||||
if isinstance(self.timeout, str):
|
||||
self.timeout = int(self.timeout)
|
||||
|
||||
if not isinstance(self.custom_headers, dict) or not self.custom_headers:
|
||||
self.custom_headers = None
|
||||
else:
|
||||
for key in self.custom_headers:
|
||||
self.custom_headers[key] = str(self.custom_headers[key])
|
||||
|
||||
# 适配 azure openai #332
|
||||
if "api_version" in provider_config:
|
||||
# 使用 azure api
|
||||
self.client = AsyncAzureOpenAI(
|
||||
api_key=self.chosen_api_key,
|
||||
api_version=provider_config.get("api_version", None),
|
||||
default_headers=self.custom_headers,
|
||||
base_url=provider_config.get("api_base", ""),
|
||||
timeout=self.timeout,
|
||||
)
|
||||
@@ -59,6 +68,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=self.chosen_api_key,
|
||||
base_url=provider_config.get("api_base", None),
|
||||
default_headers=self.custom_headers,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ class ChatRoute(Route):
|
||||
audio_url = post_data.get("audio_url")
|
||||
selected_provider = post_data.get("selected_provider")
|
||||
selected_model = post_data.get("selected_model")
|
||||
enable_streaming = post_data.get("enable_streaming", True) # 默认为 True
|
||||
|
||||
if not message and not image_url and not audio_url:
|
||||
return (
|
||||
Response()
|
||||
@@ -224,6 +226,7 @@ class ChatRoute(Route):
|
||||
"audio_url": audio_url,
|
||||
"selected_provider": selected_provider,
|
||||
"selected_model": selected_model,
|
||||
"enable_streaming": enable_streaming,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -296,7 +296,15 @@ class ToolsRoute(Route):
|
||||
"""获取所有注册的工具列表"""
|
||||
try:
|
||||
tools = self.tool_mgr.func_list
|
||||
tools_dict = [tool.__dict__() for tool in tools]
|
||||
tools_dict = [
|
||||
{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"parameters": tool.parameters,
|
||||
"active": tool.active,
|
||||
}
|
||||
for tool in tools
|
||||
]
|
||||
return Response().ok(data=tools_dict).__dict__
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
134
build_nuitka.py
Normal file
@@ -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)
|
||||
134
build_pyinstaller.py
Normal file
@@ -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)
|
||||
5
changelogs/v4.5.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## What's Changed
|
||||
|
||||
> hotfix version of 4.5.2
|
||||
|
||||
1. 修复:修正 `get_tool_list` 方法中工具字典推导式的错误导致的 WebUI MCP 页面工具列表无法显示的问题。
|
||||
5
changelogs/v4.5.4.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## What's Changed
|
||||
|
||||
1. 修复:Docker 镜像部分依赖问题导致某些情况下无法启动容器的问题;
|
||||
2. 优化:插件卡片样式
|
||||
3. 修复:部分情况下 Windows 一键启动部署时,更新 / 部署失败的问题;
|
||||
3
changelogs/v4.5.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## What's Changed
|
||||
|
||||
1. 修复:部署失败
|
||||
3
changelogs/v4.5.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## What's Changed
|
||||
|
||||
1. 修复:构建失败
|
||||
225
dashboard/TAURI_README.md
Normal file
@@ -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/",
|
||||
"preview": "vite preview --port 5050",
|
||||
"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": {
|
||||
"@guolao/vue-monaco-editor": "^1.5.4",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@tiptap/starter-kit": "2.1.7",
|
||||
"@tiptap/vue-3": "2.1.7",
|
||||
"apexcharts": "3.42.0",
|
||||
@@ -43,6 +47,7 @@
|
||||
"devDependencies": {
|
||||
"@mdi/font": "7.2.96",
|
||||
"@rushstack/eslint-patch": "1.3.3",
|
||||
"@tauri-apps/cli": "^2.9.4",
|
||||
"@types/chance": "1.1.3",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^20.5.7",
|
||||
|
||||
4509
dashboard/pnpm-lock.yaml
generated
Normal file
3
dashboard/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Tauri specific
|
||||
src-tauri/target/
|
||||
src-tauri/WixTools/
|
||||
4692
dashboard/src-tauri/Cargo.lock
generated
Normal file
27
dashboard/src-tauri/Cargo.toml
Normal file
@@ -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" ]
|
||||
3
dashboard/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
1
dashboard/src-tauri/gen/schemas/acl-manifests.json
Normal file
1
dashboard/src-tauri/gen/schemas/capabilities.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
2244
dashboard/src-tauri/gen/schemas/desktop-schema.json
Normal file
2244
dashboard/src-tauri/gen/schemas/macOS-schema.json
Normal file
BIN
dashboard/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
dashboard/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
dashboard/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
dashboard/src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
dashboard/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
dashboard/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
dashboard/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
dashboard/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
dashboard/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
dashboard/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
dashboard/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
dashboard/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
dashboard/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
dashboard/src-tauri/icons/StoreLogo.png
Normal file
|
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>
|
||||
BIN
dashboard/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
BIN
dashboard/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
dashboard/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
BIN
dashboard/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
BIN
dashboard/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
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>
|
||||
BIN
dashboard/src-tauri/icons/icon.icns
Normal file
BIN
dashboard/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
dashboard/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 602 B |
BIN
dashboard/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
dashboard/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
104
dashboard/src-tauri/src/main.rs
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
53
dashboard/src-tauri/tauri.conf.json
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
1
dashboard/src/assets/images/icon-no-shadow.svg
Normal file
|
After Width: | Height: | Size: 58 KiB |
@@ -1,55 +1,70 @@
|
||||
<template>
|
||||
<v-card class="chat-page-card">
|
||||
<v-card class="chat-page-card" elevation="0" rounded="0">
|
||||
<v-card-text class="chat-page-container">
|
||||
<!-- 遮罩层 (手机端) -->
|
||||
<div class="mobile-overlay" v-if="isMobile && mobileMenuOpen" @click="closeMobileSidebar"></div>
|
||||
|
||||
<div class="chat-layout">
|
||||
<div class="sidebar-panel" :class="{ 'sidebar-collapsed': sidebarCollapsed }"
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f5f5f5' }"
|
||||
<div class="sidebar-panel"
|
||||
:class="{
|
||||
'sidebar-collapsed': sidebarCollapsed && !isMobile,
|
||||
'mobile-sidebar-open': isMobile && mobileMenuOpen,
|
||||
'mobile-sidebar': isMobile
|
||||
}"
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f1f4f9' }"
|
||||
@mouseenter="handleSidebarMouseEnter" @mouseleave="handleSidebarMouseLeave">
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center; padding: 16px; padding-bottom: 0px;"
|
||||
v-if="chatboxMode">
|
||||
<img width="50" src="@/assets/images/astrbot_logo_mini.webp" alt="AstrBot Logo">
|
||||
<img width="50" src="@/assets/images/icon-no-shadow.svg" alt="AstrBot Logo">
|
||||
<span v-if="!sidebarCollapsed"
|
||||
style="font-weight: 1000; font-size: 26px; margin-left: 8px;">AstrBot</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sidebar-collapse-btn-container">
|
||||
<div class="sidebar-collapse-btn-container" v-if="!isMobile">
|
||||
<v-btn icon class="sidebar-collapse-btn" @click="toggleSidebar" variant="text"
|
||||
color="deep-purple">
|
||||
<v-icon>{{ (sidebarCollapsed || (!sidebarCollapsed && sidebarHoverExpanded)) ?
|
||||
'mdi-chevron-right' : 'mdi-chevron-left' }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<!-- 手机端关闭按钮 -->
|
||||
<div class="sidebar-collapse-btn-container" v-if="isMobile">
|
||||
<v-btn icon class="sidebar-collapse-btn" @click="closeMobileSidebar" variant="text"
|
||||
color="deep-purple">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; padding-top: 8px;">
|
||||
<v-btn block variant="text" class="new-chat-btn" @click="newC" :disabled="!currCid"
|
||||
v-if="!sidebarCollapsed" prepend-icon="mdi-plus"
|
||||
v-if="!sidebarCollapsed || isMobile" prepend-icon="mdi-plus"
|
||||
style="background-color: transparent !important; border-radius: 4px;">{{
|
||||
tm('actions.newChat') }}</v-btn>
|
||||
<v-btn icon="mdi-plus" rounded="lg" @click="newC" :disabled="!currCid" v-if="sidebarCollapsed"
|
||||
<v-btn icon="mdi-plus" rounded="lg" @click="newC" :disabled="!currCid" v-if="sidebarCollapsed && !isMobile"
|
||||
elevation="0"></v-btn>
|
||||
</div>
|
||||
<div v-if="!sidebarCollapsed">
|
||||
<div v-if="!sidebarCollapsed || isMobile">
|
||||
<v-divider class="mx-4"></v-divider>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="overflow-y: auto; flex-grow: 1;" :class="{ 'fade-in': sidebarHoverExpanded }"
|
||||
v-if="!sidebarCollapsed">
|
||||
v-if="!sidebarCollapsed || isMobile">
|
||||
<v-card v-if="conversations.length > 0" flat style="background-color: transparent;">
|
||||
<v-list density="compact" nav class="conversation-list"
|
||||
style="background-color: transparent;" v-model:selected="selectedConversations"
|
||||
@update:selected="getConversationMessages">
|
||||
<v-list-item v-for="(item, i) in conversations" :key="item.cid" :value="item.cid"
|
||||
rounded="lg" class="conversation-item" active-color="secondary">
|
||||
<v-list-item-title v-if="!sidebarCollapsed" class="conversation-title">{{ item.title
|
||||
<v-list-item-title v-if="!sidebarCollapsed || isMobile" class="conversation-title">{{ item.title
|
||||
|| tm('conversation.newConversation') }}</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="!sidebarCollapsed" class="timestamp">{{
|
||||
<v-list-item-subtitle v-if="!sidebarCollapsed || isMobile" class="timestamp">{{
|
||||
formatDate(item.updated_at)
|
||||
}}</v-list-item-subtitle>
|
||||
}}</v-list-item-subtitle>
|
||||
|
||||
<template v-if="!sidebarCollapsed" v-slot:append>
|
||||
<template v-if="!sidebarCollapsed || isMobile" v-slot:append>
|
||||
<div class="conversation-actions">
|
||||
<v-btn icon="mdi-pencil" size="x-small" variant="text"
|
||||
class="edit-title-btn"
|
||||
@@ -66,7 +81,7 @@
|
||||
<v-fade-transition>
|
||||
<div class="no-conversations" v-if="conversations.length === 0">
|
||||
<v-icon icon="mdi-message-text-outline" size="large" color="grey-lighten-1"></v-icon>
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || sidebarHoverExpanded">
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || sidebarHoverExpanded || isMobile">
|
||||
{{ tm('conversation.noHistory') }}</div>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
@@ -78,12 +93,17 @@
|
||||
<div class="chat-content-panel">
|
||||
|
||||
<div class="conversation-header fade-in">
|
||||
<div v-if="currCid && getCurrentConversation">
|
||||
<!-- 手机端菜单按钮 -->
|
||||
<v-btn icon class="mobile-menu-btn" @click="toggleMobileSidebar" v-if="isMobile" variant="text">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <div v-if="currCid && getCurrentConversation">
|
||||
<h3
|
||||
style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
{{ getCurrentConversation.title || tm('conversation.newConversation') }}</h3>
|
||||
<span style="font-size: 12px;">{{ formatDate(getCurrentConversation.updated_at) }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="conversation-header-actions">
|
||||
<!-- router 推送到 /chatbox -->
|
||||
<v-tooltip :text="tm('actions.fullscreen')" v-if="!chatboxMode">
|
||||
@@ -117,7 +137,6 @@
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<v-divider v-if="currCid && getCurrentConversation" class="conversation-divider"></v-divider>
|
||||
|
||||
<MessageList v-if="messages && messages.length > 0" :messages="messages" :isDark="isDark"
|
||||
:isStreaming="isStreaming || isConvRunning" @openImagePreview="openImagePreview"
|
||||
@@ -146,17 +165,30 @@
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="input-area fade-in">
|
||||
<div
|
||||
<div class="input-container"
|
||||
style="width: 85%; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 24px;">
|
||||
<textarea id="input-field" v-model="prompt" @keydown="handleInputKeyDown"
|
||||
:disabled="isStreaming" @click:clear="clearMessage"
|
||||
placeholder="Ask AstrBot..."
|
||||
:disabled="isStreaming" @click:clear="clearMessage" placeholder="Ask AstrBot..."
|
||||
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 8px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
||||
<div
|
||||
style="display: flex; justify-content: space-between; align-items: center; padding: 0px 8px;">
|
||||
<div style="display: flex; justify-content: flex-start; margin-top: 4px;">
|
||||
style="display: flex; justify-content: space-between; align-items: center; padding: 0px 12px;">
|
||||
<div
|
||||
style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
|
||||
<!-- 选择提供商和模型 -->
|
||||
<ProviderModelSelector ref="providerModelSelector" />
|
||||
<!-- 流式响应开关 -->
|
||||
<v-tooltip
|
||||
:text="enableStreaming ? tm('streaming.enabled') : tm('streaming.disabled')"
|
||||
location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-chip v-bind="props" @click="toggleStreaming" size="x-small"
|
||||
class="streaming-toggle-chip">
|
||||
<v-icon start :icon="enableStreaming ? 'mdi-flash' : 'mdi-flash-off'"
|
||||
size="small"></v-icon>
|
||||
{{ enableStreaming ? tm('streaming.on') : tm('streaming.off') }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center;">
|
||||
@@ -175,7 +207,6 @@
|
||||
class="send-btn" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 附件预览区 -->
|
||||
@@ -242,6 +273,7 @@ import ProviderModelSelector from '@/components/chat/ProviderModelSelector.vue';
|
||||
import MessageList from '@/components/chat/MessageList.vue';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import { useToast } from '@/utils/toast';
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
export default {
|
||||
name: 'ChatPage',
|
||||
@@ -258,10 +290,12 @@ export default {
|
||||
}, setup() {
|
||||
const { t } = useI18n();
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
const theme = useTheme();
|
||||
|
||||
return {
|
||||
t,
|
||||
tm,
|
||||
theme,
|
||||
router,
|
||||
ref
|
||||
};
|
||||
@@ -287,7 +321,7 @@ export default {
|
||||
// Ctrl键长按相关变量
|
||||
ctrlKeyDown: false,
|
||||
ctrlKeyTimer: null,
|
||||
ctrlKeyLongPressThreshold: 300, // 长按阈值,单位毫秒
|
||||
ctrlKeyLongPressThreshold: 300, // 长按阈值,单位毫秒
|
||||
|
||||
mediaCache: {}, // Add a cache to store media blobs
|
||||
|
||||
@@ -313,6 +347,13 @@ export default {
|
||||
|
||||
isToastedRunningInfo: false, // To avoid multiple toasts
|
||||
activeSSECount: 0, // Track number of active SSE connections
|
||||
|
||||
// 流式响应开关
|
||||
enableStreaming: true, // 默认开启流式响应
|
||||
|
||||
// 手机端相关变量
|
||||
isMobile: false,
|
||||
mobileMenuOpen: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -391,6 +432,18 @@ export default {
|
||||
this.sidebarCollapsed = true; // 默认折叠状态
|
||||
}
|
||||
|
||||
// 从 localStorage 读取流式响应开关状态,默认为 true(开启)
|
||||
const savedStreamingState = localStorage.getItem('enableStreaming');
|
||||
if (savedStreamingState !== null) {
|
||||
this.enableStreaming = JSON.parse(savedStreamingState);
|
||||
} else {
|
||||
this.enableStreaming = true; // 默认开启
|
||||
}
|
||||
|
||||
// 检测是否为手机端
|
||||
this.checkMobile();
|
||||
window.addEventListener('resize', this.checkMobile);
|
||||
|
||||
// 设置输入框标签
|
||||
this.inputFieldLabel = this.tm('input.chatPrompt');
|
||||
this.getConversations();
|
||||
@@ -413,6 +466,9 @@ export default {
|
||||
beforeUnmount() {
|
||||
// 移除keyup事件监听
|
||||
document.removeEventListener('keyup', this.handleInputKeyUp);
|
||||
|
||||
// 移除resize事件监听
|
||||
window.removeEventListener('resize', this.checkMobile);
|
||||
|
||||
// 清除悬停定时器
|
||||
if (this.sidebarHoverTimer) {
|
||||
@@ -427,6 +483,28 @@ export default {
|
||||
const customizer = useCustomizerStore();
|
||||
const newTheme = customizer.uiTheme === 'PurpleTheme' ? 'PurpleThemeDark' : 'PurpleTheme';
|
||||
customizer.SET_UI_THEME(newTheme);
|
||||
this.theme.global.name.value = newTheme;
|
||||
},
|
||||
// 检测是否为手机端
|
||||
checkMobile() {
|
||||
this.isMobile = window.innerWidth <= 768;
|
||||
// 如果切换到桌面端,关闭手机菜单
|
||||
if (!this.isMobile) {
|
||||
this.mobileMenuOpen = false;
|
||||
}
|
||||
},
|
||||
// 切换手机端菜单
|
||||
toggleMobileSidebar() {
|
||||
this.mobileMenuOpen = !this.mobileMenuOpen;
|
||||
},
|
||||
// 关闭手机端菜单
|
||||
closeMobileSidebar() {
|
||||
this.mobileMenuOpen = false;
|
||||
},
|
||||
// 切换流式响应
|
||||
toggleStreaming() {
|
||||
this.enableStreaming = !this.enableStreaming;
|
||||
localStorage.setItem('enableStreaming', JSON.stringify(this.enableStreaming));
|
||||
},
|
||||
// 切换侧边栏折叠状态
|
||||
toggleSidebar() {
|
||||
@@ -441,7 +519,7 @@ export default {
|
||||
|
||||
// 侧边栏鼠标悬停处理
|
||||
handleSidebarMouseEnter() {
|
||||
if (!this.sidebarCollapsed) return;
|
||||
if (!this.sidebarCollapsed || this.isMobile) return;
|
||||
|
||||
this.sidebarHovered = true;
|
||||
|
||||
@@ -668,6 +746,11 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 手机端关闭侧边栏
|
||||
if (this.isMobile) {
|
||||
this.closeMobileSidebar();
|
||||
}
|
||||
|
||||
axios.get('/api/chat/get_conversation?conversation_id=' + cid[0]).then(async response => {
|
||||
this.currCid = cid[0];
|
||||
// Update the selected conversation in the sidebar
|
||||
@@ -748,6 +831,10 @@ export default {
|
||||
this.currCid = '';
|
||||
this.selectedConversations = []; // 清除选中状态
|
||||
this.messages = [];
|
||||
// 手机端关闭侧边栏
|
||||
if (this.isMobile) {
|
||||
this.closeMobileSidebar();
|
||||
}
|
||||
if (this.$route.path.startsWith('/chatbox')) {
|
||||
this.$router.push('/chatbox');
|
||||
} else {
|
||||
@@ -867,7 +954,8 @@ export default {
|
||||
image_url: imageNamesToSend,
|
||||
audio_url: audioNameToSend ? [audioNameToSend] : [],
|
||||
selected_provider: selectedProviderId,
|
||||
selected_model: selectedModelName
|
||||
selected_model: selectedModelName,
|
||||
enable_streaming: this.enableStreaming
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1101,6 +1189,17 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 流式响应开关芯片样式 */
|
||||
.streaming-toggle-chip {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.streaming-toggle-chip:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 28px;
|
||||
margin-bottom: 16px;
|
||||
@@ -1141,7 +1240,6 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1166,7 +1264,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.04);
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
@@ -1188,6 +1286,77 @@ export default {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 手机端菜单按钮 */
|
||||
.mobile-menu-btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 手机端遮罩层 */
|
||||
.mobile-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
/* 手机端侧边栏 */
|
||||
.mobile-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
max-width: 280px !important;
|
||||
min-width: 280px !important;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mobile-sidebar-open {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
|
||||
/* 手机端样式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar-panel:not(.mobile-sidebar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-content-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 手机端去掉容器padding */
|
||||
.chat-page-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 手机端输入区域样式 */
|
||||
.input-area {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
#input-field {
|
||||
border-radius: 0 !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 侧边栏折叠按钮 */
|
||||
.sidebar-collapse-btn-container {
|
||||
margin: 16px;
|
||||
@@ -1267,25 +1436,12 @@ export default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-chips {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.status-chips .v-chip {
|
||||
.v-chip {
|
||||
flex: 1 1 0;
|
||||
justify-content: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.status-chip {
|
||||
font-size: 12px;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
.no-conversations {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<v-avatar class="bot-avatar" size="36">
|
||||
<v-progress-circular :index="index" v-if="isStreaming && index === messages.length - 1" indeterminate size="28"
|
||||
width="2"></v-progress-circular>
|
||||
<span v-else-if="messages[index - 1]?.content.type !== 'bot'" class="text-h2">✨</span>
|
||||
<v-icon v-else-if="messages[index - 1]?.content.type !== 'bot'" size="64" color="#8fb6d2">mdi-star-four-points-small</v-icon>
|
||||
</v-avatar>
|
||||
<div class="bot-message-content">
|
||||
<div class="message-bubble bot-bubble">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 选择提供商和模型按钮 -->
|
||||
<v-btn class="text-none" variant="tonal" rounded="xl" size="small"
|
||||
<v-chip class="text-none" variant="tonal" size="x-small"
|
||||
v-if="selectedProviderId && selectedModelName" @click="openDialog">
|
||||
{{ selectedProviderId }} / {{ selectedModelName }}
|
||||
</v-btn>
|
||||
<v-btn variant="tonal" rounded="xl" size="small" v-else @click="openDialog">
|
||||
</v-chip>
|
||||
<v-chip variant="tonal" rounded="xl" size="x-small" v-else @click="openDialog">
|
||||
选择模型
|
||||
</v-btn>
|
||||
</v-chip>
|
||||
|
||||
<!-- 选择提供商和模型对话框 -->
|
||||
<v-dialog v-model="showDialog" max-width="800" persistent>
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span v-if="!modelValue || (Array.isArray(modelValue) && modelValue.length === 0)"
|
||||
style="color: rgb(var(--v-theme-primaryText));">
|
||||
未选择
|
||||
</span>
|
||||
<div v-else class="d-flex flex-wrap gap-1">
|
||||
<v-chip
|
||||
v-for="name in modelValue"
|
||||
:key="name"
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
closable
|
||||
@click:close="removeKnowledgeBase(name)">
|
||||
{{ name }}
|
||||
</v-chip>
|
||||
<div class="d-flex align-center justify-space-between" style="gap: 8px;">
|
||||
<div style="flex: 1; min-width: 0; overflow: hidden;">
|
||||
<span v-if="!modelValue || (Array.isArray(modelValue) && modelValue.length === 0)"
|
||||
style="color: rgb(var(--v-theme-primaryText));">
|
||||
未选择
|
||||
</span>
|
||||
<div v-else class="d-flex flex-wrap gap-1">
|
||||
<v-chip
|
||||
v-for="name in modelValue"
|
||||
:key="name"
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
closable
|
||||
@click:close="removeKnowledgeBase(name)"
|
||||
style="max-width: 100%;">
|
||||
<span class="text-truncate" style="max-width: 200px;">{{ name }}</span>
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn size="small" color="primary" variant="tonal" @click="openDialog">
|
||||
<v-btn size="small" color="primary" variant="tonal" @click="openDialog" style="flex-shrink: 0;">
|
||||
{{ buttonText }}
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -220,4 +223,11 @@ function goToKnowledgeBasePage() {
|
||||
.gap-1 {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,10 +70,6 @@ const formatTitle = (title: string) => {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.logo-image img:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"login": "Login",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"defaultHint": "Default username and password: astrbot",
|
||||
"logo": {
|
||||
"title": "AstrBot Dashboard",
|
||||
"subtitle": "Welcome"
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
"voiceRecord": "Record Voice",
|
||||
"pasteImage": "Paste Image"
|
||||
},
|
||||
"streaming": {
|
||||
"enabled": "Streaming enabled",
|
||||
"disabled": "Streaming disabled",
|
||||
"on": "Stream",
|
||||
"off": "Normal"
|
||||
},
|
||||
"connection": {
|
||||
"title": "Connection Status Notice",
|
||||
"message": "The system detected that the chat connection needs to be re-established.",
|
||||
|
||||
@@ -79,6 +79,14 @@
|
||||
"devDocs": "Extension Development Docs",
|
||||
"submitRepo": "Submit Extension Repository"
|
||||
},
|
||||
"sort": {
|
||||
"default": "Default",
|
||||
"stars": "Stars",
|
||||
"author": "Author",
|
||||
"updated": "Last Updated",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending"
|
||||
},
|
||||
"tags": {
|
||||
"danger": "Danger"
|
||||
},
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
"login": "登录",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"defaultHint": "默认账户和密码均为:astrbot",
|
||||
"logo": {
|
||||
"title": "AstrBot 仪表盘",
|
||||
"title": "AstrBot WebUI",
|
||||
"subtitle": "欢迎使用"
|
||||
},
|
||||
"theme": {
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
"voiceRecord": "录制语音",
|
||||
"pasteImage": "粘贴图片"
|
||||
},
|
||||
"streaming": {
|
||||
"enabled": "流式响应已开启",
|
||||
"disabled": "流式响应已关闭",
|
||||
"on": "流式",
|
||||
"off": "普通"
|
||||
},
|
||||
"connection": {
|
||||
"title": "连接状态提醒",
|
||||
"message": "系统检测到聊天连接需要重新建立。",
|
||||
|
||||
@@ -79,6 +79,14 @@
|
||||
"devDocs": "插件开发文档",
|
||||
"submitRepo": "提交插件仓库"
|
||||
},
|
||||
"sort": {
|
||||
"default": "默认排序",
|
||||
"stars": "Star数",
|
||||
"author": "作者名",
|
||||
"updated": "更新时间",
|
||||
"ascending": "升序",
|
||||
"descending": "降序"
|
||||
},
|
||||
"tags": {
|
||||
"danger": "危险"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { RouterView, useRoute } from 'vue-router';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import axios from 'axios';
|
||||
import VerticalSidebarVue from './vertical-sidebar/VerticalSidebar.vue';
|
||||
import VerticalHeaderVue from './vertical-header/VerticalHeader.vue';
|
||||
@@ -8,6 +8,12 @@ import MigrationDialog from '@/components/shared/MigrationDialog.vue';
|
||||
import { useCustomizerStore } from '@/stores/customizer';
|
||||
|
||||
const customizer = useCustomizerStore();
|
||||
const route = useRoute();
|
||||
|
||||
// 计算是否在聊天页面(非全屏模式)
|
||||
const isChatPage = computed(() => {
|
||||
return route.path.startsWith('/chat');
|
||||
});
|
||||
const migrationDialog = ref<InstanceType<typeof MigrationDialog> | null>(null);
|
||||
|
||||
// 检查是否需要迁移
|
||||
@@ -45,7 +51,10 @@ onMounted(() => {
|
||||
<VerticalHeaderVue />
|
||||
<VerticalSidebarVue />
|
||||
<v-main>
|
||||
<v-container fluid class="page-wrapper" style="height: calc(100% - 8px)">
|
||||
<v-container fluid class="page-wrapper" :style="{
|
||||
height: 'calc(100% - 8px)',
|
||||
padding: isChatPage ? '0' : undefined
|
||||
}">
|
||||
<div style="height: 100%;">
|
||||
<RouterView />
|
||||
</div>
|
||||
|
||||