merge: 合并 main 分支到 docker

This commit is contained in:
root
2026-02-02 20:31:42 +08:00
parent 6cfc7051c4
commit bd4c2bacbc
10 changed files with 1026 additions and 1054 deletions

View File

@@ -1,50 +1,50 @@
# Git
.git
.gitignore
# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
.venv
venv
ENV
# IDE
.idea
.vscode
*.swp
*.swo
# Node
webui/node_modules
webui/.vite
# Build artifacts (前端构建产物在 Docker 中重新生成)
static/admin
# 配置和敏感文件
.env
.env.*
config.json
# 日志和临时文件
*.log
logs/
tmp/
temp/
# 测试
tests/
*.test.py
# 文档和截图
*.md
截图/
docs/
# Claude Code
.claude/
CLAUDE*.md
# Git
.git
.gitignore
# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
.venv
venv
ENV
# IDE
.idea
.vscode
*.swp
*.swo
# Node
webui/node_modules
webui/.vite
# Build artifacts (前端构建产物在 Docker 中重新生成)
static/admin
# 配置和敏感文件
.env
.env.*
config.json
# 日志和临时文件
*.log
logs/
tmp/
temp/
# 测试
tests/
*.test.py
# 文档和截图
*.md
截图/
docs/
# Claude Code
.claude/
CLAUDE*.md

View File

@@ -1,24 +1,20 @@
#### 💻 变更类型 | Change Type
<!-- For change type, change [ ] to [x]. -->
- [ ] ✨ feat
- [ ] 🐛 fix
- [ ] ♻️ refactor
- [ ] 💄 style
- [ ] 👷 build
- [ ] ⚡️ perf
- [ ] 📝 docs
- [ ] 🔨 chore
#### 🔀 变更说明 | Description of Change
<!-- Thank you for your Pull Request. Please provide a description above. -->
#### 📝 补充信息 | Additional Information
<!-- Add any other context about the Pull Request here. -->
---
> 💡 **提示**:如果修改了 `webui/` 目录下的文件PR 合并后 CI 会自动构建并提交产物,无需手动构建。
#### 💻 变更类型 | Change Type
<!-- For change type, change [ ] to [x]. -->
- [ ] ✨ feat
- [ ] 🐛 fix
- [ ] ♻️ refactor
- [ ] 💄 style
- [ ] 👷 build
- [ ] ⚡️ perf
- [ ] 📝 docs
- [ ] 🔨 chore
#### 🔀 变更说明 | Description of Change
<!-- Thank you for your Pull Request. Please provide a description above. -->
#### 📝 补充信息 | Additional Information
<!-- Add any other context about the Pull Request here. -->

View File

@@ -1,128 +1,128 @@
name: Release to Aliyun CR
on:
workflow_dispatch:
inputs:
version_type:
description: '版本类型'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get current version
id: get_version
run: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
TAG_VERSION=${LATEST_TAG#v}
if [ -f VERSION ]; then
FILE_VERSION=$(cat VERSION | tr -d '[:space:]')
else
FILE_VERSION="0.0.0"
fi
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
if version_gt "$FILE_VERSION" "$TAG_VERSION"; then
VERSION="$FILE_VERSION"
else
VERSION="$TAG_VERSION"
fi
echo "Current version: $VERSION"
echo "current_version=$VERSION" >> $GITHUB_OUTPUT
- name: Calculate next version
id: next_version
env:
VERSION_TYPE: ${{ github.event.inputs.version_type }}
run: |
VERSION="${{ steps.get_version.outputs.current_version }}"
BASE_VERSION=$(echo "$VERSION" | sed 's/-.*$//')
IFS='.' read -r -a version_parts <<< "$BASE_VERSION"
MAJOR="${version_parts[0]:-0}"
MINOR="${version_parts[1]:-0}"
PATCH="${version_parts[2]:-0}"
case "$VERSION_TYPE" in
major)
NEW_VERSION="$((MAJOR + 1)).0.0"
;;
minor)
NEW_VERSION="${MAJOR}.$((MINOR + 1)).0"
;;
*)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
;;
esac
echo "New version: $NEW_VERSION"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "new_tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Update VERSION file
run: |
echo "${{ steps.next_version.outputs.new_version }}" > VERSION
- name: Commit VERSION and create tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add VERSION
if ! git diff --cached --quiet; then
git commit -m "chore: bump version to ${{ steps.next_version.outputs.new_tag }} [skip ci]"
fi
NEW_TAG="${{ steps.next_version.outputs.new_tag }}"
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin HEAD:main "$NEW_TAG"
# Docker 构建并推送到阿里云
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Aliyun Container Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_REGISTRY_USER }}
password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:${{ steps.next_version.outputs.new_tag }}
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:${{ steps.next_version.outputs.new_version }}
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:latest
labels: |
org.opencontainers.image.version=${{ steps.next_version.outputs.new_version }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
name: Release to Aliyun CR
on:
workflow_dispatch:
inputs:
version_type:
description: '版本类型'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get current version
id: get_version
run: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
TAG_VERSION=${LATEST_TAG#v}
if [ -f VERSION ]; then
FILE_VERSION=$(cat VERSION | tr -d '[:space:]')
else
FILE_VERSION="0.0.0"
fi
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
if version_gt "$FILE_VERSION" "$TAG_VERSION"; then
VERSION="$FILE_VERSION"
else
VERSION="$TAG_VERSION"
fi
echo "Current version: $VERSION"
echo "current_version=$VERSION" >> $GITHUB_OUTPUT
- name: Calculate next version
id: next_version
env:
VERSION_TYPE: ${{ github.event.inputs.version_type }}
run: |
VERSION="${{ steps.get_version.outputs.current_version }}"
BASE_VERSION=$(echo "$VERSION" | sed 's/-.*$//')
IFS='.' read -r -a version_parts <<< "$BASE_VERSION"
MAJOR="${version_parts[0]:-0}"
MINOR="${version_parts[1]:-0}"
PATCH="${version_parts[2]:-0}"
case "$VERSION_TYPE" in
major)
NEW_VERSION="$((MAJOR + 1)).0.0"
;;
minor)
NEW_VERSION="${MAJOR}.$((MINOR + 1)).0"
;;
*)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
;;
esac
echo "New version: $NEW_VERSION"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "new_tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Update VERSION file
run: |
echo "${{ steps.next_version.outputs.new_version }}" > VERSION
- name: Commit VERSION and create tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add VERSION
if ! git diff --cached --quiet; then
git commit -m "chore: bump version to ${{ steps.next_version.outputs.new_tag }} [skip ci]"
fi
NEW_TAG="${{ steps.next_version.outputs.new_tag }}"
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin HEAD:main "$NEW_TAG"
# Docker 构建并推送到阿里云
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Aliyun Container Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_REGISTRY_USER }}
password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:${{ steps.next_version.outputs.new_tag }}
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:${{ steps.next_version.outputs.new_version }}
${{ secrets.ALIYUN_REGISTRY }}/${{ secrets.ALIYUN_REGISTRY_NAMESPACE }}/ds2api:latest
labels: |
org.opencontainers.image.version=${{ steps.next_version.outputs.new_version }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

163
.gitignore vendored
View File

@@ -1,82 +1,81 @@
*.bak
config.json
.env
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
ENV/
env/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
logs/
uvicorn.log
# Vercel
.vercel
# Node.js / Frontend
node_modules/
webui/node_modules/
webui/dist/
.npm
.pnpm-store/
# 保留 webui/package-lock.json 用于 CI 缓存
# package-lock.json # 如果有根目录的可以忽略
yarn.lock
pnpm-lock.yaml
# Build artifacts
*.tsbuildinfo
.cache/
.parcel-cache/
# Environment
.env.local
.env.*.local
# Testing
.coverage
htmlcov/
.pytest_cache/
.tox/
# Misc
*.pyc
*.pyo
.git/
Thumbs.db
*.bak
config.json
.env
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
ENV/
env/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
logs/
uvicorn.log
# Vercel
.vercel
# Node.js / Frontend
node_modules/
webui/node_modules/
webui/dist/
.npm
.pnpm-store/
package-lock.json
yarn.lock
pnpm-lock.yaml
# Build artifacts
*.tsbuildinfo
.cache/
.parcel-cache/
# Environment
.env.local
.env.*.local
# Testing
.coverage
htmlcov/
.pytest_cache/
.tox/
# Misc
*.pyc
*.pyo
.git/
Thumbs.db

659
DEPLOY.md
View File

@@ -1,340 +1,319 @@
# DS2API 部署指南
本文档详细介绍 DS2API 的各种部署方式。
---
## 目录
- [Vercel 部署(推荐)](#vercel-部署推荐)
- [本地开发](#本地开发)
- [生产环境部署](#生产环境部署)
- [常见问题](#常见问题)
---
## Vercel 部署(推荐)
### 一键部署
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api&env=DS2API_ADMIN_KEY&envDescription=管理面板访问密码(必填)&envLink=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api%23环境变量&project-name=ds2api&repository-name=ds2api)
### 部署步骤
1. **点击部署按钮**
- 登录你的 GitHub 账号
- 授权 Vercel 访问
2. **设置环境变量**
- `DS2API_ADMIN_KEY`: 管理面板密码(**必填**
3. **等待部署完成**
- Vercel 会自动构建并部署项目
- 部署完成后获得访问 URL
4. **配置账号**
- 访问 `https://your-project.vercel.app/admin`
- 输入管理密码登录
- 添加 DeepSeek 账号
- 设置自定义 API Key
5. **同步配置**
- 点击「同步到 Vercel」按钮
- 首次需要输入 Vercel Token 和 Project ID
- 同步成功后配置会持久化
### 获取 Vercel 凭证
**Vercel Token**:
1. 访问 https://vercel.com/account/tokens
2. 点击 "Create Token"
3. 设置名称和有效期
4. 复制生成的 Token
**Project ID**:
1. 进入 Vercel 项目页面
2. 点击 Settings -> General
3. 复制 "Project ID"
---
## 本地开发
### 环境要求
- Python 3.9+
- Node.js 18+ (WebUI 开发)
- pip
### 快速开始
```bash
# 1. 克隆项目
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. 安装 Python 依赖
pip install -r requirements.txt
# 3. 配置账号
cp config.example.json config.json
# 编辑 config.json填入 DeepSeek 账号信息
# 4. 启动服务
python dev.py
```
### 配置文件示例
```json
{
"keys": ["my-api-key-1", "my-api-key-2"],
"accounts": [
{
"email": "your-email@example.com",
"password": "your-password",
"token": ""
},
{
"mobile": "12345678901",
"password": "your-password",
"token": ""
}
]
}
```
**说明**
- `keys`: 自定义 API Key用于调用本服务的接口
- `accounts`: DeepSeek 网页版账号
- 支持 `email``mobile` 登录
- `token` 留空,系统会自动获取
### WebUI 开发
```bash
# 进入 WebUI 目录
cd webui
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
WebUI 开发服务器会启动在 `http://localhost:5173`,并自动代理 API 请求到后端 `http://localhost:5001`
### WebUI 构建
WebUI 构建产物位于 `static/admin/` 目录。
**自动构建(推荐)**
-`webui/` 目录下的文件变更并推送到 `main` 分支时GitHub Actions 会自动构建并提交产物
- PR 合并时会自动触发构建
**手动构建**
```bash
# 方式1使用脚本
./scripts/build-webui.sh
# 方式2直接执行
cd webui
npm install
npm run build
```
> **贡献者注意**:修改 WebUI 后无需手动构建CI 会自动处理。
---
## 生产环境部署
### 使用 systemd (Linux)
1. **创建服务文件**
```bash
sudo nano /etc/systemd/system/ds2api.service
```
```ini
[Unit]
Description=DS2API Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/ds2api
ExecStart=/usr/bin/python3 app.py
Restart=always
RestartSec=10
Environment=PORT=5001
Environment=DS2API_ADMIN_KEY=your-admin-key
[Install]
WantedBy=multi-user.target
```
2. **启动服务**
```bash
sudo systemctl daemon-reload
sudo systemctl enable ds2api
sudo systemctl start ds2api
```
3. **查看状态**
```bash
sudo systemctl status ds2api
sudo journalctl -u ds2api -f
```
### Nginx 反向代理
```nginx
server {
listen 80;
server_name api.yourdomain.com;
# SSL 配置(推荐)
# listen 443 ssl http2;
# ssl_certificate /path/to/cert.pem;
# ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1;
# 关闭缓冲,支持 SSE
proxy_buffering off;
proxy_cache off;
# 连接设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE 超时设置
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 分块传输
chunked_transfer_encoding on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
}
}
```
### Docker 部署(可选)
```dockerfile
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5001
CMD ["python", "app.py"]
```
```bash
# 构建镜像
docker build -t ds2api .
# 运行容器
docker run -d \
--name ds2api \
-p 5001:5001 \
-e DS2API_ADMIN_KEY=your-admin-key \
-e DS2API_CONFIG_JSON='{"keys":["api-key"],"accounts":[...]}' \
ds2api
```
### Docker Compose
```yaml
# docker-compose.yml
version: '3.8'
services:
ds2api:
build: .
ports:
- "5001:5001"
environment:
- DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY}
- DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
restart: unless-stopped
```
---
## 常见问题
### Q: 账号验证失败怎么办?
**A**: 检查以下几点:
1. 确认 DeepSeek 账号密码正确
2. 检查账号是否被封禁或需要验证
3. 尝试在浏览器中手动登录一次
4. 查看日志获取详细错误信息
### Q: 流式响应断开怎么办?
**A**:
1. 检查 Nginx/反向代理配置,确保关闭了 `proxy_buffering`
2. 增加 `proxy_read_timeout` 超时时间
3. 检查网络连接稳定性
### Q: Vercel 部署后配置丢失?
**A**:
1. 确保点击了「同步到 Vercel」按钮
2. 检查 Vercel Token 是否正确且未过期
3. 确认 Project ID 正确
### Q: 如何更新到新版本?
**本地部署**:
```bash
git pull origin main
pip install -r requirements.txt
# 重启服务
```
**Vercel 部署**:
- 项目会自动从 GitHub 同步更新
- 或在 Vercel 控制台手动触发重新部署
### Q: 如何查看日志?
**本地开发**:
```bash
# 设置日志级别
export LOG_LEVEL=DEBUG
python dev.py
```
**Vercel**:
- 访问 Vercel 控制台 -> 项目 -> Deployments -> Logs
### Q: Token 计数不准确?
**A**: DS2API 使用估算方式计算 token 数量(字符数 / 4与 OpenAI 官方的 tokenizer 可能有差异,仅供参考。
---
## 获取帮助
- **GitHub Issues**: https://github.com/CJackHwang/ds2api/issues
- **文档**: https://github.com/CJackHwang/ds2api
# DS2API 部署指南
本文档详细介绍 DS2API 的各种部署方式。
---
## 目录
- [Vercel 部署(推荐)](#vercel-部署推荐)
- [本地开发](#本地开发)
- [生产环境部署](#生产环境部署)
- [常见问题](#常见问题)
---
## Vercel 部署(推荐)
### 一键部署
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api&env=DS2API_ADMIN_KEY&envDescription=管理面板访问密码(必填)&envLink=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api%23环境变量&project-name=ds2api&repository-name=ds2api)
### 部署步骤
1. **点击部署按钮**
- 登录你的 GitHub 账号
- 授权 Vercel 访问
2. **设置环境变量**
- `DS2API_ADMIN_KEY`: 管理面板密码(**必填**
3. **等待部署完成**
- Vercel 会自动构建并部署项目
- 部署完成后获得访问 URL
4. **配置账号**
- 访问 `https://your-project.vercel.app/admin`
- 输入管理密码登录
- 添加 DeepSeek 账号
- 设置自定义 API Key
5. **同步配置**
- 点击「同步到 Vercel」按钮
- 首次需要输入 Vercel Token 和 Project ID
- 同步成功后配置会持久化
### 获取 Vercel 凭证
**Vercel Token**:
1. 访问 https://vercel.com/account/tokens
2. 点击 "Create Token"
3. 设置名称和有效期
4. 复制生成的 Token
**Project ID**:
1. 进入 Vercel 项目页面
2. 点击 Settings -> General
3. 复制 "Project ID"
---
## 本地开发
### 环境要求
- Python 3.9+
- Node.js 18+ (WebUI 开发)
- pip
### 快速开始
```bash
# 1. 克隆项目
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. 安装 Python 依赖
pip install -r requirements.txt
# 3. 配置账号
cp config.example.json config.json
# 编辑 config.json填入 DeepSeek 账号信息
# 4. 启动服务
python dev.py
```
### 配置文件示例
```json
{
"keys": ["my-api-key-1", "my-api-key-2"],
"accounts": [
{
"email": "your-email@example.com",
"password": "your-password",
"token": ""
},
{
"mobile": "12345678901",
"password": "your-password",
"token": ""
}
]
}
```
**说明**
- `keys`: 自定义 API Key用于调用本服务的接口
- `accounts`: DeepSeek 网页版账号
- 支持 `email``mobile` 登录
- `token` 留空,系统会自动获取
### WebUI 开发
```bash
# 进入 WebUI 目录
cd webui
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
WebUI 开发服务器会启动在 `http://localhost:5173`,并自动代理 API 请求到后端 `http://localhost:5001`
---
## 生产环境部署
### 使用 systemd (Linux)
1. **创建服务文件**
```bash
sudo nano /etc/systemd/system/ds2api.service
```
```ini
[Unit]
Description=DS2API Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/ds2api
ExecStart=/usr/bin/python3 app.py
Restart=always
RestartSec=10
Environment=PORT=5001
Environment=DS2API_ADMIN_KEY=your-admin-key
[Install]
WantedBy=multi-user.target
```
2. **启动服务**
```bash
sudo systemctl daemon-reload
sudo systemctl enable ds2api
sudo systemctl start ds2api
```
3. **查看状态**
```bash
sudo systemctl status ds2api
sudo journalctl -u ds2api -f
```
### Nginx 反向代理
```nginx
server {
listen 80;
server_name api.yourdomain.com;
# SSL 配置(推荐)
# listen 443 ssl http2;
# ssl_certificate /path/to/cert.pem;
# ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5001;
proxy_http_version 1.1;
# 关闭缓冲,支持 SSE
proxy_buffering off;
proxy_cache off;
# 连接设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE 超时设置
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 分块传输
chunked_transfer_encoding on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
}
}
```
### Docker 部署(可选)
```dockerfile
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5001
CMD ["python", "app.py"]
```
```bash
# 构建镜像
docker build -t ds2api .
# 运行容器
docker run -d \
--name ds2api \
-p 5001:5001 \
-e DS2API_ADMIN_KEY=your-admin-key \
-e DS2API_CONFIG_JSON='{"keys":["api-key"],"accounts":[...]}' \
ds2api
```
### Docker Compose
```yaml
# docker-compose.yml
version: '3.8'
services:
ds2api:
build: .
ports:
- "5001:5001"
environment:
- DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY}
- DS2API_CONFIG_JSON=${DS2API_CONFIG_JSON}
restart: unless-stopped
```
---
## 常见问题
### Q: 账号验证失败怎么办?
**A**: 检查以下几点:
1. 确认 DeepSeek 账号密码正确
2. 检查账号是否被封禁或需要验证
3. 尝试在浏览器中手动登录一次
4. 查看日志获取详细错误信息
### Q: 流式响应断开怎么办?
**A**:
1. 检查 Nginx/反向代理配置,确保关闭了 `proxy_buffering`
2. 增加 `proxy_read_timeout` 超时时间
3. 检查网络连接稳定性
### Q: Vercel 部署后配置丢失?
**A**:
1. 确保点击了「同步到 Vercel」按钮
2. 检查 Vercel Token 是否正确且未过期
3. 确认 Project ID 正确
### Q: 如何更新到新版本?
**本地部署**:
```bash
git pull origin main
pip install -r requirements.txt
# 重启服务
```
**Vercel 部署**:
- 项目会自动从 GitHub 同步更新
- 或在 Vercel 控制台手动触发重新部署
### Q: 如何查看日志?
**本地开发**:
```bash
# 设置日志级别
export LOG_LEVEL=DEBUG
python dev.py
```
**Vercel**:
- 访问 Vercel 控制台 -> 项目 -> Deployments -> Logs
### Q: Token 计数不准确?
**A**: DS2API 使用估算方式计算 token 数量(字符数 / 4与 OpenAI 官方的 tokenizer 可能有差异,仅供参考。
---
## 获取帮助
- **GitHub Issues**: https://github.com/CJackHwang/ds2api/issues
- **文档**: https://github.com/CJackHwang/ds2api

View File

@@ -1 +1 @@
0.1.0
0.1.0

View File

@@ -1,14 +1,14 @@
services:
ds2api:
image: crpi-cnazxqmg4avmg4fq.cn-beijing.personal.cr.aliyuncs.com/ronghuaxueleng/ds2api:latest
container_name: ds2api
restart: always
ports:
- "6011:5001"
volumes:
- ./config.json:/app/config.json # 配置文件
- ./.env:/app/.env # 环境变量
environment:
- TZ=Asia/Shanghai
- LOG_LEVEL=INFO
- DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY:-ds2api}
services:
ds2api:
image: crpi-cnazxqmg4avmg4fq.cn-beijing.personal.cr.aliyuncs.com/ronghuaxueleng/ds2api:latest
container_name: ds2api
restart: always
ports:
- "6011:5001"
volumes:
- ./config.json:/app/config.json # 配置文件
- ./.env:/app/.env # 环境变量
environment:
- TZ=Asia/Shanghai
- LOG_LEVEL=INFO
- DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY:-ds2api}

View File

@@ -1,70 +1,70 @@
# ========== 阶段1: 构建前端 ==========
FROM node:20-slim AS frontend-builder
WORKDIR /app/webui
# 复制前端依赖文件
COPY webui/package.json webui/package-lock.json ./
# 安装依赖
RUN npm ci
# 复制前端源码
COPY webui/ ./
# 构建前端
RUN npm run build
# ========== 阶段2: 构建后端 ==========
FROM python:3.11-slim
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Shanghai
# 安装系统依赖
# curl_cffi 需要 libcurl 和编译工具
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
libffi-dev \
libcurl4-openssl-dev \
libssl-dev \
curl \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
&& rm -rf /var/lib/apt/lists/*
# 复制并安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 复制后端代码
COPY app.py .
COPY core/ ./core/
COPY routes/ ./routes/
# 创建 templates 目录(预留扩展用)
RUN mkdir -p ./templates
# 复制 WASM 文件和 Tokenizer 相关文件
COPY sha3_wasm_bg.7b9ca65ddd.wasm ./
COPY tokenizer.json tokenizer_config.json ./
# 从前端构建阶段复制构建产物到 static/admin
COPY --from=frontend-builder /app/webui/dist ./static/admin
# 创建配置文件目录(运行时挂载)
RUN mkdir -p /app/data
# 暴露端口
EXPOSE 5001
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5001/ || exit 1
# 启动命令
CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5001"]
# ========== 阶段1: 构建前端 ==========
FROM node:20-slim AS frontend-builder
WORKDIR /app/webui
# 复制前端依赖文件
COPY webui/package.json webui/package-lock.json ./
# 安装依赖
RUN npm ci
# 复制前端源码
COPY webui/ ./
# 构建前端
RUN npm run build
# ========== 阶段2: 构建后端 ==========
FROM python:3.11-slim
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Shanghai
# 安装系统依赖
# curl_cffi 需要 libcurl 和编译工具
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
libffi-dev \
libcurl4-openssl-dev \
libssl-dev \
curl \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
&& rm -rf /var/lib/apt/lists/*
# 复制并安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 复制后端代码
COPY app.py .
COPY core/ ./core/
COPY routes/ ./routes/
# 创建 templates 目录(预留扩展用)
RUN mkdir -p ./templates
# 复制 WASM 文件和 Tokenizer 相关文件
COPY sha3_wasm_bg.7b9ca65ddd.wasm ./
COPY tokenizer.json tokenizer_config.json ./
# 从前端构建阶段复制构建产物到 static/admin
COPY --from=frontend-builder /app/webui/dist ./static/admin
# 创建配置文件目录(运行时挂载)
RUN mkdir -p /app/data
# 暴露端口
EXPOSE 5001
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5001/ || exit 1
# 启动命令
CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5001"]

View File

@@ -1,303 +1,301 @@
# -*- coding: utf-8 -*-
"""首页和 WebUI 路由"""
import os
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, FileResponse
from core.config import STATIC_ADMIN_DIR
router = APIRouter()
# 首页 HTML内嵌避免依赖模板目录
WELCOME_HTML = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DS2API - DeepSeek to OpenAI API</title>
<meta name="description" content="DS2API - 将 DeepSeek 网页版转换为 OpenAI 兼容 API">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E">
<style>
:root {
--primary: #f59e0b;
--primary-glow: rgba(245, 158, 11, 0.4);
--secondary: #ef4444;
--bg: #030712;
--card-bg: rgba(255, 255, 255, 0.03);
--card-border: rgba(255, 255, 255, 0.08);
--text-main: #f9fafb;
--text-dim: #9ca3af;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg);
color: var(--text-main);
min-height: 100vh;
overflow-x: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
/* Animated Background */
.bg-glow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background:
radial-gradient(circle at 20% 30%, rgba(245, 158, 11, 0.05) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(239, 68, 68, 0.05) 0%, transparent 40%);
}
.blob {
position: absolute;
width: 400px;
height: 400px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
filter: blur(80px);
opacity: 0.15;
border-radius: 50%;
z-index: -1;
animation: move 20s infinite alternate;
}
@keyframes move {
from { transform: translate(-10%, -10%) scale(1); }
to { transform: translate(10%, 10%) scale(1.1); }
}
.container {
width: 100%;
max-width: 900px;
padding: 2rem;
text-align: center;
}
.logo-section {
margin-bottom: 3rem;
animation: fadeInUp 0.8s ease-out;
}
.logo {
font-family: 'Orbitron', sans-serif;
font-size: clamp(3rem, 10vw, 5rem);
font-weight: 700;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -2px;
margin-bottom: 0.5rem;
display: inline-block;
}
.subtitle {
color: var(--text-dim);
font-size: 1.25rem;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 4rem;
flex-wrap: wrap;
animation: fadeInUp 0.8s ease-out 0.2s backwards;
}
.btn {
padding: 0.8rem 2rem;
border-radius: 12px;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
box-shadow: 0 4px 15px var(--primary-glow);
}
.btn-primary:hover {
transform: translateY(-3px) scale(1.02);
box-shadow: 0 8px 25px var(--primary-glow);
}
.btn-secondary {
background: var(--card-bg);
color: var(--text-main);
border: 1px solid var(--card-border);
backdrop-filter: blur(10px);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
animation: fadeInUp 0.8s ease-out 0.4s backwards;
}
.feature-card {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 1.5rem;
text-align: left;
transition: all 0.3s ease;
backdrop-filter: blur(8px);
}
.feature-card:hover {
border-color: rgba(245, 158, 11, 0.3);
background: rgba(255, 255, 255, 0.05);
transform: translateY(-5px);
}
.feature-icon {
font-size: 1.5rem;
margin-bottom: 1rem;
display: block;
}
.feature-card h3 {
font-size: 1.1rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.feature-card p {
color: var(--text-dim);
font-size: 0.9rem;
line-height: 1.5;
}
footer {
margin-top: 4rem;
padding: 2rem;
color: var(--text-dim);
font-size: 0.875rem;
animation: fadeInUp 0.8s ease-out 0.6s backwards;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 640px) {
.logo { font-size: 3.5rem; }
.container { padding: 1.5rem; }
}
</style>
</head>
<body>
<div class="bg-glow"></div>
<div class="blob" style="top: 10%; left: 15%;"></div>
<div class="blob" style="bottom: 10%; right: 15%; animation-delay: -5s;"></div>
<div class="container">
<header class="logo-section">
<div class="logo">DS2API</div>
<p class="subtitle">DeepSeek to OpenAI & Claude Compatible API Interface</p>
</header>
<div class="actions">
<a href="/admin" class="btn btn-primary">
<span>🎛️</span> 管理面板
</a>
<a href="/v1/models" class="btn btn-secondary">
<span>📡</span> API 状态
</a>
<a href="https://github.com/CJackHwang/ds2api" class="btn btn-secondary" target="_blank">
<span>📦</span> GitHub
</a>
</div>
<div class="features-grid">
<div class="feature-card">
<span class="feature-icon">🚀</span>
<h3>全面兼容</h3>
<p>完美适配 OpenAI 与 Claude API 格式,无缝集成现有工具。</p>
</div>
<div class="feature-card">
<span class="feature-icon">⚖️</span>
<h3>负载均衡</h3>
<p>内置智能轮询机制,支持多账号并发,稳定高效。</p>
</div>
<div class="feature-card">
<span class="feature-icon">🧠</span>
<h3>深度思考</h3>
<p>完整支持 推理过程输出,让思考可见。</p>
</div>
<div class="feature-card">
<span class="feature-icon">🔍</span>
<h3>联网搜索</h3>
<p>集成 DeepSeek 原生搜索能力,获取最新实时资讯。</p>
</div>
</div>
<footer>
<p>&copy; 2026 DS2API Project. Designed for flexibility & performance.</p>
</footer>
</div>
</body>
</html>"""
@router.get("/")
def index(request: Request):
return HTMLResponse(content=WELCOME_HTML)
@router.get("/admin")
@router.get("/admin/{path:path}")
async def webui(request: Request, path: str = ""):
"""提供 WebUI 静态文件"""
# 检查 static/admin 目录是否存在
if not os.path.isdir(STATIC_ADMIN_DIR):
return HTMLResponse(
content="<h1>WebUI not built</h1><p>Run <code>cd webui && npm run build</code> first.</p>",
status_code=404
)
# 如果请求的是具体文件(如 js, css
if path and "." in path:
file_path = os.path.join(STATIC_ADMIN_DIR, path)
if os.path.isfile(file_path):
return FileResponse(file_path)
return HTMLResponse(content="Not Found", status_code=404)
# 否则返回 index.htmlSPA 路由)
index_path = os.path.join(STATIC_ADMIN_DIR, "index.html")
if os.path.isfile(index_path):
return FileResponse(index_path)
return HTMLResponse(content="index.html not found", status_code=404)
# -*- coding: utf-8 -*-
"""首页和 WebUI 路由"""
import os
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, FileResponse
from core.config import STATIC_ADMIN_DIR
router = APIRouter()
# 首页 HTML内嵌避免依赖模板目录
WELCOME_HTML = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DS2API - DeepSeek to OpenAI API</title>
<meta name="description" content="DS2API - 将 DeepSeek 网页版转换为 OpenAI 兼容 API">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E">
<style>
:root {
--primary: #f59e0b;
--primary-glow: rgba(245, 158, 11, 0.4);
--secondary: #ef4444;
--bg: #030712;
--card-bg: rgba(255, 255, 255, 0.03);
--card-border: rgba(255, 255, 255, 0.08);
--text-main: #f9fafb;
--text-dim: #9ca3af;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg);
color: var(--text-main);
min-height: 100vh;
overflow-x: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
/* Animated Background */
.bg-glow {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background:
radial-gradient(circle at 20% 30%, rgba(245, 158, 11, 0.05) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(239, 68, 68, 0.05) 0%, transparent 40%);
}
.blob {
position: absolute;
width: 400px;
height: 400px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
filter: blur(80px);
opacity: 0.15;
border-radius: 50%;
z-index: -1;
animation: move 20s infinite alternate;
}
@keyframes move {
from { transform: translate(-10%, -10%) scale(1); }
to { transform: translate(10%, 10%) scale(1.1); }
}
.container {
width: 100%;
max-width: 900px;
padding: 2rem;
text-align: center;
}
.logo-section {
margin-bottom: 3rem;
animation: fadeInUp 0.8s ease-out;
}
.logo {
font-family: 'Orbitron', sans-serif;
font-size: clamp(3rem, 10vw, 5rem);
font-weight: 700;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -2px;
margin-bottom: 0.5rem;
display: inline-block;
}
.subtitle {
color: var(--text-dim);
font-size: 1.25rem;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 4rem;
flex-wrap: wrap;
animation: fadeInUp 0.8s ease-out 0.2s backwards;
}
.btn {
padding: 0.8rem 2rem;
border-radius: 12px;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
box-shadow: 0 4px 15px var(--primary-glow);
}
.btn-primary:hover {
transform: translateY(-3px) scale(1.02);
box-shadow: 0 8px 25px var(--primary-glow);
}
.btn-secondary {
background: var(--card-bg);
color: var(--text-main);
border: 1px solid var(--card-border);
backdrop-filter: blur(10px);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
animation: fadeInUp 0.8s ease-out 0.4s backwards;
}
.feature-card {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 1.5rem;
text-align: left;
transition: all 0.3s ease;
backdrop-filter: blur(8px);
}
.feature-card:hover {
border-color: rgba(245, 158, 11, 0.3);
background: rgba(255, 255, 255, 0.05);
transform: translateY(-5px);
}
.feature-icon {
font-size: 1.5rem;
margin-bottom: 1rem;
display: block;
}
.feature-card h3 {
font-size: 1.1rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.feature-card p {
color: var(--text-dim);
font-size: 0.9rem;
line-height: 1.5;
}
footer {
margin-top: 4rem;
padding: 2rem;
color: var(--text-dim);
font-size: 0.875rem;
animation: fadeInUp 0.8s ease-out 0.6s backwards;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 640px) {
.logo { font-size: 3.5rem; }
.container { padding: 1.5rem; }
}
</style>
</head>
<body>
<div class="bg-glow"></div>
<div class="blob" style="top: 10%; left: 15%;"></div>
<div class="blob" style="bottom: 10%; right: 15%; animation-delay: -5s;"></div>
<div class="container">
<header class="logo-section">
<div class="logo">DS2API</div>
<p class="subtitle">DeepSeek to OpenAI & Claude Compatible API Interface</p>
</header>
<div class="actions">
<a href="/admin" class="btn btn-primary">
<span>🎛️</span> 管理面板
</a>
<a href="/v1/models" class="btn btn-secondary">
<span>📡</span> API 状态
</a>
<a href="https://github.com/CJackHwang/ds2api" class="btn btn-secondary" target="_blank">
<span>📦</span> GitHub
</a>
</div>
<div class="features-grid">
<div class="feature-card">
<span class="feature-icon">🚀</span>
<h3>全面兼容</h3>
<p>完美适配 OpenAI 与 Claude API 格式,无缝集成现有工具。</p>
</div>
<div class="feature-card">
<span class="feature-icon">⚖️</span>
<h3>负载均衡</h3>
<p>内置智能轮询机制,支持多账号并发,稳定高效。</p>
</div>
<div class="feature-card">
<span class="feature-icon">🧠</span>
<h3>深度思考</h3>
<p>完整支持 推理过程输出,让思考可见。</p>
</div>
<div class="feature-card">
<span class="feature-icon">🔍</span>
<h3>联网搜索</h3>
<p>集成 DeepSeek 原生搜索能力,获取最新实时资讯。</p>
</div>
</div>
<footer>
<p>&copy; 2026 DS2API Project. Designed for flexibility & performance.</p>
</footer>
</div>
</body>
</html>"""
@router.get("/")
def index(request: Request):
return HTMLResponse(content=WELCOME_HTML)
@router.get("/admin")
@router.get("/admin/{path:path}")
async def webui(request: Request, path: str = ""):
"""提供 WebUI 静态文件"""
# 检查 static/admin 目录是否存在
if not os.path.isdir(STATIC_ADMIN_DIR):
return HTMLResponse(
content="<h1>WebUI not built</h1><p>Run <code>cd webui && npm run build</code> first.</p>",
status_code=404
)
# 如果请求的是具体文件(如 js, css
if path and "." in path:
file_path = os.path.join(STATIC_ADMIN_DIR, path)
if os.path.isfile(file_path):
return FileResponse(file_path)
return HTMLResponse(content="Not Found", status_code=404)
# 否则返回 index.htmlSPA 路由)
index_path = os.path.join(STATIC_ADMIN_DIR, "index.html")
if os.path.isfile(index_path):
return FileResponse(index_path)
return HTMLResponse(content="index.html not found", status_code=404)

View File

@@ -1,43 +1,43 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- SEO Meta Tags -->
<title>DS2API - DeepSeek API 管理面板</title>
<meta name="description" content="DS2API 管理面板 - 轻松管理 DeepSeek 账号、测试 API、同步 Vercel 配置" />
<meta name="keywords" content="DeepSeek, API, OpenAI, 管理面板, DS2API" />
<meta name="author" content="CJackHwang" />
<meta name="robots" content="noindex, nofollow" />
<!-- Open Graph / Social -->
<meta property="og:type" content="website" />
<meta property="og:title" content="DS2API - DeepSeek API 管理面板" />
<meta property="og:description" content="轻松管理 DeepSeek 账号、测试 API、同步 Vercel 配置" />
<meta property="og:site_name" content="DS2API" />
<!-- PWA / Mobile -->
<meta name="theme-color" content="#f59e0b" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="DS2API" />
<!-- Favicon - using data URI for orange-yellow gradient icon -->
<link rel="icon" type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/admin/assets/index-C7aw1GYL.js"></script>
<link rel="stylesheet" crossorigin href="/admin/assets/index-D9_KYhGM.css">
</head>
<body>
<div id="root"></div>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- SEO Meta Tags -->
<title>DS2API - DeepSeek API 管理面板</title>
<meta name="description" content="DS2API 管理面板 - 轻松管理 DeepSeek 账号、测试 API、同步 Vercel 配置" />
<meta name="keywords" content="DeepSeek, API, OpenAI, 管理面板, DS2API" />
<meta name="author" content="CJackHwang" />
<meta name="robots" content="noindex, nofollow" />
<!-- Open Graph / Social -->
<meta property="og:type" content="website" />
<meta property="og:title" content="DS2API - DeepSeek API 管理面板" />
<meta property="og:description" content="轻松管理 DeepSeek 账号、测试 API、同步 Vercel 配置" />
<meta property="og:site_name" content="DS2API" />
<!-- PWA / Mobile -->
<meta name="theme-color" content="#f59e0b" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="DS2API" />
<!-- Favicon - using data URI for orange-yellow gradient icon -->
<link rel="icon" type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23f59e0b'/%3E%3Cstop offset='100%25' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect rx='20' width='100' height='100' fill='url(%23g)'/%3E%3Ctext x='50' y='68' font-family='Arial,sans-serif' font-size='48' font-weight='bold' fill='white' text-anchor='middle'%3EDS%3C/text%3E%3C/svg%3E" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/admin/assets/index-Da8PBvaf.js"></script>
<link rel="stylesheet" crossorigin href="/admin/assets/index-BzdNSWuo.css">
</head>
<body>
<div id="root"></div>
</body>
</html>