Compare commits

...

6 Commits

Author SHA1 Message Date
CJACK
092532c625 feat: Add Vercel static builder and routes to serve admin assets. 2026-02-01 21:24:53 +08:00
CJACK
1a842c28d1 feat: Establish WebUI build process, update frontend assets, and add CI workflow. 2026-02-01 21:17:19 +08:00
CJACK.
ce0538fcd9 Merge pull request #4 from ronghuaxueleng/main
feat(webui): 添加 API 密钥自动生成和复制功能
2026-02-01 21:07:20 +08:00
qiangcao
eef4ec4a65 Merge branch 'CJackHwang:main' into main 2026-02-01 21:01:28 +08:00
CJACK
951c47f8c5 docs: Update copyright year to 2026 and generalize a feature description. 2026-02-01 20:41:46 +08:00
root
db80aebadc feat(webui): 添加 API 密钥自动生成和复制功能
- 添加密钥弹窗增加「生成」按钮,自动生成 sk-xxx 格式随机密钥
- 密钥列表添加复制按钮,点击复制完整密钥到剪贴板
- 复制成功显示内联 toast 提示
2026-02-01 20:32:59 +08:00
16 changed files with 3550 additions and 301 deletions

View File

@@ -17,4 +17,8 @@
#### 📝 补充信息 | Additional Information
<!-- Add any other context about the Pull Request here. -->
<!-- Add any other context about the Pull Request here. -->
---
> 💡 **提示**:如果修改了 `webui/` 目录下的文件PR 合并后 CI 会自动构建并提交产物,无需手动构建。

76
.github/workflows/build-webui.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
# 自动构建 WebUI 并提交构建产物
# 触发条件webui 目录下的文件变更
name: Build WebUI
on:
push:
branches:
- main
paths:
- 'webui/**'
- '.github/workflows/build-webui.yml'
pull_request:
branches:
- main
paths:
- 'webui/**'
# 允许手动触发
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
# 只在主仓库运行,避免 fork 仓库运行
if: github.repository == 'CJackHwang/ds2api'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: webui/package-lock.json
- name: Install dependencies
working-directory: webui
run: npm ci
- name: Build WebUI
working-directory: webui
run: npm run build
- name: Check for changes
id: check_changes
run: |
git add static/admin
if git diff --staged --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Commit and push changes
if: steps.check_changes.outputs.changed == 'true' && github.event_name == 'push'
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "chore: auto-build WebUI [skip ci]"
git push
- name: Upload build artifacts (for PR review)
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: webui-build
path: static/admin
retention-days: 7

3
.gitignore vendored
View File

@@ -55,7 +55,8 @@ webui/node_modules/
webui/dist/
.npm
.pnpm-store/
package-lock.json
# 保留 webui/package-lock.json 用于 CI 缓存
# package-lock.json # 如果有根目录的可以忽略
yarn.lock
pnpm-lock.yaml

90
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,90 @@
# 贡献指南
感谢你对 DS2API 的贡献!
## 开发环境设置
### 后端
```bash
# 1. 克隆仓库
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. 创建虚拟环境(推荐)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 3. 安装依赖
pip install -r requirements.txt
# 4. 配置
cp config.example.json config.json
# 编辑 config.json
# 5. 启动
python dev.py
```
### 前端 (WebUI)
```bash
cd webui
npm install
npm run dev
```
## 代码规范
- **Python**: 遵循 PEP 8使用 4 空格缩进
- **JavaScript/React**: 使用 4 空格缩进,使用函数组件
- **提交信息**: 使用语义化提交格式(如 `feat:`, `fix:`, `docs:`
## 提交 PR
1. Fork 本仓库
2. 创建功能分支 (`git checkout -b feature/xxx`)
3. 提交更改 (`git commit -m 'feat: 添加xxx功能'`)
4. 推送分支 (`git push origin feature/xxx`)
5. 创建 Pull Request
## WebUI 构建
> **重要**: 修改 `webui/` 目录后 **无需手动构建**
当 PR 合并到 `main` 分支后GitHub Actions 会自动:
1. 构建 WebUI
2. 提交构建产物到 `static/admin/`
如果需要本地构建(测试用):
```bash
./scripts/build-webui.sh
```
## 项目结构
```
ds2api/
├── app.py # FastAPI 应用入口
├── dev.py # 开发服务器
├── core/ # 核心模块
│ ├── auth.py # 账号认证与轮询
│ ├── config.py # 配置管理
│ ├── deepseek.py # DeepSeek API 调用
│ ├── models.py # 模型定义
│ ├── pow.py # PoW 计算
│ └── sse_parser.py # SSE 解析
├── routes/ # API 路由
│ ├── openai.py # OpenAI 兼容接口
│ ├── claude.py # Claude 兼容接口
│ ├── home.py # 首页路由
│ └── admin/ # 管理接口
├── webui/ # React WebUI 源码
├── static/admin/ # WebUI 构建产物(自动生成)
└── scripts/ # 辅助脚本
```
## 问题反馈
- 使用 [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) 报告问题
- 提供详细的复现步骤和日志信息

View File

@@ -125,6 +125,27 @@ 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 会自动处理。
---
## 生产环境部署

View File

@@ -1,19 +1,24 @@
# DS2API 依赖
# 安装命令: pip install -r requirements.txt
# Python 版本要求: >=3.9
# Web 框架
# ===== Web 框架 =====
fastapi>=0.110.0,<1.0.0
uvicorn[standard]>=0.24.0,<1.0.0
# HTTP 客户端
# ===== HTTP 客户端 =====
# curl_cffi: 支持 TLS 指纹模拟,绕过 Cloudflare 等防护
curl_cffi>=0.7.0
# httpx: 异步 HTTP 客户端,用于 Vercel API 调用
httpx>=0.25.0
# 模板引擎
# ===== 模板引擎 =====
jinja2>=3.1.0,<4.0.0
# Tokenizer(用于 token 计数)
# ===== Tokenizer =====
# 用于 token 计数(可选,不安装则使用估算方式)
transformers>=4.39.0,<5.0.0
# WASM 运行时(用于 PoW 计算)
# ===== WASM 运行时 =====
# 用于 DeepSeek PoW (Proof of Work) 计算
wasmtime>=14.0.0

View File

@@ -253,7 +253,7 @@ WELCOME_HTML = """<!DOCTYPE html>
<div class="feature-card">
<span class="feature-icon">🧠</span>
<h3>深度思考</h3>
<p>完整支持 DeepSeek-R1 推理过程输出,让思考可见。</p>
<p>完整支持 推理过程输出,让思考可见。</p>
</div>
<div class="feature-card">
<span class="feature-icon">🔍</span>
@@ -263,7 +263,7 @@ WELCOME_HTML = """<!DOCTYPE html>
</div>
<footer>
<p>&copy; 2024 DS2API Project. Designed for flexibility & performance.</p>
<p>&copy; 2026 DS2API Project. Designed for flexibility & performance.</p>
</footer>
</div>
</body>

22
scripts/build-webui.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# WebUI 构建脚本
# 用法: ./scripts/build-webui.sh
set -e
echo "🔨 Building WebUI..."
cd "$(dirname "$0")/../webui"
# 检查 node_modules
if [ ! -d "node_modules" ]; then
echo "📦 Installing dependencies..."
npm install
fi
# 构建
echo "🏗️ Running build..."
npm run build
echo "✅ WebUI built successfully!"
echo "📁 Output: static/admin/"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -32,8 +32,8 @@
<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">
<script type="module" crossorigin src="/admin/assets/index-C7aw1GYL.js"></script>
<link rel="stylesheet" crossorigin href="/admin/assets/index-D9_KYhGM.css">
</head>
<body>

View File

@@ -4,9 +4,21 @@
{
"src": "app.py",
"use": "@vercel/python"
},
{
"src": "static/**",
"use": "@vercel/static"
}
],
"routes": [
{
"src": "/admin/assets/(.*)",
"dest": "/static/admin/assets/$1"
},
{
"src": "/admin/(.+\\.[a-z]+)$",
"dest": "/static/admin/$1"
},
{
"src": "/(.*)",
"dest": "app.py"

2986
webui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,9 @@ import {
MoreHorizontal,
X,
Server,
ShieldCheck
ShieldCheck,
Copy,
Check
} from 'lucide-react'
import clsx from 'clsx'
@@ -18,6 +20,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
const [showAddKey, setShowAddKey] = useState(false)
const [showAddAccount, setShowAddAccount] = useState(false)
const [newKey, setNewKey] = useState('')
const [copiedKey, setCopiedKey] = useState(null)
const [newAccount, setNewAccount] = useState({ email: '', mobile: '', password: '' })
const [loading, setLoading] = useState(false)
const [validating, setValidating] = useState({})
@@ -298,15 +301,34 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
{config.keys?.length > 0 ? (
config.keys.map((key, i) => (
<div key={i} className="p-4 flex items-center justify-between hover:bg-muted/50 transition-colors group">
<div className="font-mono text-sm bg-muted/50 px-3 py-1 rounded inline-block">
{key.slice(0, 16)}****
<div className="flex items-center gap-2">
<div className="font-mono text-sm bg-muted/50 px-3 py-1 rounded inline-block">
{key.slice(0, 16)}****
</div>
{copiedKey === key && (
<span className="text-xs text-green-500 animate-pulse">已复制</span>
)}
</div>
<div className="flex items-center gap-1">
<button
onClick={() => {
navigator.clipboard.writeText(key)
setCopiedKey(key)
setTimeout(() => setCopiedKey(null), 2000)
}}
className="p-2 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-md transition-colors opacity-0 group-hover:opacity-100"
title="复制密钥"
>
{copiedKey === key ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
</button>
<button
onClick={() => deleteKey(key)}
className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-md transition-colors opacity-0 group-hover:opacity-100"
title="删除密钥"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<button
onClick={() => deleteKey(key)}
className="p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-md transition-colors opacity-0 group-hover:opacity-100"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))
) : (
@@ -445,14 +467,24 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium mb-1.5">新密钥值</label>
<input
type="text"
className="input-field bg-[#09090b]"
placeholder="输入自定义 API 密钥"
value={newKey}
onChange={e => setNewKey(e.target.value)}
autoFocus
/>
<div className="flex gap-2">
<input
type="text"
className="input-field bg-[#09090b] flex-1"
placeholder="输入自定义 API 密钥"
value={newKey}
onChange={e => setNewKey(e.target.value)}
autoFocus
/>
<button
type="button"
onClick={() => setNewKey('sk-' + crypto.randomUUID().replace(/-/g, ''))}
className="px-3 py-2 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-sm font-medium border border-border whitespace-nowrap"
>
生成
</button>
</div>
<p className="text-xs text-muted-foreground mt-1.5">点击生成自动创建随机密钥</p>
</div>
<div className="flex justify-end gap-2 pt-2">
<button onClick={() => setShowAddKey(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">取消</button>